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