Merge pull request #13938 from jackrabbithanna/core-725
[civicrm-core.git] / CRM / Extension / Info.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Metadata for an extension (e.g. the extension's "info.xml" file)
30 *
31 * @package CRM
32 * @copyright CiviCRM LLC (c) 2004-2019
33 */
34 class CRM_Extension_Info {
35
36 /**
37 * Extension info file name.
38 */
39 const FILENAME = 'info.xml';
40
41 public $key = NULL;
42 public $type = NULL;
43 public $name = NULL;
44 public $label = NULL;
45 public $file = NULL;
46
47 /**
48 * @var array
49 * Each item is a specification like:
50 * array('type'=>'psr4', 'namespace'=>'Foo\Bar', 'path'=>'/foo/bar').
51 */
52 public $classloader = array();
53
54 /**
55 * @var array
56 * Each item is they key-name of an extension required by this extension.
57 */
58 public $requires = array();
59
60 /**
61 * Load extension info an XML file.
62 *
63 * @param $file
64 *
65 * @throws CRM_Extension_Exception_ParseException
66 * @return CRM_Extension_Info
67 */
68 public static function loadFromFile($file) {
69 list ($xml, $error) = CRM_Utils_XML::parseFile($file);
70 if ($xml === FALSE) {
71 throw new CRM_Extension_Exception_ParseException("Failed to parse info XML: $error");
72 }
73
74 $instance = new CRM_Extension_Info();
75 $instance->parse($xml);
76 return $instance;
77 }
78
79 /**
80 * Load extension info a string.
81 *
82 * @param string $string
83 * XML content.
84 *
85 * @throws CRM_Extension_Exception_ParseException
86 * @return CRM_Extension_Info
87 */
88 public static function loadFromString($string) {
89 list ($xml, $error) = CRM_Utils_XML::parseString($string);
90 if ($xml === FALSE) {
91 throw new CRM_Extension_Exception_ParseException("Failed to parse info XML: $string");
92 }
93
94 $instance = new CRM_Extension_Info();
95 $instance->parse($xml);
96 return $instance;
97 }
98
99 /**
100 * Build a reverse-dependency map.
101 *
102 * @param array $infos
103 * The universe of available extensions.
104 * Ex: $infos['org.civicrm.foobar'] = new CRM_Extension_Info().
105 * @return array
106 * If "org.civicrm.api" is required by "org.civicrm.foo", then return
107 * array('org.civicrm.api' => array(CRM_Extension_Info[org.civicrm.foo])).
108 * Array(string $key => array $requiredBys).
109 */
110 public static function buildReverseMap($infos) {
111 $revMap = array();
112 foreach ($infos as $info) {
113 foreach ($info->requires as $key) {
114 $revMap[$key][] = $info;
115 }
116 }
117 return $revMap;
118 }
119
120 /**
121 * @param null $key
122 * @param null $type
123 * @param null $name
124 * @param null $label
125 * @param null $file
126 */
127 public function __construct($key = NULL, $type = NULL, $name = NULL, $label = NULL, $file = NULL) {
128 $this->key = $key;
129 $this->type = $type;
130 $this->name = $name;
131 $this->label = $label;
132 $this->file = $file;
133 }
134
135 /**
136 * Copy attributes from an XML document to $this
137 *
138 * @param SimpleXMLElement $info
139 */
140 public function parse($info) {
141 $this->key = (string) $info->attributes()->key;
142 $this->type = (string) $info->attributes()->type;
143 $this->file = (string) $info->file;
144 $this->label = (string) $info->name;
145
146 // Convert first level variables to CRM_Core_Extension properties
147 // and deeper into arrays. An exception for URLS section, since
148 // we want them in special format.
149 foreach ($info as $attr => $val) {
150 if (count($val->children()) == 0) {
151 $this->$attr = (string) $val;
152 }
153 elseif ($attr === 'urls') {
154 $this->urls = array();
155 foreach ($val->url as $url) {
156 $urlAttr = (string) $url->attributes()->desc;
157 $this->urls[$urlAttr] = (string) $url;
158 }
159 ksort($this->urls);
160 }
161 elseif ($attr === 'classloader') {
162 $this->classloader = array();
163 foreach ($val->psr4 as $psr4) {
164 $this->classloader[] = array(
165 'type' => 'psr4',
166 'prefix' => (string) $psr4->attributes()->prefix,
167 'path' => (string) $psr4->attributes()->path,
168 );
169 }
170 }
171 elseif ($attr === 'requires') {
172 $this->requires = $this->filterRequirements($val);
173 }
174 else {
175 $this->$attr = CRM_Utils_XML::xmlObjToArray($val);
176 }
177 }
178 }
179
180 /**
181 * Filter out invalid requirements, e.g. extensions that have been moved to core.
182 *
183 * @param SimpleXMLElement $requirements
184 * @return array
185 */
186 public function filterRequirements($requirements) {
187 $filtered = [];
188 $compatInfo = CRM_Extension_System::getCompatibilityInfo();
189 foreach ($requirements->ext as $ext) {
190 $ext = (string) $ext;
191 if (empty($compatInfo[$ext]['obsolete'])) {
192 $filtered[] = $ext;
193 }
194 }
195 return $filtered;
196 }
197
198 }