xref: /dokuwiki/lib/plugins/extension/Extension.php (revision 2ee9c3059314f83679b47395536291737a4dda19)
1cf2dcf1bSAndreas Gohr<?php
2cf2dcf1bSAndreas Gohr
3cf2dcf1bSAndreas Gohrnamespace dokuwiki\plugin\extension;
4cf2dcf1bSAndreas Gohr
5cf2dcf1bSAndreas Gohruse dokuwiki\Extension\PluginController;
6cf2dcf1bSAndreas Gohruse dokuwiki\Utf8\PhpString;
7cf2dcf1bSAndreas Gohruse RuntimeException;
8cf2dcf1bSAndreas Gohr
9cf2dcf1bSAndreas Gohrclass Extension
10cf2dcf1bSAndreas Gohr{
117c184cfcSAndreas Gohr    public const TYPE_PLUGIN = 'plugin';
127c184cfcSAndreas Gohr    public const TYPE_TEMPLATE = 'template';
13cf2dcf1bSAndreas Gohr
143e63733dSAndreas Gohr    /** @var string[] The types the API uses for plugin components */
153e63733dSAndreas Gohr    public const COMPONENT_TYPES = [
163e63733dSAndreas Gohr        1 => 'Syntax',
173e63733dSAndreas Gohr        2 => 'Admin',
183e63733dSAndreas Gohr        4 => 'Action',
193e63733dSAndreas Gohr        8 => 'Render',
203e63733dSAndreas Gohr        16 => 'Helper',
213e63733dSAndreas Gohr        32 => 'Template',
223e63733dSAndreas Gohr        64 => 'Remote',
233e63733dSAndreas Gohr        128 => 'Auth',
243e63733dSAndreas Gohr        256 => 'CLI',
253e63733dSAndreas Gohr        512 => 'CSS/JS-only',
263e63733dSAndreas Gohr    ];
273e63733dSAndreas Gohr
28cf2dcf1bSAndreas Gohr    /** @var string "plugin"|"template" */
29cf2dcf1bSAndreas Gohr    protected string $type = self::TYPE_PLUGIN;
30cf2dcf1bSAndreas Gohr
31cf2dcf1bSAndreas Gohr    /** @var string The base name of this extension */
32cf2dcf1bSAndreas Gohr    protected string $base;
33cf2dcf1bSAndreas Gohr
3425d28a01SAndreas Gohr    /** @var string The current location of this extension */
3525d28a01SAndreas Gohr    protected string $currentDir = '';
36cf2dcf1bSAndreas Gohr
37cf2dcf1bSAndreas Gohr    /** @var array The local info array of the extension */
38cf2dcf1bSAndreas Gohr    protected array $localInfo = [];
39cf2dcf1bSAndreas Gohr
40cf2dcf1bSAndreas Gohr    /** @var array The remote info array of the extension */
41cf2dcf1bSAndreas Gohr    protected array $remoteInfo = [];
42cf2dcf1bSAndreas Gohr
437c9966a5SAndreas Gohr    /** @var Manager|null The manager for this extension */
4425d28a01SAndreas Gohr    protected ?Manager $manager = null;
45cf2dcf1bSAndreas Gohr
46cf2dcf1bSAndreas Gohr    // region Constructors
47cf2dcf1bSAndreas Gohr
48cf2dcf1bSAndreas Gohr    /**
49cf2dcf1bSAndreas Gohr     * The main constructor is private to force the use of the factory methods
50cf2dcf1bSAndreas Gohr     */
51cf2dcf1bSAndreas Gohr    protected function __construct()
52cf2dcf1bSAndreas Gohr    {
53cf2dcf1bSAndreas Gohr    }
54cf2dcf1bSAndreas Gohr
55cf2dcf1bSAndreas Gohr    /**
56a1e045f7SAndreas Gohr     * Initializes an extension from an id
57a1e045f7SAndreas Gohr     *
58a1e045f7SAndreas Gohr     * @param string $id The id of the extension
59a1e045f7SAndreas Gohr     * @return Extension
60a1e045f7SAndreas Gohr     */
61a1e045f7SAndreas Gohr    public static function createFromId($id)
62a1e045f7SAndreas Gohr    {
63a1e045f7SAndreas Gohr        $extension = new self();
64a1e045f7SAndreas Gohr        $extension->initFromId($id);
65a1e045f7SAndreas Gohr        return $extension;
66a1e045f7SAndreas Gohr    }
67a1e045f7SAndreas Gohr
68a1e045f7SAndreas Gohr    protected function initFromId($id)
69a1e045f7SAndreas Gohr    {
70a1e045f7SAndreas Gohr        [$type, $base] = $this->idToTypeBase($id);
71a1e045f7SAndreas Gohr        $this->type = $type;
72a1e045f7SAndreas Gohr        $this->base = $base;
73a1e045f7SAndreas Gohr        $this->readLocalInfo();
74a1e045f7SAndreas Gohr    }
75a1e045f7SAndreas Gohr
76a1e045f7SAndreas Gohr    /**
77cf2dcf1bSAndreas Gohr     * Initializes an extension from a directory
78cf2dcf1bSAndreas Gohr     *
79cf2dcf1bSAndreas Gohr     * The given directory might be the one where the extension has already been installed to
80cf2dcf1bSAndreas Gohr     * or it might be the extracted source in some temporary directory.
81cf2dcf1bSAndreas Gohr     *
82cf2dcf1bSAndreas Gohr     * @param string $dir Where the extension code is currently located
83cf2dcf1bSAndreas Gohr     * @param string|null $type TYPE_PLUGIN|TYPE_TEMPLATE, null for auto-detection
84cf2dcf1bSAndreas Gohr     * @param string $base The base name of the extension, null for auto-detection
85cf2dcf1bSAndreas Gohr     * @return Extension
86cf2dcf1bSAndreas Gohr     */
87cf2dcf1bSAndreas Gohr    public static function createFromDirectory($dir, $type = null, $base = null)
88cf2dcf1bSAndreas Gohr    {
89cf2dcf1bSAndreas Gohr        $extension = new self();
90cf2dcf1bSAndreas Gohr        $extension->initFromDirectory($dir, $type, $base);
91cf2dcf1bSAndreas Gohr        return $extension;
92cf2dcf1bSAndreas Gohr    }
93cf2dcf1bSAndreas Gohr
94cf2dcf1bSAndreas Gohr    protected function initFromDirectory($dir, $type = null, $base = null)
95cf2dcf1bSAndreas Gohr    {
96cf2dcf1bSAndreas Gohr        if (!is_dir($dir)) throw new RuntimeException('Directory not found: ' . $dir);
97e8fd67e9SAndreas Gohr        $this->currentDir = fullpath($dir);
98cf2dcf1bSAndreas Gohr
99cf2dcf1bSAndreas Gohr        if ($type === null || $type === self::TYPE_TEMPLATE) {
100cf2dcf1bSAndreas Gohr            if (
1018fe483c9SAndreas Gohr                file_exists($dir . '/template.info.txt') ||
102cf2dcf1bSAndreas Gohr                file_exists($dir . '/style.ini') ||
103cf2dcf1bSAndreas Gohr                file_exists($dir . '/main.php') ||
104cf2dcf1bSAndreas Gohr                file_exists($dir . '/detail.php') ||
105cf2dcf1bSAndreas Gohr                file_exists($dir . '/mediamanager.php')
106cf2dcf1bSAndreas Gohr            ) {
107cf2dcf1bSAndreas Gohr                $this->type = self::TYPE_TEMPLATE;
108cf2dcf1bSAndreas Gohr            }
109cf2dcf1bSAndreas Gohr        } else {
110cf2dcf1bSAndreas Gohr            $this->type = self::TYPE_PLUGIN;
111cf2dcf1bSAndreas Gohr        }
112cf2dcf1bSAndreas Gohr
113cf2dcf1bSAndreas Gohr        $this->readLocalInfo();
114cf2dcf1bSAndreas Gohr
115cf2dcf1bSAndreas Gohr        if ($base !== null) {
116cf2dcf1bSAndreas Gohr            $this->base = $base;
117cf2dcf1bSAndreas Gohr        } elseif (isset($this->localInfo['base'])) {
118cf2dcf1bSAndreas Gohr            $this->base = $this->localInfo['base'];
119cf2dcf1bSAndreas Gohr        } else {
120cf2dcf1bSAndreas Gohr            $this->base = basename($dir);
121cf2dcf1bSAndreas Gohr        }
122cf2dcf1bSAndreas Gohr    }
123cf2dcf1bSAndreas Gohr
124cf2dcf1bSAndreas Gohr    /**
125cf2dcf1bSAndreas Gohr     * Initializes an extension from remote data
126cf2dcf1bSAndreas Gohr     *
127cf2dcf1bSAndreas Gohr     * @param array $data The data as returned by the repository api
128cf2dcf1bSAndreas Gohr     * @return Extension
129cf2dcf1bSAndreas Gohr     */
130cf2dcf1bSAndreas Gohr    public static function createFromRemoteData($data)
131cf2dcf1bSAndreas Gohr    {
132cf2dcf1bSAndreas Gohr        $extension = new self();
133cf2dcf1bSAndreas Gohr        $extension->initFromRemoteData($data);
134cf2dcf1bSAndreas Gohr        return $extension;
135cf2dcf1bSAndreas Gohr    }
136cf2dcf1bSAndreas Gohr
137cf2dcf1bSAndreas Gohr    protected function initFromRemoteData($data)
138cf2dcf1bSAndreas Gohr    {
139cf2dcf1bSAndreas Gohr        if (!isset($data['plugin'])) throw new RuntimeException('Invalid remote data');
140cf2dcf1bSAndreas Gohr
141a1e045f7SAndreas Gohr        [$type, $base] = $this->idToTypeBase($data['plugin']);
142cf2dcf1bSAndreas Gohr        $this->remoteInfo = $data;
143cf2dcf1bSAndreas Gohr        $this->type = $type;
144cf2dcf1bSAndreas Gohr        $this->base = $base;
145cf2dcf1bSAndreas Gohr
146cf2dcf1bSAndreas Gohr        if ($this->isInstalled()) {
147cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
148cf2dcf1bSAndreas Gohr            $this->readLocalInfo();
149cf2dcf1bSAndreas Gohr        }
150cf2dcf1bSAndreas Gohr    }
151cf2dcf1bSAndreas Gohr
152cf2dcf1bSAndreas Gohr    // endregion
153cf2dcf1bSAndreas Gohr
154cf2dcf1bSAndreas Gohr    // region Getters
155cf2dcf1bSAndreas Gohr
156cf2dcf1bSAndreas Gohr    /**
1574fd6a1d7SAndreas Gohr     * @param bool $wrap If true, the id is wrapped in backticks
158cf2dcf1bSAndreas Gohr     * @return string The extension id (same as base but prefixed with "template:" for templates)
159cf2dcf1bSAndreas Gohr     */
1604fd6a1d7SAndreas Gohr    public function getId($wrap = false)
161cf2dcf1bSAndreas Gohr    {
162cf2dcf1bSAndreas Gohr        if ($this->type === self::TYPE_TEMPLATE) {
1634fd6a1d7SAndreas Gohr            $id = self::TYPE_TEMPLATE . ':' . $this->base;
1644fd6a1d7SAndreas Gohr        } else {
1654fd6a1d7SAndreas Gohr            $id = $this->base;
166cf2dcf1bSAndreas Gohr        }
1674fd6a1d7SAndreas Gohr        if ($wrap) $id = "`$id`";
1684fd6a1d7SAndreas Gohr        return $id;
169cf2dcf1bSAndreas Gohr    }
170cf2dcf1bSAndreas Gohr
171cf2dcf1bSAndreas Gohr    /**
172cf2dcf1bSAndreas Gohr     * Get the base name of this extension
173cf2dcf1bSAndreas Gohr     *
174cf2dcf1bSAndreas Gohr     * @return string
175cf2dcf1bSAndreas Gohr     */
176cf2dcf1bSAndreas Gohr    public function getBase()
177cf2dcf1bSAndreas Gohr    {
178cf2dcf1bSAndreas Gohr        return $this->base;
179cf2dcf1bSAndreas Gohr    }
180cf2dcf1bSAndreas Gohr
181cf2dcf1bSAndreas Gohr    /**
182cf2dcf1bSAndreas Gohr     * Get the type of the extension
183cf2dcf1bSAndreas Gohr     *
184cf2dcf1bSAndreas Gohr     * @return string "plugin"|"template"
185cf2dcf1bSAndreas Gohr     */
186cf2dcf1bSAndreas Gohr    public function getType()
187cf2dcf1bSAndreas Gohr    {
188cf2dcf1bSAndreas Gohr        return $this->type;
189cf2dcf1bSAndreas Gohr    }
190cf2dcf1bSAndreas Gohr
191cf2dcf1bSAndreas Gohr    /**
192cf2dcf1bSAndreas Gohr     * The current directory of the extension
193cf2dcf1bSAndreas Gohr     *
194cf2dcf1bSAndreas Gohr     * @return string|null
195cf2dcf1bSAndreas Gohr     */
196cf2dcf1bSAndreas Gohr    public function getCurrentDir()
197cf2dcf1bSAndreas Gohr    {
198cf2dcf1bSAndreas Gohr        // recheck that the current currentDir is still valid
199cf2dcf1bSAndreas Gohr        if ($this->currentDir && !is_dir($this->currentDir)) {
20025d28a01SAndreas Gohr            $this->currentDir = '';
201cf2dcf1bSAndreas Gohr        }
202cf2dcf1bSAndreas Gohr
203cf2dcf1bSAndreas Gohr        // if the extension is installed, then the currentDir is the install dir!
204cf2dcf1bSAndreas Gohr        if (!$this->currentDir && $this->isInstalled()) {
205cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
206cf2dcf1bSAndreas Gohr        }
207cf2dcf1bSAndreas Gohr
208cf2dcf1bSAndreas Gohr        return $this->currentDir;
209cf2dcf1bSAndreas Gohr    }
210cf2dcf1bSAndreas Gohr
211cf2dcf1bSAndreas Gohr    /**
212cf2dcf1bSAndreas Gohr     * Get the directory where this extension should be installed in
213cf2dcf1bSAndreas Gohr     *
214cf2dcf1bSAndreas Gohr     * Note: this does not mean that the extension is actually installed there
215cf2dcf1bSAndreas Gohr     *
216cf2dcf1bSAndreas Gohr     * @return string
217cf2dcf1bSAndreas Gohr     */
218cf2dcf1bSAndreas Gohr    public function getInstallDir()
219cf2dcf1bSAndreas Gohr    {
220cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
221176901c2SAndreas Gohr            $dir = dirname(tpl_incdir()) . '/' . $this->base;
222cf2dcf1bSAndreas Gohr        } else {
223cf2dcf1bSAndreas Gohr            $dir = DOKU_PLUGIN . $this->base;
224cf2dcf1bSAndreas Gohr        }
225cf2dcf1bSAndreas Gohr
22625d28a01SAndreas Gohr        return fullpath($dir);
227cf2dcf1bSAndreas Gohr    }
228cf2dcf1bSAndreas Gohr
229cf2dcf1bSAndreas Gohr
230cf2dcf1bSAndreas Gohr    /**
231cf2dcf1bSAndreas Gohr     * Get the display name of the extension
232cf2dcf1bSAndreas Gohr     *
233cf2dcf1bSAndreas Gohr     * @return string
234cf2dcf1bSAndreas Gohr     */
235cf2dcf1bSAndreas Gohr    public function getDisplayName()
236cf2dcf1bSAndreas Gohr    {
237cf2dcf1bSAndreas Gohr        return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType()));
238cf2dcf1bSAndreas Gohr    }
239cf2dcf1bSAndreas Gohr
240cf2dcf1bSAndreas Gohr    /**
241cf2dcf1bSAndreas Gohr     * Get the author name of the extension
242cf2dcf1bSAndreas Gohr     *
243cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the author info is missing
244cf2dcf1bSAndreas Gohr     */
245cf2dcf1bSAndreas Gohr    public function getAuthor()
246cf2dcf1bSAndreas Gohr    {
247cf2dcf1bSAndreas Gohr        return $this->getTag('author');
248cf2dcf1bSAndreas Gohr    }
249cf2dcf1bSAndreas Gohr
250cf2dcf1bSAndreas Gohr    /**
251cf2dcf1bSAndreas Gohr     * Get the email of the author of the extension if there is any
252cf2dcf1bSAndreas Gohr     *
253cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the email info is missing
254cf2dcf1bSAndreas Gohr     */
255cf2dcf1bSAndreas Gohr    public function getEmail()
256cf2dcf1bSAndreas Gohr    {
257cf2dcf1bSAndreas Gohr        // email is only in the local data
258cf2dcf1bSAndreas Gohr        return $this->localInfo['email'] ?? '';
259cf2dcf1bSAndreas Gohr    }
260cf2dcf1bSAndreas Gohr
261cf2dcf1bSAndreas Gohr    /**
262cf2dcf1bSAndreas Gohr     * Get the email id, i.e. the md5sum of the email
263cf2dcf1bSAndreas Gohr     *
264cf2dcf1bSAndreas Gohr     * @return string Empty string if no email is available
265cf2dcf1bSAndreas Gohr     */
266cf2dcf1bSAndreas Gohr    public function getEmailID()
267cf2dcf1bSAndreas Gohr    {
268cf2dcf1bSAndreas Gohr        if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid'];
269cf2dcf1bSAndreas Gohr        if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']);
270cf2dcf1bSAndreas Gohr        return '';
271cf2dcf1bSAndreas Gohr    }
272cf2dcf1bSAndreas Gohr
273cf2dcf1bSAndreas Gohr    /**
274cf2dcf1bSAndreas Gohr     * Get the description of the extension
275cf2dcf1bSAndreas Gohr     *
276cf2dcf1bSAndreas Gohr     * @return string Empty string if no description is available
277cf2dcf1bSAndreas Gohr     */
278cf2dcf1bSAndreas Gohr    public function getDescription()
279cf2dcf1bSAndreas Gohr    {
280cf2dcf1bSAndreas Gohr        return $this->getTag(['desc', 'description']);
281cf2dcf1bSAndreas Gohr    }
282cf2dcf1bSAndreas Gohr
283cf2dcf1bSAndreas Gohr    /**
284cf2dcf1bSAndreas Gohr     * Get the URL of the extension, usually a page on dokuwiki.org
285cf2dcf1bSAndreas Gohr     *
286cf2dcf1bSAndreas Gohr     * @return string
287cf2dcf1bSAndreas Gohr     */
288cf2dcf1bSAndreas Gohr    public function getURL()
289cf2dcf1bSAndreas Gohr    {
290cf2dcf1bSAndreas Gohr        return $this->getTag(
291cf2dcf1bSAndreas Gohr            'url',
292cf2dcf1bSAndreas Gohr            'https://www.dokuwiki.org/' .
293cf2dcf1bSAndreas Gohr            ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase()
294cf2dcf1bSAndreas Gohr        );
295cf2dcf1bSAndreas Gohr    }
296cf2dcf1bSAndreas Gohr
297cf2dcf1bSAndreas Gohr    /**
2987c9966a5SAndreas Gohr     * Get the version of the extension that is actually installed
2997c9966a5SAndreas Gohr     *
3007c9966a5SAndreas Gohr     * Returns an empty string if the version is not available
3017c9966a5SAndreas Gohr     *
3027c9966a5SAndreas Gohr     * @return string
3037c9966a5SAndreas Gohr     */
3047c9966a5SAndreas Gohr    public function getInstalledVersion()
3057c9966a5SAndreas Gohr    {
3067c9966a5SAndreas Gohr        return $this->localInfo['date'] ?? '';
3077c9966a5SAndreas Gohr    }
3087c9966a5SAndreas Gohr
3097c9966a5SAndreas Gohr    /**
3104fd6a1d7SAndreas Gohr     * Get the types of components this extension provides
3114fd6a1d7SAndreas Gohr     *
3124fd6a1d7SAndreas Gohr     * @return array int -> type
3134fd6a1d7SAndreas Gohr     */
3144fd6a1d7SAndreas Gohr    public function getComponentTypes()
3154fd6a1d7SAndreas Gohr    {
3168fe483c9SAndreas Gohr        // for installed extensions we can check the files
3178fe483c9SAndreas Gohr        if ($this->isInstalled()) {
3188fe483c9SAndreas Gohr            if ($this->isTemplate()) {
3198fe483c9SAndreas Gohr                return ['Template'];
3208fe483c9SAndreas Gohr            } else {
3218fe483c9SAndreas Gohr                $types = [];
3223e63733dSAndreas Gohr                foreach (self::COMPONENT_TYPES as $type) {
3238fe483c9SAndreas Gohr                    $check = strtolower($type);
3248fe483c9SAndreas Gohr                    if (
3258fe483c9SAndreas Gohr                        file_exists($this->getInstallDir() . '/' . $check . '.php') ||
3268fe483c9SAndreas Gohr                        is_dir($this->getInstallDir() . '/' . $check)
3278fe483c9SAndreas Gohr                    ) {
3288fe483c9SAndreas Gohr                        $types[] = $type;
3298fe483c9SAndreas Gohr                    }
3308fe483c9SAndreas Gohr                }
3318fe483c9SAndreas Gohr                return $types;
3328fe483c9SAndreas Gohr            }
3338fe483c9SAndreas Gohr        }
3348fe483c9SAndreas Gohr        // still, here? use the remote info
3354fd6a1d7SAndreas Gohr        return $this->getTag('types', []);
3364fd6a1d7SAndreas Gohr    }
3374fd6a1d7SAndreas Gohr
3384fd6a1d7SAndreas Gohr    /**
33925d28a01SAndreas Gohr     * Get a list of extension ids this extension depends on
34025d28a01SAndreas Gohr     *
34125d28a01SAndreas Gohr     * @return string[]
34225d28a01SAndreas Gohr     */
34325d28a01SAndreas Gohr    public function getDependencyList()
34425d28a01SAndreas Gohr    {
34525d28a01SAndreas Gohr        return $this->getTag('depends', []);
34625d28a01SAndreas Gohr    }
34725d28a01SAndreas Gohr
34825d28a01SAndreas Gohr    /**
349b69d74f1SAndreas Gohr     * Get a list of extensions that are currently installed, enabled and depend on this extension
350b69d74f1SAndreas Gohr     *
351b69d74f1SAndreas Gohr     * @return Extension[]
352b69d74f1SAndreas Gohr     */
353b69d74f1SAndreas Gohr    public function getDependants()
354b69d74f1SAndreas Gohr    {
355b69d74f1SAndreas Gohr        $local = new Local();
356b69d74f1SAndreas Gohr        $extensions = $local->getExtensions();
357b69d74f1SAndreas Gohr        $dependants = [];
358b69d74f1SAndreas Gohr        foreach ($extensions as $extension) {
359b69d74f1SAndreas Gohr            if (
360b69d74f1SAndreas Gohr                in_array($this->getId(), $extension->getDependencyList()) &&
361b69d74f1SAndreas Gohr                $extension->isEnabled()
362b69d74f1SAndreas Gohr            ) {
363b69d74f1SAndreas Gohr                $dependants[$extension->getId()] = $extension;
364b69d74f1SAndreas Gohr            }
365b69d74f1SAndreas Gohr        }
366b69d74f1SAndreas Gohr        return $dependants;
367b69d74f1SAndreas Gohr    }
368b69d74f1SAndreas Gohr
369b69d74f1SAndreas Gohr    /**
370b2a05b76SAndreas Gohr     * Return the minimum PHP version required by the extension
371b2a05b76SAndreas Gohr     *
372b2a05b76SAndreas Gohr     * Empty if not set
373b2a05b76SAndreas Gohr     *
374b2a05b76SAndreas Gohr     * @return string
375b2a05b76SAndreas Gohr     */
376b2a05b76SAndreas Gohr    public function getMinimumPHPVersion()
377b2a05b76SAndreas Gohr    {
378b2a05b76SAndreas Gohr        return $this->getTag('phpmin', '');
379b2a05b76SAndreas Gohr    }
380b2a05b76SAndreas Gohr
381b2a05b76SAndreas Gohr    /**
382b2a05b76SAndreas Gohr     * Return the minimum PHP version supported by the extension
383b2a05b76SAndreas Gohr     *
384b2a05b76SAndreas Gohr     * @return string
385b2a05b76SAndreas Gohr     */
386b2a05b76SAndreas Gohr    public function getMaximumPHPVersion()
387b2a05b76SAndreas Gohr    {
388b2a05b76SAndreas Gohr        return $this->getTag('phpmax', '');
389b2a05b76SAndreas Gohr    }
390b2a05b76SAndreas Gohr
391b2a05b76SAndreas Gohr    /**
392cf2dcf1bSAndreas Gohr     * Is this extension a template?
393cf2dcf1bSAndreas Gohr     *
394cf2dcf1bSAndreas Gohr     * @return bool false if it is a plugin
395cf2dcf1bSAndreas Gohr     */
396cf2dcf1bSAndreas Gohr    public function isTemplate()
397cf2dcf1bSAndreas Gohr    {
398cf2dcf1bSAndreas Gohr        return $this->type === self::TYPE_TEMPLATE;
399cf2dcf1bSAndreas Gohr    }
400cf2dcf1bSAndreas Gohr
401cf2dcf1bSAndreas Gohr    /**
402cf2dcf1bSAndreas Gohr     * Is the extension installed locally?
403cf2dcf1bSAndreas Gohr     *
404cf2dcf1bSAndreas Gohr     * @return bool
405cf2dcf1bSAndreas Gohr     */
406cf2dcf1bSAndreas Gohr    public function isInstalled()
407cf2dcf1bSAndreas Gohr    {
408cf2dcf1bSAndreas Gohr        return is_dir($this->getInstallDir());
409cf2dcf1bSAndreas Gohr    }
410cf2dcf1bSAndreas Gohr
411cf2dcf1bSAndreas Gohr    /**
412cf2dcf1bSAndreas Gohr     * Is the extension under git control?
413cf2dcf1bSAndreas Gohr     *
414cf2dcf1bSAndreas Gohr     * @return bool
415cf2dcf1bSAndreas Gohr     */
416cf2dcf1bSAndreas Gohr    public function isGitControlled()
417cf2dcf1bSAndreas Gohr    {
418cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) return false;
419cf2dcf1bSAndreas Gohr        return file_exists($this->getInstallDir() . '/.git');
420cf2dcf1bSAndreas Gohr    }
421cf2dcf1bSAndreas Gohr
422cf2dcf1bSAndreas Gohr    /**
423cf2dcf1bSAndreas Gohr     * If the extension is bundled
424cf2dcf1bSAndreas Gohr     *
425cf2dcf1bSAndreas Gohr     * @return bool If the extension is bundled
426cf2dcf1bSAndreas Gohr     */
427cf2dcf1bSAndreas Gohr    public function isBundled()
428cf2dcf1bSAndreas Gohr    {
429cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
430cf2dcf1bSAndreas Gohr        return $this->remoteInfo['bundled'] ?? in_array(
431cf2dcf1bSAndreas Gohr            $this->getId(),
432cf2dcf1bSAndreas Gohr            [
433cf2dcf1bSAndreas Gohr                'authad',
434cf2dcf1bSAndreas Gohr                'authldap',
435cf2dcf1bSAndreas Gohr                'authpdo',
436cf2dcf1bSAndreas Gohr                'authplain',
437cf2dcf1bSAndreas Gohr                'acl',
438cf2dcf1bSAndreas Gohr                'config',
439cf2dcf1bSAndreas Gohr                'extension',
440cf2dcf1bSAndreas Gohr                'info',
441cf2dcf1bSAndreas Gohr                'popularity',
442cf2dcf1bSAndreas Gohr                'revert',
443cf2dcf1bSAndreas Gohr                'safefnrecode',
444cf2dcf1bSAndreas Gohr                'styling',
445cf2dcf1bSAndreas Gohr                'testing',
446cf2dcf1bSAndreas Gohr                'usermanager',
447cf2dcf1bSAndreas Gohr                'logviewer',
448cf2dcf1bSAndreas Gohr                'template:dokuwiki'
449cf2dcf1bSAndreas Gohr            ]
450cf2dcf1bSAndreas Gohr        );
451cf2dcf1bSAndreas Gohr    }
452cf2dcf1bSAndreas Gohr
453cf2dcf1bSAndreas Gohr    /**
454cf2dcf1bSAndreas Gohr     * Is the extension protected against any modification (disable/uninstall)
455cf2dcf1bSAndreas Gohr     *
456cf2dcf1bSAndreas Gohr     * @return bool if the extension is protected
457cf2dcf1bSAndreas Gohr     */
458cf2dcf1bSAndreas Gohr    public function isProtected()
459cf2dcf1bSAndreas Gohr    {
460cf2dcf1bSAndreas Gohr        // never allow deinstalling the current auth plugin:
461cf2dcf1bSAndreas Gohr        global $conf;
462cf2dcf1bSAndreas Gohr        if ($this->getId() == $conf['authtype']) return true;
463cf2dcf1bSAndreas Gohr
464077f55feSAndreas Gohr        // disallow current template to be uninstalled
465077f55feSAndreas Gohr        if ($this->isTemplate() && ($this->getBase() === $conf['template'])) return true;
466cf2dcf1bSAndreas Gohr
467cf2dcf1bSAndreas Gohr        /** @var PluginController $plugin_controller */
468cf2dcf1bSAndreas Gohr        global $plugin_controller;
469cf2dcf1bSAndreas Gohr        $cascade = $plugin_controller->getCascade();
470cf2dcf1bSAndreas Gohr        return ($cascade['protected'][$this->getId()] ?? false);
471cf2dcf1bSAndreas Gohr    }
472cf2dcf1bSAndreas Gohr
473cf2dcf1bSAndreas Gohr    /**
474cf2dcf1bSAndreas Gohr     * Is the extension installed in the correct directory?
475cf2dcf1bSAndreas Gohr     *
476cf2dcf1bSAndreas Gohr     * @return bool
477cf2dcf1bSAndreas Gohr     */
478cf2dcf1bSAndreas Gohr    public function isInWrongFolder()
479cf2dcf1bSAndreas Gohr    {
4804fd6a1d7SAndreas Gohr        if (!$this->isInstalled()) return false;
481cf2dcf1bSAndreas Gohr        return $this->getInstallDir() != $this->currentDir;
482cf2dcf1bSAndreas Gohr    }
483cf2dcf1bSAndreas Gohr
484cf2dcf1bSAndreas Gohr    /**
485cf2dcf1bSAndreas Gohr     * Is the extension enabled?
486cf2dcf1bSAndreas Gohr     *
487cf2dcf1bSAndreas Gohr     * @return bool
488cf2dcf1bSAndreas Gohr     */
489cf2dcf1bSAndreas Gohr    public function isEnabled()
490cf2dcf1bSAndreas Gohr    {
491cf2dcf1bSAndreas Gohr        global $conf;
492cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
493cf2dcf1bSAndreas Gohr            return ($conf['template'] == $this->getBase());
494cf2dcf1bSAndreas Gohr        }
495cf2dcf1bSAndreas Gohr
496cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
497cf2dcf1bSAndreas Gohr        global $plugin_controller;
498cf2dcf1bSAndreas Gohr        return $plugin_controller->isEnabled($this->base);
499cf2dcf1bSAndreas Gohr    }
500cf2dcf1bSAndreas Gohr
501160d3688SAndreas Gohr    /**
502160d3688SAndreas Gohr     * Has the download URL changed since the last download?
503160d3688SAndreas Gohr     *
504160d3688SAndreas Gohr     * @return bool
505160d3688SAndreas Gohr     */
506160d3688SAndreas Gohr    public function hasChangedURL()
507160d3688SAndreas Gohr    {
5084fd6a1d7SAndreas Gohr        $last = $this->getManager()->getDownloadURL();
509160d3688SAndreas Gohr        if (!$last) return false;
510*2ee9c305Sfiwswe        $url = $this->getDownloadURL();
511*2ee9c305Sfiwswe        if (!$url) return false;
512*2ee9c305Sfiwswe        return $last !== $url;
513160d3688SAndreas Gohr    }
514160d3688SAndreas Gohr
515160d3688SAndreas Gohr    /**
516160d3688SAndreas Gohr     * Is an update available for this extension?
517160d3688SAndreas Gohr     *
518160d3688SAndreas Gohr     * @return bool
519160d3688SAndreas Gohr     */
5204fd6a1d7SAndreas Gohr    public function isUpdateAvailable()
521160d3688SAndreas Gohr    {
522160d3688SAndreas Gohr        if ($this->isBundled()) return false; // bundled extensions are never updated
523160d3688SAndreas Gohr        $self = $this->getInstalledVersion();
524160d3688SAndreas Gohr        $remote = $this->getLastUpdate();
525160d3688SAndreas Gohr        return $self < $remote;
526160d3688SAndreas Gohr    }
527160d3688SAndreas Gohr
528cf2dcf1bSAndreas Gohr    // endregion
529cf2dcf1bSAndreas Gohr
5307c9966a5SAndreas Gohr    // region Remote Info
5317c9966a5SAndreas Gohr
5327c9966a5SAndreas Gohr    /**
5337c9966a5SAndreas Gohr     * Get the date of the last available update
5347c9966a5SAndreas Gohr     *
5357c9966a5SAndreas Gohr     * @return string yyyy-mm-dd
5367c9966a5SAndreas Gohr     */
5377c9966a5SAndreas Gohr    public function getLastUpdate()
5387c9966a5SAndreas Gohr    {
5397c9966a5SAndreas Gohr        return $this->getRemoteTag('lastupdate');
5407c9966a5SAndreas Gohr    }
5417c9966a5SAndreas Gohr
5427c9966a5SAndreas Gohr    /**
5437c9966a5SAndreas Gohr     * Get a list of tags this extension is tagged with at dokuwiki.org
5447c9966a5SAndreas Gohr     *
5457c9966a5SAndreas Gohr     * @return string[]
5467c9966a5SAndreas Gohr     */
5477c9966a5SAndreas Gohr    public function getTags()
5487c9966a5SAndreas Gohr    {
5497c9966a5SAndreas Gohr        return $this->getRemoteTag('tags', []);
5507c9966a5SAndreas Gohr    }
5517c9966a5SAndreas Gohr
5527c9966a5SAndreas Gohr    /**
5537c9966a5SAndreas Gohr     * Get the popularity of the extension
5547c9966a5SAndreas Gohr     *
5557c9966a5SAndreas Gohr     * This is a float between 0 and 1
5567c9966a5SAndreas Gohr     *
5577c9966a5SAndreas Gohr     * @return float
5587c9966a5SAndreas Gohr     */
5597c9966a5SAndreas Gohr    public function getPopularity()
5607c9966a5SAndreas Gohr    {
5617c9966a5SAndreas Gohr        return (float)$this->getRemoteTag('popularity', 0);
5627c9966a5SAndreas Gohr    }
5637c9966a5SAndreas Gohr
5647c9966a5SAndreas Gohr    /**
5657c9966a5SAndreas Gohr     * Get the text of the update message if there is any
5667c9966a5SAndreas Gohr     *
5677c9966a5SAndreas Gohr     * @return string
5687c9966a5SAndreas Gohr     */
5697c9966a5SAndreas Gohr    public function getUpdateMessage()
5707c9966a5SAndreas Gohr    {
5717c9966a5SAndreas Gohr        return $this->getRemoteTag('updatemessage');
5727c9966a5SAndreas Gohr    }
5737c9966a5SAndreas Gohr
5747c9966a5SAndreas Gohr    /**
5757c9966a5SAndreas Gohr     * Get the text of the security warning if there is any
5767c9966a5SAndreas Gohr     *
5777c9966a5SAndreas Gohr     * @return string
5787c9966a5SAndreas Gohr     */
5797c9966a5SAndreas Gohr    public function getSecurityWarning()
5807c9966a5SAndreas Gohr    {
5817c9966a5SAndreas Gohr        return $this->getRemoteTag('securitywarning');
5827c9966a5SAndreas Gohr    }
5837c9966a5SAndreas Gohr
5847c9966a5SAndreas Gohr    /**
5857c9966a5SAndreas Gohr     * Get the text of the security issue if there is any
5867c9966a5SAndreas Gohr     *
5877c9966a5SAndreas Gohr     * @return string
5887c9966a5SAndreas Gohr     */
5897c9966a5SAndreas Gohr    public function getSecurityIssue()
5907c9966a5SAndreas Gohr    {
5917c9966a5SAndreas Gohr        return $this->getRemoteTag('securityissue');
5927c9966a5SAndreas Gohr    }
5937c9966a5SAndreas Gohr
5947c9966a5SAndreas Gohr    /**
5957c9966a5SAndreas Gohr     * Get the URL of the screenshot of the extension if there is any
5967c9966a5SAndreas Gohr     *
5977c9966a5SAndreas Gohr     * @return string
5987c9966a5SAndreas Gohr     */
5997c9966a5SAndreas Gohr    public function getScreenshotURL()
6007c9966a5SAndreas Gohr    {
6017c9966a5SAndreas Gohr        return $this->getRemoteTag('screenshoturl');
6027c9966a5SAndreas Gohr    }
6037c9966a5SAndreas Gohr
6047c9966a5SAndreas Gohr    /**
6057c9966a5SAndreas Gohr     * Get the URL of the thumbnail of the extension if there is any
6067c9966a5SAndreas Gohr     *
6077c9966a5SAndreas Gohr     * @return string
6087c9966a5SAndreas Gohr     */
6097c9966a5SAndreas Gohr    public function getThumbnailURL()
6107c9966a5SAndreas Gohr    {
6117c9966a5SAndreas Gohr        return $this->getRemoteTag('thumbnailurl');
6127c9966a5SAndreas Gohr    }
6137c9966a5SAndreas Gohr
6147c9966a5SAndreas Gohr    /**
6157c9966a5SAndreas Gohr     * Get the download URL of the extension if there is any
6167c9966a5SAndreas Gohr     *
6177c9966a5SAndreas Gohr     * @return string
6187c9966a5SAndreas Gohr     */
6197c9966a5SAndreas Gohr    public function getDownloadURL()
6207c9966a5SAndreas Gohr    {
6217c9966a5SAndreas Gohr        return $this->getRemoteTag('downloadurl');
6227c9966a5SAndreas Gohr    }
6237c9966a5SAndreas Gohr
6247c9966a5SAndreas Gohr    /**
6257c9966a5SAndreas Gohr     * Get the bug tracker URL of the extension if there is any
6267c9966a5SAndreas Gohr     *
6277c9966a5SAndreas Gohr     * @return string
6287c9966a5SAndreas Gohr     */
6297c9966a5SAndreas Gohr    public function getBugtrackerURL()
6307c9966a5SAndreas Gohr    {
6317c9966a5SAndreas Gohr        return $this->getRemoteTag('bugtracker');
6327c9966a5SAndreas Gohr    }
6337c9966a5SAndreas Gohr
6347c9966a5SAndreas Gohr    /**
6357c9966a5SAndreas Gohr     * Get the URL of the source repository if there is any
6367c9966a5SAndreas Gohr     *
6377c9966a5SAndreas Gohr     * @return string
6387c9966a5SAndreas Gohr     */
6397c9966a5SAndreas Gohr    public function getSourcerepoURL()
6407c9966a5SAndreas Gohr    {
6417c9966a5SAndreas Gohr        return $this->getRemoteTag('sourcerepo');
6427c9966a5SAndreas Gohr    }
6437c9966a5SAndreas Gohr
6447c9966a5SAndreas Gohr    /**
6457c9966a5SAndreas Gohr     * Get the donation URL of the extension if there is any
6467c9966a5SAndreas Gohr     *
6477c9966a5SAndreas Gohr     * @return string
6487c9966a5SAndreas Gohr     */
6497c9966a5SAndreas Gohr    public function getDonationURL()
6507c9966a5SAndreas Gohr    {
6517c9966a5SAndreas Gohr        return $this->getRemoteTag('donationurl');
6527c9966a5SAndreas Gohr    }
6537c9966a5SAndreas Gohr
6544fd6a1d7SAndreas Gohr    /**
6554fd6a1d7SAndreas Gohr     * Get a list of extensions that are similar to this one
6564fd6a1d7SAndreas Gohr     *
6574fd6a1d7SAndreas Gohr     * @return string[]
6584fd6a1d7SAndreas Gohr     */
6594fd6a1d7SAndreas Gohr    public function getSimilarList()
6604fd6a1d7SAndreas Gohr    {
6614fd6a1d7SAndreas Gohr        return $this->getRemoteTag('similar', []);
6624fd6a1d7SAndreas Gohr    }
6634fd6a1d7SAndreas Gohr
6644fd6a1d7SAndreas Gohr    /**
6654fd6a1d7SAndreas Gohr     * Get a list of extensions that are marked as conflicting with this one
6664fd6a1d7SAndreas Gohr     *
6674fd6a1d7SAndreas Gohr     * @return string[]
6684fd6a1d7SAndreas Gohr     */
6694fd6a1d7SAndreas Gohr    public function getConflictList()
6704fd6a1d7SAndreas Gohr    {
6714fd6a1d7SAndreas Gohr        return $this->getRemoteTag('conflicts', []);
6724fd6a1d7SAndreas Gohr    }
6734fd6a1d7SAndreas Gohr
6744fd6a1d7SAndreas Gohr    /**
6754fd6a1d7SAndreas Gohr     * Get a list of DokuWiki versions this plugin is marked as compatible with
6764fd6a1d7SAndreas Gohr     *
6774fd6a1d7SAndreas Gohr     * @return string[][] date -> version
6784fd6a1d7SAndreas Gohr     */
6794fd6a1d7SAndreas Gohr    public function getCompatibleVersions()
6804fd6a1d7SAndreas Gohr    {
6814fd6a1d7SAndreas Gohr        return $this->getRemoteTag('compatible', []);
6824fd6a1d7SAndreas Gohr    }
6834fd6a1d7SAndreas Gohr
6847c9966a5SAndreas Gohr    // endregion
6857c9966a5SAndreas Gohr
686cf2dcf1bSAndreas Gohr    // region Actions
687cf2dcf1bSAndreas Gohr
688cf2dcf1bSAndreas Gohr    /**
689cf2dcf1bSAndreas Gohr     * Install or update the extension
690cf2dcf1bSAndreas Gohr     *
691cf2dcf1bSAndreas Gohr     * @throws Exception
692cf2dcf1bSAndreas Gohr     */
693cf2dcf1bSAndreas Gohr    public function installOrUpdate()
694cf2dcf1bSAndreas Gohr    {
695cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
696160d3688SAndreas Gohr        $installer->installExtension($this);
697cf2dcf1bSAndreas Gohr    }
698cf2dcf1bSAndreas Gohr
699cf2dcf1bSAndreas Gohr    /**
700cf2dcf1bSAndreas Gohr     * Uninstall the extension
701cf2dcf1bSAndreas Gohr     * @throws Exception
702cf2dcf1bSAndreas Gohr     */
703cf2dcf1bSAndreas Gohr    public function uninstall()
704cf2dcf1bSAndreas Gohr    {
705cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
706cf2dcf1bSAndreas Gohr        $installer->uninstall($this);
707cf2dcf1bSAndreas Gohr    }
708cf2dcf1bSAndreas Gohr
709cf2dcf1bSAndreas Gohr    /**
7105732c960SAndreas Gohr     * Toggle the extension between enabled and disabled
7115732c960SAndreas Gohr     * @return void
7125732c960SAndreas Gohr     * @throws Exception
7135732c960SAndreas Gohr     */
7145732c960SAndreas Gohr    public function toggle()
7155732c960SAndreas Gohr    {
7165732c960SAndreas Gohr        if ($this->isEnabled()) {
7175732c960SAndreas Gohr            $this->disable();
7185732c960SAndreas Gohr        } else {
7195732c960SAndreas Gohr            $this->enable();
7205732c960SAndreas Gohr        }
7215732c960SAndreas Gohr    }
7225732c960SAndreas Gohr
7235732c960SAndreas Gohr    /**
724cf2dcf1bSAndreas Gohr     * Enable the extension
725b69d74f1SAndreas Gohr     *
726cf2dcf1bSAndreas Gohr     * @throws Exception
727cf2dcf1bSAndreas Gohr     */
728cf2dcf1bSAndreas Gohr    public function enable()
729cf2dcf1bSAndreas Gohr    {
730b69d74f1SAndreas Gohr        (new Installer())->enable($this);
731cf2dcf1bSAndreas Gohr    }
732cf2dcf1bSAndreas Gohr
733cf2dcf1bSAndreas Gohr    /**
734cf2dcf1bSAndreas Gohr     * Disable the extension
735b69d74f1SAndreas Gohr     *
736cf2dcf1bSAndreas Gohr     * @throws Exception
737cf2dcf1bSAndreas Gohr     */
738cf2dcf1bSAndreas Gohr    public function disable()
739cf2dcf1bSAndreas Gohr    {
740b69d74f1SAndreas Gohr        (new Installer())->disable($this);
741cf2dcf1bSAndreas Gohr    }
742cf2dcf1bSAndreas Gohr
743cf2dcf1bSAndreas Gohr    // endregion
744cf2dcf1bSAndreas Gohr
745cf2dcf1bSAndreas Gohr    // region Meta Data Management
746cf2dcf1bSAndreas Gohr
747cf2dcf1bSAndreas Gohr    /**
7487c9966a5SAndreas Gohr     * Access the Manager for this extension
749cf2dcf1bSAndreas Gohr     *
7507c9966a5SAndreas Gohr     * @return Manager
751cf2dcf1bSAndreas Gohr     */
7527c9966a5SAndreas Gohr    public function getManager()
753cf2dcf1bSAndreas Gohr    {
7547c184cfcSAndreas Gohr        if (!$this->manager instanceof Manager) {
7557c9966a5SAndreas Gohr            $this->manager = new Manager($this);
756cf2dcf1bSAndreas Gohr        }
7577c9966a5SAndreas Gohr        return $this->manager;
758cf2dcf1bSAndreas Gohr    }
759cf2dcf1bSAndreas Gohr
760cf2dcf1bSAndreas Gohr    /**
761cf2dcf1bSAndreas Gohr     * Reads the info file of the extension if available and fills the localInfo array
762cf2dcf1bSAndreas Gohr     */
763cf2dcf1bSAndreas Gohr    protected function readLocalInfo()
764cf2dcf1bSAndreas Gohr    {
765a1e045f7SAndreas Gohr        if (!$this->getCurrentDir()) return;
766cf2dcf1bSAndreas Gohr        $file = $this->currentDir . '/' . $this->type . '.info.txt';
767cf2dcf1bSAndreas Gohr        if (!is_readable($file)) return;
768cf2dcf1bSAndreas Gohr        $this->localInfo = confToHash($file, true);
769cf2dcf1bSAndreas Gohr        $this->localInfo = array_filter($this->localInfo); // remove all falsy keys
770cf2dcf1bSAndreas Gohr    }
771cf2dcf1bSAndreas Gohr
772cf2dcf1bSAndreas Gohr    /**
773cf2dcf1bSAndreas Gohr     * Fetches the remote info from the repository
774cf2dcf1bSAndreas Gohr     *
775cf2dcf1bSAndreas Gohr     * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case
776cf2dcf1bSAndreas Gohr     */
777cf2dcf1bSAndreas Gohr    protected function loadRemoteInfo()
778cf2dcf1bSAndreas Gohr    {
779cf2dcf1bSAndreas Gohr        if ($this->remoteInfo) return;
780cf2dcf1bSAndreas Gohr        $remote = Repository::getInstance();
781cf2dcf1bSAndreas Gohr        try {
782cf2dcf1bSAndreas Gohr            $this->remoteInfo = (array)$remote->getExtensionData($this->getId());
783cf2dcf1bSAndreas Gohr        } catch (Exception $e) {
784cf2dcf1bSAndreas Gohr            $this->remoteInfo = [];
785cf2dcf1bSAndreas Gohr        }
786cf2dcf1bSAndreas Gohr    }
787cf2dcf1bSAndreas Gohr
788cf2dcf1bSAndreas Gohr    /**
789cf2dcf1bSAndreas Gohr     * Read information from either local or remote info
790cf2dcf1bSAndreas Gohr     *
7917c9966a5SAndreas Gohr     * Always prefers local info over remote info. Giving multiple keys is useful when the
7927c9966a5SAndreas Gohr     * key has been renamed in the past or if local and remote keys might differ.
793cf2dcf1bSAndreas Gohr     *
794cf2dcf1bSAndreas Gohr     * @param string|string[] $tag one or multiple keys to check
795cf2dcf1bSAndreas Gohr     * @param mixed $default
796cf2dcf1bSAndreas Gohr     * @return mixed
797cf2dcf1bSAndreas Gohr     */
798cf2dcf1bSAndreas Gohr    protected function getTag($tag, $default = '')
799cf2dcf1bSAndreas Gohr    {
800cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
801cf2dcf1bSAndreas Gohr            if (isset($this->localInfo[$t])) return $this->localInfo[$t];
802cf2dcf1bSAndreas Gohr        }
8037c9966a5SAndreas Gohr
8047c9966a5SAndreas Gohr        return $this->getRemoteTag($tag, $default);
8057c9966a5SAndreas Gohr    }
8067c9966a5SAndreas Gohr
8077c9966a5SAndreas Gohr    /**
8087c9966a5SAndreas Gohr     * Read information from remote info
8097c9966a5SAndreas Gohr     *
8107c9966a5SAndreas Gohr     * @param string|string[] $tag one or mutiple keys to check
8117c9966a5SAndreas Gohr     * @param mixed $default
8127c9966a5SAndreas Gohr     * @return mixed
8137c9966a5SAndreas Gohr     */
8147c9966a5SAndreas Gohr    protected function getRemoteTag($tag, $default = '')
8157c9966a5SAndreas Gohr    {
816cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
817cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
818cf2dcf1bSAndreas Gohr            if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t];
819cf2dcf1bSAndreas Gohr        }
820cf2dcf1bSAndreas Gohr        return $default;
821cf2dcf1bSAndreas Gohr    }
822cf2dcf1bSAndreas Gohr
823cf2dcf1bSAndreas Gohr    // endregion
824a1e045f7SAndreas Gohr
825a1e045f7SAndreas Gohr    // region utilities
826a1e045f7SAndreas Gohr
827a1e045f7SAndreas Gohr    /**
828a1e045f7SAndreas Gohr     * Convert an extension id to a type and base
829a1e045f7SAndreas Gohr     *
830a1e045f7SAndreas Gohr     * @param string $id
831a1e045f7SAndreas Gohr     * @return array [type, base]
832a1e045f7SAndreas Gohr     */
833a1e045f7SAndreas Gohr    protected function idToTypeBase($id)
834a1e045f7SAndreas Gohr    {
835a1e045f7SAndreas Gohr        [$type, $base] = sexplode(':', $id, 2);
836a1e045f7SAndreas Gohr        if ($base === null) {
837a1e045f7SAndreas Gohr            $base = $type;
838a1e045f7SAndreas Gohr            $type = self::TYPE_PLUGIN;
839a1e045f7SAndreas Gohr        } elseif ($type === self::TYPE_TEMPLATE) {
840a1e045f7SAndreas Gohr            $type = self::TYPE_TEMPLATE;
841a1e045f7SAndreas Gohr        } else {
842a1e045f7SAndreas Gohr            throw new RuntimeException('Invalid extension id: ' . $id);
843a1e045f7SAndreas Gohr        }
844a1e045f7SAndreas Gohr
845a1e045f7SAndreas Gohr        return [$type, $base];
846a1e045f7SAndreas Gohr    }
847b69d74f1SAndreas Gohr
8487c9966a5SAndreas Gohr    /**
8497c9966a5SAndreas Gohr     * @return string
8507c9966a5SAndreas Gohr     */
8517c9966a5SAndreas Gohr    public function __toString()
8527c9966a5SAndreas Gohr    {
8537c9966a5SAndreas Gohr        return $this->getId();
8547c9966a5SAndreas Gohr    }
8557c9966a5SAndreas Gohr
856a1e045f7SAndreas Gohr    // endregion
857cf2dcf1bSAndreas Gohr}
858