3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
13 * Metadata for an extension (e.g. the extension's "info.xml" file)
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 class CRM_Extension_Info
{
21 * Extension info file name.
23 const FILENAME
= 'info.xml';
52 * Each item is a specification like:
53 * array('type'=>'psr4', 'namespace'=>'Foo\Bar', 'path'=>'/foo/bar').
55 public $classloader = [];
59 * Each item is they key-name of an extension required by this extension.
61 public $requires = [];
65 * List of expected mixins.
72 * List of strings (tag-names).
79 * Ex: [0 => ['name' => 'Alice', 'email' => 'a@b', 'homepage' => 'https://example.com', 'role' => 'Person']]
85 * The current maintainer at time of publication.
86 * This is deprecated in favor of $authors.
89 public $maintainer = NULL;
93 * The name of a class which handles the install/upgrade lifecycle.
94 * @see \CRM_Extension_Upgrader_Interface
96 public $upgrader = NULL;
99 * Load extension info an XML file.
103 * @throws CRM_Extension_Exception_ParseException
104 * @return CRM_Extension_Info
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");
112 $instance = new CRM_Extension_Info();
113 $instance->parse($xml);
118 * Load extension info a string.
120 * @param string $string
123 * @throws CRM_Extension_Exception_ParseException
124 * @return CRM_Extension_Info
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");
132 $instance = new CRM_Extension_Info();
133 $instance->parse($xml);
138 * Build a reverse-dependency map.
140 * @param array $infos
141 * The universe of available extensions.
142 * Ex: $infos['org.civicrm.foobar'] = new CRM_Extension_Info().
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).
148 public static function buildReverseMap($infos) {
150 foreach ($infos as $info) {
151 foreach ($info->requires
as $key) {
152 $revMap[$key][] = $info;
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
165 public function __construct($key = NULL, $type = NULL, $name = NULL, $label = NULL, $file = NULL) {
169 $this->label
= $label;
174 * Copy attributes from an XML document to $this
176 * @param SimpleXMLElement $info
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
;
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);
192 elseif ($attr === 'urls') {
194 foreach ($val->url
as $url) {
195 $urlAttr = (string) $url->attributes()->desc
;
196 $this->urls
[$urlAttr] = (string) $url;
200 elseif ($attr === 'classloader') {
201 $this->classloader
= [];
202 foreach ($val->psr4
as $psr4) {
203 $this->classloader
[] = [
205 'prefix' => (string) $psr4->attributes()->prefix
,
206 'path' => (string) $psr4->attributes()->path
,
209 foreach ($val->psr0
as $psr0) {
210 $this->classloader
[] = [
212 'prefix' => (string) $psr0->attributes()->prefix
,
213 'path' => (string) $psr0->attributes()->path
,
217 elseif ($attr === 'tags') {
219 foreach ($val->tag
as $tag) {
220 $this->tags
[] = (string) $tag;
223 elseif ($attr === 'mixins') {
225 foreach ($val->mixin
as $mixin) {
226 $this->mixins
[] = (string) $mixin;
229 elseif ($attr === 'requires') {
230 $this->requires
= $this->filterRequirements($val);
232 elseif ($attr === 'maintainer') {
233 $this->maintainer
= CRM_Utils_XML
::xmlObjToArray($val);
235 'name' => (string) $val->author
,
236 'email' => (string) $val->email
,
237 'role' => 'Maintainer',
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];
249 $this->$attr = CRM_Utils_XML
::xmlObjToArray($val);
255 * Filter out invalid requirements, e.g. extensions that have been moved to core.
257 * @param SimpleXMLElement $requirements
260 public function filterRequirements($requirements) {
262 $compatInfo = CRM_Extension_System
::getCompatibilityInfo();
263 foreach ($requirements->ext
as $ext) {
264 $ext = (string) $ext;
265 if (empty($compatInfo[$ext]['obsolete'])) {