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