Resolve docblock inaccuracy in CRM_Contribute_Form_Task_TaskTrait
[civicrm-core.git] / CRM / Extension / Info.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Metadata for an extension (e.g. the extension's "info.xml" file)
14 *
15 * @package CRM
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 */
18 class CRM_Extension_Info {
19
20 /**
21 * Extension info file name.
22 */
23 const FILENAME = 'info.xml';
24
25 /**
26 * @var string|null
27 */
28 public $key = NULL;
29
30 /**
31 * @var string|null
32 */
33 public $type = NULL;
34
35 /**
36 * @var string|null
37 */
38 public $name = NULL;
39
40 /**
41 * @var string|null
42 */
43 public $label = NULL;
44
45 /**
46 * @var string|null
47 */
48 public $file = NULL;
49
50 /**
51 * @var array
52 * Each item is a specification like:
53 * array('type'=>'psr4', 'namespace'=>'Foo\Bar', 'path'=>'/foo/bar').
54 */
55 public $classloader = [];
56
57 /**
58 * @var array
59 * Each item is they key-name of an extension required by this extension.
60 */
61 public $requires = [];
62
63 /**
64 * @var array
65 * List of expected mixins.
66 * Ex: ['civix@2.0.0']
67 */
68 public $mixins = [];
69
70 /**
71 * @var array
72 * List of strings (tag-names).
73 */
74 public $tags = [];
75
76 /**
77 * @var array
78 * List of authors.
79 * Ex: [0 => ['name' => 'Alice', 'email' => 'a@b', 'homepage' => 'https://example.com', 'role' => 'Person']]
80 */
81 public $authors = [];
82
83 /**
84 * @var array|null
85 * The current maintainer at time of publication.
86 * This is deprecated in favor of $authors.
87 * @deprecated
88 */
89 public $maintainer = NULL;
90
91 /**
92 * @var string|null
93 * The name of a class which handles the install/upgrade lifecycle.
94 * @see \CRM_Extension_Upgrader_Interface
95 */
96 public $upgrader = NULL;
97
98 /**
99 * Load extension info an XML file.
100 *
101 * @param $file
102 *
103 * @throws CRM_Extension_Exception_ParseException
104 * @return CRM_Extension_Info
105 */
106 public static function loadFromFile($file) {
107 list ($xml, $error) = CRM_Utils_XML::parseFile($file);
108 if ($xml === FALSE) {
109 throw new CRM_Extension_Exception_ParseException("Failed to parse info XML: $error");
110 }
111
112 $instance = new CRM_Extension_Info();
113 $instance->parse($xml);
114 return $instance;
115 }
116
117 /**
118 * Load extension info a string.
119 *
120 * @param string $string
121 * XML content.
122 *
123 * @throws CRM_Extension_Exception_ParseException
124 * @return CRM_Extension_Info
125 */
126 public static function loadFromString($string) {
127 list ($xml, $error) = CRM_Utils_XML::parseString($string);
128 if ($xml === FALSE) {
129 throw new CRM_Extension_Exception_ParseException("Failed to parse info XML: $string");
130 }
131
132 $instance = new CRM_Extension_Info();
133 $instance->parse($xml);
134 return $instance;
135 }
136
137 /**
138 * Build a reverse-dependency map.
139 *
140 * @param array $infos
141 * The universe of available extensions.
142 * Ex: $infos['org.civicrm.foobar'] = new CRM_Extension_Info().
143 * @return array
144 * If "org.civicrm.api" is required by "org.civicrm.foo", then return
145 * array('org.civicrm.api' => array(CRM_Extension_Info[org.civicrm.foo])).
146 * Array(string $key => array $requiredBys).
147 */
148 public static function buildReverseMap($infos) {
149 $revMap = [];
150 foreach ($infos as $info) {
151 foreach ($info->requires as $key) {
152 $revMap[$key][] = $info;
153 }
154 }
155 return $revMap;
156 }
157
158 /**
159 * @param string|null $key
160 * @param string|null $type
161 * @param string|null $name
162 * @param string|null $label
163 * @param string|null $file
164 */
165 public function __construct($key = NULL, $type = NULL, $name = NULL, $label = NULL, $file = NULL) {
166 $this->key = $key;
167 $this->type = $type;
168 $this->name = $name;
169 $this->label = $label;
170 $this->file = $file;
171 }
172
173 /**
174 * Copy attributes from an XML document to $this
175 *
176 * @param SimpleXMLElement $info
177 */
178 public function parse($info) {
179 $this->key = (string) $info->attributes()->key;
180 $this->type = (string) $info->attributes()->type;
181 $this->file = (string) $info->file;
182 $this->label = (string) $info->name;
183 $this->upgrader = (string) $info->upgrader;
184
185 // Convert first level variables to CRM_Core_Extension properties
186 // and deeper into arrays. An exception for URLS section, since
187 // we want them in special format.
188 foreach ($info as $attr => $val) {
189 if (count($val->children()) == 0) {
190 $this->$attr = trim((string) $val);
191 }
192 elseif ($attr === 'urls') {
193 $this->urls = [];
194 foreach ($val->url as $url) {
195 $urlAttr = (string) $url->attributes()->desc;
196 $this->urls[$urlAttr] = (string) $url;
197 }
198 ksort($this->urls);
199 }
200 elseif ($attr === 'classloader') {
201 $this->classloader = [];
202 foreach ($val->psr4 as $psr4) {
203 $this->classloader[] = [
204 'type' => 'psr4',
205 'prefix' => (string) $psr4->attributes()->prefix,
206 'path' => (string) $psr4->attributes()->path,
207 ];
208 }
209 foreach ($val->psr0 as $psr0) {
210 $this->classloader[] = [
211 'type' => 'psr0',
212 'prefix' => (string) $psr0->attributes()->prefix,
213 'path' => (string) $psr0->attributes()->path,
214 ];
215 }
216 }
217 elseif ($attr === 'tags') {
218 $this->tags = [];
219 foreach ($val->tag as $tag) {
220 $this->tags[] = (string) $tag;
221 }
222 }
223 elseif ($attr === 'mixins') {
224 $this->mixins = [];
225 foreach ($val->mixin as $mixin) {
226 $this->mixins[] = (string) $mixin;
227 }
228 }
229 elseif ($attr === 'requires') {
230 $this->requires = $this->filterRequirements($val);
231 }
232 elseif ($attr === 'maintainer') {
233 $this->maintainer = CRM_Utils_XML::xmlObjToArray($val);
234 $this->authors[] = [
235 'name' => (string) $val->author,
236 'email' => (string) $val->email,
237 'role' => 'Maintainer',
238 ];
239 }
240 elseif ($attr === 'authors') {
241 foreach ($val->author as $author) {
242 $this->authors[] = $thisAuthor = CRM_Utils_XML::xmlObjToArray($author);
243 if ('maintainer' === strtolower($thisAuthor['role'] ?? '')) {
244 $this->maintainer = ['author' => $thisAuthor['name'], 'email' => $thisAuthor['email'] ?? NULL];
245 }
246 }
247 }
248 else {
249 $this->$attr = CRM_Utils_XML::xmlObjToArray($val);
250 }
251 }
252 }
253
254 /**
255 * Filter out invalid requirements, e.g. extensions that have been moved to core.
256 *
257 * @param SimpleXMLElement $requirements
258 * @return array
259 */
260 public function filterRequirements($requirements) {
261 $filtered = [];
262 $compatInfo = CRM_Extension_System::getCompatibilityInfo();
263 foreach ($requirements->ext as $ext) {
264 $ext = (string) $ext;
265 if (empty($compatInfo[$ext]['obsolete'])) {
266 $filtered[] = $ext;
267 }
268 }
269 return $filtered;
270 }
271
272 }