xref: /dokuwiki/lib/plugins/extension/Extension.php (revision b69d74f1e111c77de08eb7f64e7f77368a537e44)
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
14cf2dcf1bSAndreas Gohr    /** @var string "plugin"|"template" */
15cf2dcf1bSAndreas Gohr    protected string $type = self::TYPE_PLUGIN;
16cf2dcf1bSAndreas Gohr
17cf2dcf1bSAndreas Gohr    /** @var string The base name of this extension */
18cf2dcf1bSAndreas Gohr    protected string $base;
19cf2dcf1bSAndreas Gohr
2025d28a01SAndreas Gohr    /** @var string The current location of this extension */
2125d28a01SAndreas Gohr    protected string $currentDir = '';
22cf2dcf1bSAndreas Gohr
23cf2dcf1bSAndreas Gohr    /** @var array The local info array of the extension */
24cf2dcf1bSAndreas Gohr    protected array $localInfo = [];
25cf2dcf1bSAndreas Gohr
26cf2dcf1bSAndreas Gohr    /** @var array The remote info array of the extension */
27cf2dcf1bSAndreas Gohr    protected array $remoteInfo = [];
28cf2dcf1bSAndreas Gohr
297c9966a5SAndreas Gohr    /** @var Manager|null The manager for this extension */
3025d28a01SAndreas Gohr    protected ?Manager $manager = null;
31cf2dcf1bSAndreas Gohr
32cf2dcf1bSAndreas Gohr    // region Constructors
33cf2dcf1bSAndreas Gohr
34cf2dcf1bSAndreas Gohr    /**
35cf2dcf1bSAndreas Gohr     * The main constructor is private to force the use of the factory methods
36cf2dcf1bSAndreas Gohr     */
37cf2dcf1bSAndreas Gohr    protected function __construct()
38cf2dcf1bSAndreas Gohr    {
39cf2dcf1bSAndreas Gohr    }
40cf2dcf1bSAndreas Gohr
41cf2dcf1bSAndreas Gohr    /**
42a1e045f7SAndreas Gohr     * Initializes an extension from an id
43a1e045f7SAndreas Gohr     *
44a1e045f7SAndreas Gohr     * @param string $id The id of the extension
45a1e045f7SAndreas Gohr     * @return Extension
46a1e045f7SAndreas Gohr     */
47a1e045f7SAndreas Gohr    public static function createFromId($id)
48a1e045f7SAndreas Gohr    {
49a1e045f7SAndreas Gohr        $extension = new self();
50a1e045f7SAndreas Gohr        $extension->initFromId($id);
51a1e045f7SAndreas Gohr        return $extension;
52a1e045f7SAndreas Gohr    }
53a1e045f7SAndreas Gohr
54a1e045f7SAndreas Gohr    protected function initFromId($id)
55a1e045f7SAndreas Gohr    {
56a1e045f7SAndreas Gohr        [$type, $base] = $this->idToTypeBase($id);
57a1e045f7SAndreas Gohr        $this->type = $type;
58a1e045f7SAndreas Gohr        $this->base = $base;
59a1e045f7SAndreas Gohr        $this->readLocalInfo();
60a1e045f7SAndreas Gohr    }
61a1e045f7SAndreas Gohr
62a1e045f7SAndreas Gohr    /**
63cf2dcf1bSAndreas Gohr     * Initializes an extension from a directory
64cf2dcf1bSAndreas Gohr     *
65cf2dcf1bSAndreas Gohr     * The given directory might be the one where the extension has already been installed to
66cf2dcf1bSAndreas Gohr     * or it might be the extracted source in some temporary directory.
67cf2dcf1bSAndreas Gohr     *
68cf2dcf1bSAndreas Gohr     * @param string $dir Where the extension code is currently located
69cf2dcf1bSAndreas Gohr     * @param string|null $type TYPE_PLUGIN|TYPE_TEMPLATE, null for auto-detection
70cf2dcf1bSAndreas Gohr     * @param string $base The base name of the extension, null for auto-detection
71cf2dcf1bSAndreas Gohr     * @return Extension
72cf2dcf1bSAndreas Gohr     */
73cf2dcf1bSAndreas Gohr    public static function createFromDirectory($dir, $type = null, $base = null)
74cf2dcf1bSAndreas Gohr    {
75cf2dcf1bSAndreas Gohr        $extension = new self();
76cf2dcf1bSAndreas Gohr        $extension->initFromDirectory($dir, $type, $base);
77cf2dcf1bSAndreas Gohr        return $extension;
78cf2dcf1bSAndreas Gohr    }
79cf2dcf1bSAndreas Gohr
80cf2dcf1bSAndreas Gohr    protected function initFromDirectory($dir, $type = null, $base = null)
81cf2dcf1bSAndreas Gohr    {
82cf2dcf1bSAndreas Gohr        if (!is_dir($dir)) throw new RuntimeException('Directory not found: ' . $dir);
83e8fd67e9SAndreas Gohr        $this->currentDir = fullpath($dir);
84cf2dcf1bSAndreas Gohr
85cf2dcf1bSAndreas Gohr        if ($type === null || $type === self::TYPE_TEMPLATE) {
86cf2dcf1bSAndreas Gohr            if (
878fe483c9SAndreas Gohr                file_exists($dir . '/template.info.txt') ||
88cf2dcf1bSAndreas Gohr                file_exists($dir . '/style.ini') ||
89cf2dcf1bSAndreas Gohr                file_exists($dir . '/main.php') ||
90cf2dcf1bSAndreas Gohr                file_exists($dir . '/detail.php') ||
91cf2dcf1bSAndreas Gohr                file_exists($dir . '/mediamanager.php')
92cf2dcf1bSAndreas Gohr            ) {
93cf2dcf1bSAndreas Gohr                $this->type = self::TYPE_TEMPLATE;
94cf2dcf1bSAndreas Gohr            }
95cf2dcf1bSAndreas Gohr        } else {
96cf2dcf1bSAndreas Gohr            $this->type = self::TYPE_PLUGIN;
97cf2dcf1bSAndreas Gohr        }
98cf2dcf1bSAndreas Gohr
99cf2dcf1bSAndreas Gohr        $this->readLocalInfo();
100cf2dcf1bSAndreas Gohr
101cf2dcf1bSAndreas Gohr        if ($base !== null) {
102cf2dcf1bSAndreas Gohr            $this->base = $base;
103cf2dcf1bSAndreas Gohr        } elseif (isset($this->localInfo['base'])) {
104cf2dcf1bSAndreas Gohr            $this->base = $this->localInfo['base'];
105cf2dcf1bSAndreas Gohr        } else {
106cf2dcf1bSAndreas Gohr            $this->base = basename($dir);
107cf2dcf1bSAndreas Gohr        }
108cf2dcf1bSAndreas Gohr    }
109cf2dcf1bSAndreas Gohr
110cf2dcf1bSAndreas Gohr    /**
111cf2dcf1bSAndreas Gohr     * Initializes an extension from remote data
112cf2dcf1bSAndreas Gohr     *
113cf2dcf1bSAndreas Gohr     * @param array $data The data as returned by the repository api
114cf2dcf1bSAndreas Gohr     * @return Extension
115cf2dcf1bSAndreas Gohr     */
116cf2dcf1bSAndreas Gohr    public static function createFromRemoteData($data)
117cf2dcf1bSAndreas Gohr    {
118cf2dcf1bSAndreas Gohr        $extension = new self();
119cf2dcf1bSAndreas Gohr        $extension->initFromRemoteData($data);
120cf2dcf1bSAndreas Gohr        return $extension;
121cf2dcf1bSAndreas Gohr    }
122cf2dcf1bSAndreas Gohr
123cf2dcf1bSAndreas Gohr    protected function initFromRemoteData($data)
124cf2dcf1bSAndreas Gohr    {
125cf2dcf1bSAndreas Gohr        if (!isset($data['plugin'])) throw new RuntimeException('Invalid remote data');
126cf2dcf1bSAndreas Gohr
127a1e045f7SAndreas Gohr        [$type, $base] = $this->idToTypeBase($data['plugin']);
128cf2dcf1bSAndreas Gohr        $this->remoteInfo = $data;
129cf2dcf1bSAndreas Gohr        $this->type = $type;
130cf2dcf1bSAndreas Gohr        $this->base = $base;
131cf2dcf1bSAndreas Gohr
132cf2dcf1bSAndreas Gohr        if ($this->isInstalled()) {
133cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
134cf2dcf1bSAndreas Gohr            $this->readLocalInfo();
135cf2dcf1bSAndreas Gohr        }
136cf2dcf1bSAndreas Gohr    }
137cf2dcf1bSAndreas Gohr
138cf2dcf1bSAndreas Gohr    // endregion
139cf2dcf1bSAndreas Gohr
140cf2dcf1bSAndreas Gohr    // region Getters
141cf2dcf1bSAndreas Gohr
142cf2dcf1bSAndreas Gohr    /**
1434fd6a1d7SAndreas Gohr     * @param bool $wrap If true, the id is wrapped in backticks
144cf2dcf1bSAndreas Gohr     * @return string The extension id (same as base but prefixed with "template:" for templates)
145cf2dcf1bSAndreas Gohr     */
1464fd6a1d7SAndreas Gohr    public function getId($wrap = false)
147cf2dcf1bSAndreas Gohr    {
148cf2dcf1bSAndreas Gohr        if ($this->type === self::TYPE_TEMPLATE) {
1494fd6a1d7SAndreas Gohr            $id = self::TYPE_TEMPLATE . ':' . $this->base;
1504fd6a1d7SAndreas Gohr        } else {
1514fd6a1d7SAndreas Gohr            $id = $this->base;
152cf2dcf1bSAndreas Gohr        }
1534fd6a1d7SAndreas Gohr        if ($wrap) $id = "`$id`";
1544fd6a1d7SAndreas Gohr        return $id;
155cf2dcf1bSAndreas Gohr    }
156cf2dcf1bSAndreas Gohr
157cf2dcf1bSAndreas Gohr    /**
158cf2dcf1bSAndreas Gohr     * Get the base name of this extension
159cf2dcf1bSAndreas Gohr     *
160cf2dcf1bSAndreas Gohr     * @return string
161cf2dcf1bSAndreas Gohr     */
162cf2dcf1bSAndreas Gohr    public function getBase()
163cf2dcf1bSAndreas Gohr    {
164cf2dcf1bSAndreas Gohr        return $this->base;
165cf2dcf1bSAndreas Gohr    }
166cf2dcf1bSAndreas Gohr
167cf2dcf1bSAndreas Gohr    /**
168cf2dcf1bSAndreas Gohr     * Get the type of the extension
169cf2dcf1bSAndreas Gohr     *
170cf2dcf1bSAndreas Gohr     * @return string "plugin"|"template"
171cf2dcf1bSAndreas Gohr     */
172cf2dcf1bSAndreas Gohr    public function getType()
173cf2dcf1bSAndreas Gohr    {
174cf2dcf1bSAndreas Gohr        return $this->type;
175cf2dcf1bSAndreas Gohr    }
176cf2dcf1bSAndreas Gohr
177cf2dcf1bSAndreas Gohr    /**
178cf2dcf1bSAndreas Gohr     * The current directory of the extension
179cf2dcf1bSAndreas Gohr     *
180cf2dcf1bSAndreas Gohr     * @return string|null
181cf2dcf1bSAndreas Gohr     */
182cf2dcf1bSAndreas Gohr    public function getCurrentDir()
183cf2dcf1bSAndreas Gohr    {
184cf2dcf1bSAndreas Gohr        // recheck that the current currentDir is still valid
185cf2dcf1bSAndreas Gohr        if ($this->currentDir && !is_dir($this->currentDir)) {
18625d28a01SAndreas Gohr            $this->currentDir = '';
187cf2dcf1bSAndreas Gohr        }
188cf2dcf1bSAndreas Gohr
189cf2dcf1bSAndreas Gohr        // if the extension is installed, then the currentDir is the install dir!
190cf2dcf1bSAndreas Gohr        if (!$this->currentDir && $this->isInstalled()) {
191cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
192cf2dcf1bSAndreas Gohr        }
193cf2dcf1bSAndreas Gohr
194cf2dcf1bSAndreas Gohr        return $this->currentDir;
195cf2dcf1bSAndreas Gohr    }
196cf2dcf1bSAndreas Gohr
197cf2dcf1bSAndreas Gohr    /**
198cf2dcf1bSAndreas Gohr     * Get the directory where this extension should be installed in
199cf2dcf1bSAndreas Gohr     *
200cf2dcf1bSAndreas Gohr     * Note: this does not mean that the extension is actually installed there
201cf2dcf1bSAndreas Gohr     *
202cf2dcf1bSAndreas Gohr     * @return string
203cf2dcf1bSAndreas Gohr     */
204cf2dcf1bSAndreas Gohr    public function getInstallDir()
205cf2dcf1bSAndreas Gohr    {
206cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
207176901c2SAndreas Gohr            $dir = dirname(tpl_incdir()) . '/' . $this->base;
208cf2dcf1bSAndreas Gohr        } else {
209cf2dcf1bSAndreas Gohr            $dir = DOKU_PLUGIN . $this->base;
210cf2dcf1bSAndreas Gohr        }
211cf2dcf1bSAndreas Gohr
21225d28a01SAndreas Gohr        return fullpath($dir);
213cf2dcf1bSAndreas Gohr    }
214cf2dcf1bSAndreas Gohr
215cf2dcf1bSAndreas Gohr
216cf2dcf1bSAndreas Gohr    /**
217cf2dcf1bSAndreas Gohr     * Get the display name of the extension
218cf2dcf1bSAndreas Gohr     *
219cf2dcf1bSAndreas Gohr     * @return string
220cf2dcf1bSAndreas Gohr     */
221cf2dcf1bSAndreas Gohr    public function getDisplayName()
222cf2dcf1bSAndreas Gohr    {
223cf2dcf1bSAndreas Gohr        return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType()));
224cf2dcf1bSAndreas Gohr    }
225cf2dcf1bSAndreas Gohr
226cf2dcf1bSAndreas Gohr    /**
227cf2dcf1bSAndreas Gohr     * Get the author name of the extension
228cf2dcf1bSAndreas Gohr     *
229cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the author info is missing
230cf2dcf1bSAndreas Gohr     */
231cf2dcf1bSAndreas Gohr    public function getAuthor()
232cf2dcf1bSAndreas Gohr    {
233cf2dcf1bSAndreas Gohr        return $this->getTag('author');
234cf2dcf1bSAndreas Gohr    }
235cf2dcf1bSAndreas Gohr
236cf2dcf1bSAndreas Gohr    /**
237cf2dcf1bSAndreas Gohr     * Get the email of the author of the extension if there is any
238cf2dcf1bSAndreas Gohr     *
239cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the email info is missing
240cf2dcf1bSAndreas Gohr     */
241cf2dcf1bSAndreas Gohr    public function getEmail()
242cf2dcf1bSAndreas Gohr    {
243cf2dcf1bSAndreas Gohr        // email is only in the local data
244cf2dcf1bSAndreas Gohr        return $this->localInfo['email'] ?? '';
245cf2dcf1bSAndreas Gohr    }
246cf2dcf1bSAndreas Gohr
247cf2dcf1bSAndreas Gohr    /**
248cf2dcf1bSAndreas Gohr     * Get the email id, i.e. the md5sum of the email
249cf2dcf1bSAndreas Gohr     *
250cf2dcf1bSAndreas Gohr     * @return string Empty string if no email is available
251cf2dcf1bSAndreas Gohr     */
252cf2dcf1bSAndreas Gohr    public function getEmailID()
253cf2dcf1bSAndreas Gohr    {
254cf2dcf1bSAndreas Gohr        if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid'];
255cf2dcf1bSAndreas Gohr        if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']);
256cf2dcf1bSAndreas Gohr        return '';
257cf2dcf1bSAndreas Gohr    }
258cf2dcf1bSAndreas Gohr
259cf2dcf1bSAndreas Gohr    /**
260cf2dcf1bSAndreas Gohr     * Get the description of the extension
261cf2dcf1bSAndreas Gohr     *
262cf2dcf1bSAndreas Gohr     * @return string Empty string if no description is available
263cf2dcf1bSAndreas Gohr     */
264cf2dcf1bSAndreas Gohr    public function getDescription()
265cf2dcf1bSAndreas Gohr    {
266cf2dcf1bSAndreas Gohr        return $this->getTag(['desc', 'description']);
267cf2dcf1bSAndreas Gohr    }
268cf2dcf1bSAndreas Gohr
269cf2dcf1bSAndreas Gohr    /**
270cf2dcf1bSAndreas Gohr     * Get the URL of the extension, usually a page on dokuwiki.org
271cf2dcf1bSAndreas Gohr     *
272cf2dcf1bSAndreas Gohr     * @return string
273cf2dcf1bSAndreas Gohr     */
274cf2dcf1bSAndreas Gohr    public function getURL()
275cf2dcf1bSAndreas Gohr    {
276cf2dcf1bSAndreas Gohr        return $this->getTag(
277cf2dcf1bSAndreas Gohr            'url',
278cf2dcf1bSAndreas Gohr            'https://www.dokuwiki.org/' .
279cf2dcf1bSAndreas Gohr            ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase()
280cf2dcf1bSAndreas Gohr        );
281cf2dcf1bSAndreas Gohr    }
282cf2dcf1bSAndreas Gohr
283cf2dcf1bSAndreas Gohr    /**
2847c9966a5SAndreas Gohr     * Get the version of the extension that is actually installed
2857c9966a5SAndreas Gohr     *
2867c9966a5SAndreas Gohr     * Returns an empty string if the version is not available
2877c9966a5SAndreas Gohr     *
2887c9966a5SAndreas Gohr     * @return string
2897c9966a5SAndreas Gohr     */
2907c9966a5SAndreas Gohr    public function getInstalledVersion()
2917c9966a5SAndreas Gohr    {
2927c9966a5SAndreas Gohr        return $this->localInfo['date'] ?? '';
2937c9966a5SAndreas Gohr    }
2947c9966a5SAndreas Gohr
2957c9966a5SAndreas Gohr    /**
2964fd6a1d7SAndreas Gohr     * Get the types of components this extension provides
2974fd6a1d7SAndreas Gohr     *
2984fd6a1d7SAndreas Gohr     * @return array int -> type
2994fd6a1d7SAndreas Gohr     */
3004fd6a1d7SAndreas Gohr    public function getComponentTypes()
3014fd6a1d7SAndreas Gohr    {
3028fe483c9SAndreas Gohr        // for installed extensions we can check the files
3038fe483c9SAndreas Gohr        if ($this->isInstalled()) {
3048fe483c9SAndreas Gohr            if ($this->isTemplate()) {
3058fe483c9SAndreas Gohr                return ['Template'];
3068fe483c9SAndreas Gohr            } else {
3078fe483c9SAndreas Gohr                $types = [];
3088fe483c9SAndreas Gohr                foreach (['Admin', 'Action', 'Syntax', 'Renderer', 'Helper', 'CLI'] as $type) {
3098fe483c9SAndreas Gohr                    $check = strtolower($type);
3108fe483c9SAndreas Gohr                    if (
3118fe483c9SAndreas Gohr                        file_exists($this->getInstallDir() . '/' . $check . '.php') ||
3128fe483c9SAndreas Gohr                        is_dir($this->getInstallDir() . '/' . $check)
3138fe483c9SAndreas Gohr                    ) {
3148fe483c9SAndreas Gohr                        $types[] = $type;
3158fe483c9SAndreas Gohr                    }
3168fe483c9SAndreas Gohr                }
3178fe483c9SAndreas Gohr                return $types;
3188fe483c9SAndreas Gohr            }
3198fe483c9SAndreas Gohr        }
3208fe483c9SAndreas Gohr        // still, here? use the remote info
3214fd6a1d7SAndreas Gohr        return $this->getTag('types', []);
3224fd6a1d7SAndreas Gohr    }
3234fd6a1d7SAndreas Gohr
3244fd6a1d7SAndreas Gohr    /**
32525d28a01SAndreas Gohr     * Get a list of extension ids this extension depends on
32625d28a01SAndreas Gohr     *
32725d28a01SAndreas Gohr     * @return string[]
32825d28a01SAndreas Gohr     */
32925d28a01SAndreas Gohr    public function getDependencyList()
33025d28a01SAndreas Gohr    {
33125d28a01SAndreas Gohr        return $this->getTag('depends', []);
33225d28a01SAndreas Gohr    }
33325d28a01SAndreas Gohr
33425d28a01SAndreas Gohr    /**
335*b69d74f1SAndreas Gohr     * Get a list of extensions that are currently installed, enabled and depend on this extension
336*b69d74f1SAndreas Gohr     *
337*b69d74f1SAndreas Gohr     * @return Extension[]
338*b69d74f1SAndreas Gohr     */
339*b69d74f1SAndreas Gohr    public function getDependants()
340*b69d74f1SAndreas Gohr    {
341*b69d74f1SAndreas Gohr        $local = new Local();
342*b69d74f1SAndreas Gohr        $extensions = $local->getExtensions();
343*b69d74f1SAndreas Gohr        $dependants = [];
344*b69d74f1SAndreas Gohr        foreach ($extensions as $extension) {
345*b69d74f1SAndreas Gohr            if (
346*b69d74f1SAndreas Gohr                in_array($this->getId(), $extension->getDependencyList()) &&
347*b69d74f1SAndreas Gohr                $extension->isEnabled()
348*b69d74f1SAndreas Gohr            ) {
349*b69d74f1SAndreas Gohr                $dependants[$extension->getId()] = $extension;
350*b69d74f1SAndreas Gohr            }
351*b69d74f1SAndreas Gohr        }
352*b69d74f1SAndreas Gohr        return $dependants;
353*b69d74f1SAndreas Gohr    }
354*b69d74f1SAndreas Gohr
355*b69d74f1SAndreas Gohr    /**
356b2a05b76SAndreas Gohr     * Return the minimum PHP version required by the extension
357b2a05b76SAndreas Gohr     *
358b2a05b76SAndreas Gohr     * Empty if not set
359b2a05b76SAndreas Gohr     *
360b2a05b76SAndreas Gohr     * @return string
361b2a05b76SAndreas Gohr     */
362b2a05b76SAndreas Gohr    public function getMinimumPHPVersion()
363b2a05b76SAndreas Gohr    {
364b2a05b76SAndreas Gohr        return $this->getTag('phpmin', '');
365b2a05b76SAndreas Gohr    }
366b2a05b76SAndreas Gohr
367b2a05b76SAndreas Gohr    /**
368b2a05b76SAndreas Gohr     * Return the minimum PHP version supported by the extension
369b2a05b76SAndreas Gohr     *
370b2a05b76SAndreas Gohr     * @return string
371b2a05b76SAndreas Gohr     */
372b2a05b76SAndreas Gohr    public function getMaximumPHPVersion()
373b2a05b76SAndreas Gohr    {
374b2a05b76SAndreas Gohr        return $this->getTag('phpmax', '');
375b2a05b76SAndreas Gohr    }
376b2a05b76SAndreas Gohr
377b2a05b76SAndreas Gohr    /**
378cf2dcf1bSAndreas Gohr     * Is this extension a template?
379cf2dcf1bSAndreas Gohr     *
380cf2dcf1bSAndreas Gohr     * @return bool false if it is a plugin
381cf2dcf1bSAndreas Gohr     */
382cf2dcf1bSAndreas Gohr    public function isTemplate()
383cf2dcf1bSAndreas Gohr    {
384cf2dcf1bSAndreas Gohr        return $this->type === self::TYPE_TEMPLATE;
385cf2dcf1bSAndreas Gohr    }
386cf2dcf1bSAndreas Gohr
387cf2dcf1bSAndreas Gohr    /**
388cf2dcf1bSAndreas Gohr     * Is the extension installed locally?
389cf2dcf1bSAndreas Gohr     *
390cf2dcf1bSAndreas Gohr     * @return bool
391cf2dcf1bSAndreas Gohr     */
392cf2dcf1bSAndreas Gohr    public function isInstalled()
393cf2dcf1bSAndreas Gohr    {
394cf2dcf1bSAndreas Gohr        return is_dir($this->getInstallDir());
395cf2dcf1bSAndreas Gohr    }
396cf2dcf1bSAndreas Gohr
397cf2dcf1bSAndreas Gohr    /**
398cf2dcf1bSAndreas Gohr     * Is the extension under git control?
399cf2dcf1bSAndreas Gohr     *
400cf2dcf1bSAndreas Gohr     * @return bool
401cf2dcf1bSAndreas Gohr     */
402cf2dcf1bSAndreas Gohr    public function isGitControlled()
403cf2dcf1bSAndreas Gohr    {
404cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) return false;
405cf2dcf1bSAndreas Gohr        return file_exists($this->getInstallDir() . '/.git');
406cf2dcf1bSAndreas Gohr    }
407cf2dcf1bSAndreas Gohr
408cf2dcf1bSAndreas Gohr    /**
409cf2dcf1bSAndreas Gohr     * If the extension is bundled
410cf2dcf1bSAndreas Gohr     *
411cf2dcf1bSAndreas Gohr     * @return bool If the extension is bundled
412cf2dcf1bSAndreas Gohr     */
413cf2dcf1bSAndreas Gohr    public function isBundled()
414cf2dcf1bSAndreas Gohr    {
415cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
416cf2dcf1bSAndreas Gohr        return $this->remoteInfo['bundled'] ?? in_array(
417cf2dcf1bSAndreas Gohr            $this->getId(),
418cf2dcf1bSAndreas Gohr            [
419cf2dcf1bSAndreas Gohr                'authad',
420cf2dcf1bSAndreas Gohr                'authldap',
421cf2dcf1bSAndreas Gohr                'authpdo',
422cf2dcf1bSAndreas Gohr                'authplain',
423cf2dcf1bSAndreas Gohr                'acl',
424cf2dcf1bSAndreas Gohr                'config',
425cf2dcf1bSAndreas Gohr                'extension',
426cf2dcf1bSAndreas Gohr                'info',
427cf2dcf1bSAndreas Gohr                'popularity',
428cf2dcf1bSAndreas Gohr                'revert',
429cf2dcf1bSAndreas Gohr                'safefnrecode',
430cf2dcf1bSAndreas Gohr                'styling',
431cf2dcf1bSAndreas Gohr                'testing',
432cf2dcf1bSAndreas Gohr                'usermanager',
433cf2dcf1bSAndreas Gohr                'logviewer',
434cf2dcf1bSAndreas Gohr                'template:dokuwiki'
435cf2dcf1bSAndreas Gohr            ]
436cf2dcf1bSAndreas Gohr        );
437cf2dcf1bSAndreas Gohr    }
438cf2dcf1bSAndreas Gohr
439cf2dcf1bSAndreas Gohr    /**
440cf2dcf1bSAndreas Gohr     * Is the extension protected against any modification (disable/uninstall)
441cf2dcf1bSAndreas Gohr     *
442cf2dcf1bSAndreas Gohr     * @return bool if the extension is protected
443cf2dcf1bSAndreas Gohr     */
444cf2dcf1bSAndreas Gohr    public function isProtected()
445cf2dcf1bSAndreas Gohr    {
446cf2dcf1bSAndreas Gohr        // never allow deinstalling the current auth plugin:
447cf2dcf1bSAndreas Gohr        global $conf;
448cf2dcf1bSAndreas Gohr        if ($this->getId() == $conf['authtype']) return true;
449cf2dcf1bSAndreas Gohr
450077f55feSAndreas Gohr        // disallow current template to be uninstalled
451077f55feSAndreas Gohr        if ($this->isTemplate() && ($this->getBase() === $conf['template'])) return true;
452cf2dcf1bSAndreas Gohr
453cf2dcf1bSAndreas Gohr        /** @var PluginController $plugin_controller */
454cf2dcf1bSAndreas Gohr        global $plugin_controller;
455cf2dcf1bSAndreas Gohr        $cascade = $plugin_controller->getCascade();
456cf2dcf1bSAndreas Gohr        return ($cascade['protected'][$this->getId()] ?? false);
457cf2dcf1bSAndreas Gohr    }
458cf2dcf1bSAndreas Gohr
459cf2dcf1bSAndreas Gohr    /**
460cf2dcf1bSAndreas Gohr     * Is the extension installed in the correct directory?
461cf2dcf1bSAndreas Gohr     *
462cf2dcf1bSAndreas Gohr     * @return bool
463cf2dcf1bSAndreas Gohr     */
464cf2dcf1bSAndreas Gohr    public function isInWrongFolder()
465cf2dcf1bSAndreas Gohr    {
4664fd6a1d7SAndreas Gohr        if (!$this->isInstalled()) return false;
467cf2dcf1bSAndreas Gohr        return $this->getInstallDir() != $this->currentDir;
468cf2dcf1bSAndreas Gohr    }
469cf2dcf1bSAndreas Gohr
470cf2dcf1bSAndreas Gohr    /**
471cf2dcf1bSAndreas Gohr     * Is the extension enabled?
472cf2dcf1bSAndreas Gohr     *
473cf2dcf1bSAndreas Gohr     * @return bool
474cf2dcf1bSAndreas Gohr     */
475cf2dcf1bSAndreas Gohr    public function isEnabled()
476cf2dcf1bSAndreas Gohr    {
477cf2dcf1bSAndreas Gohr        global $conf;
478cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
479cf2dcf1bSAndreas Gohr            return ($conf['template'] == $this->getBase());
480cf2dcf1bSAndreas Gohr        }
481cf2dcf1bSAndreas Gohr
482cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
483cf2dcf1bSAndreas Gohr        global $plugin_controller;
484cf2dcf1bSAndreas Gohr        return $plugin_controller->isEnabled($this->base);
485cf2dcf1bSAndreas Gohr    }
486cf2dcf1bSAndreas Gohr
487160d3688SAndreas Gohr    /**
488160d3688SAndreas Gohr     * Has the download URL changed since the last download?
489160d3688SAndreas Gohr     *
490160d3688SAndreas Gohr     * @return bool
491160d3688SAndreas Gohr     */
492160d3688SAndreas Gohr    public function hasChangedURL()
493160d3688SAndreas Gohr    {
4944fd6a1d7SAndreas Gohr        $last = $this->getManager()->getDownloadURL();
495160d3688SAndreas Gohr        if (!$last) return false;
496160d3688SAndreas Gohr        return $last !== $this->getDownloadURL();
497160d3688SAndreas Gohr    }
498160d3688SAndreas Gohr
499160d3688SAndreas Gohr    /**
500160d3688SAndreas Gohr     * Is an update available for this extension?
501160d3688SAndreas Gohr     *
502160d3688SAndreas Gohr     * @return bool
503160d3688SAndreas Gohr     */
5044fd6a1d7SAndreas Gohr    public function isUpdateAvailable()
505160d3688SAndreas Gohr    {
506160d3688SAndreas Gohr        if ($this->isBundled()) return false; // bundled extensions are never updated
507160d3688SAndreas Gohr        $self = $this->getInstalledVersion();
508160d3688SAndreas Gohr        $remote = $this->getLastUpdate();
509160d3688SAndreas Gohr        return $self < $remote;
510160d3688SAndreas Gohr    }
511160d3688SAndreas Gohr
512cf2dcf1bSAndreas Gohr    // endregion
513cf2dcf1bSAndreas Gohr
5147c9966a5SAndreas Gohr    // region Remote Info
5157c9966a5SAndreas Gohr
5167c9966a5SAndreas Gohr    /**
5177c9966a5SAndreas Gohr     * Get the date of the last available update
5187c9966a5SAndreas Gohr     *
5197c9966a5SAndreas Gohr     * @return string yyyy-mm-dd
5207c9966a5SAndreas Gohr     */
5217c9966a5SAndreas Gohr    public function getLastUpdate()
5227c9966a5SAndreas Gohr    {
5237c9966a5SAndreas Gohr        return $this->getRemoteTag('lastupdate');
5247c9966a5SAndreas Gohr    }
5257c9966a5SAndreas Gohr
5267c9966a5SAndreas Gohr    /**
5277c9966a5SAndreas Gohr     * Get a list of tags this extension is tagged with at dokuwiki.org
5287c9966a5SAndreas Gohr     *
5297c9966a5SAndreas Gohr     * @return string[]
5307c9966a5SAndreas Gohr     */
5317c9966a5SAndreas Gohr    public function getTags()
5327c9966a5SAndreas Gohr    {
5337c9966a5SAndreas Gohr        return $this->getRemoteTag('tags', []);
5347c9966a5SAndreas Gohr    }
5357c9966a5SAndreas Gohr
5367c9966a5SAndreas Gohr    /**
5377c9966a5SAndreas Gohr     * Get the popularity of the extension
5387c9966a5SAndreas Gohr     *
5397c9966a5SAndreas Gohr     * This is a float between 0 and 1
5407c9966a5SAndreas Gohr     *
5417c9966a5SAndreas Gohr     * @return float
5427c9966a5SAndreas Gohr     */
5437c9966a5SAndreas Gohr    public function getPopularity()
5447c9966a5SAndreas Gohr    {
5457c9966a5SAndreas Gohr        return (float)$this->getRemoteTag('popularity', 0);
5467c9966a5SAndreas Gohr    }
5477c9966a5SAndreas Gohr
5487c9966a5SAndreas Gohr    /**
5497c9966a5SAndreas Gohr     * Get the text of the update message if there is any
5507c9966a5SAndreas Gohr     *
5517c9966a5SAndreas Gohr     * @return string
5527c9966a5SAndreas Gohr     */
5537c9966a5SAndreas Gohr    public function getUpdateMessage()
5547c9966a5SAndreas Gohr    {
5557c9966a5SAndreas Gohr        return $this->getRemoteTag('updatemessage');
5567c9966a5SAndreas Gohr    }
5577c9966a5SAndreas Gohr
5587c9966a5SAndreas Gohr    /**
5597c9966a5SAndreas Gohr     * Get the text of the security warning if there is any
5607c9966a5SAndreas Gohr     *
5617c9966a5SAndreas Gohr     * @return string
5627c9966a5SAndreas Gohr     */
5637c9966a5SAndreas Gohr    public function getSecurityWarning()
5647c9966a5SAndreas Gohr    {
5657c9966a5SAndreas Gohr        return $this->getRemoteTag('securitywarning');
5667c9966a5SAndreas Gohr    }
5677c9966a5SAndreas Gohr
5687c9966a5SAndreas Gohr    /**
5697c9966a5SAndreas Gohr     * Get the text of the security issue if there is any
5707c9966a5SAndreas Gohr     *
5717c9966a5SAndreas Gohr     * @return string
5727c9966a5SAndreas Gohr     */
5737c9966a5SAndreas Gohr    public function getSecurityIssue()
5747c9966a5SAndreas Gohr    {
5757c9966a5SAndreas Gohr        return $this->getRemoteTag('securityissue');
5767c9966a5SAndreas Gohr    }
5777c9966a5SAndreas Gohr
5787c9966a5SAndreas Gohr    /**
5797c9966a5SAndreas Gohr     * Get the URL of the screenshot of the extension if there is any
5807c9966a5SAndreas Gohr     *
5817c9966a5SAndreas Gohr     * @return string
5827c9966a5SAndreas Gohr     */
5837c9966a5SAndreas Gohr    public function getScreenshotURL()
5847c9966a5SAndreas Gohr    {
5857c9966a5SAndreas Gohr        return $this->getRemoteTag('screenshoturl');
5867c9966a5SAndreas Gohr    }
5877c9966a5SAndreas Gohr
5887c9966a5SAndreas Gohr    /**
5897c9966a5SAndreas Gohr     * Get the URL of the thumbnail of the extension if there is any
5907c9966a5SAndreas Gohr     *
5917c9966a5SAndreas Gohr     * @return string
5927c9966a5SAndreas Gohr     */
5937c9966a5SAndreas Gohr    public function getThumbnailURL()
5947c9966a5SAndreas Gohr    {
5957c9966a5SAndreas Gohr        return $this->getRemoteTag('thumbnailurl');
5967c9966a5SAndreas Gohr    }
5977c9966a5SAndreas Gohr
5987c9966a5SAndreas Gohr    /**
5997c9966a5SAndreas Gohr     * Get the download URL of the extension if there is any
6007c9966a5SAndreas Gohr     *
6017c9966a5SAndreas Gohr     * @return string
6027c9966a5SAndreas Gohr     */
6037c9966a5SAndreas Gohr    public function getDownloadURL()
6047c9966a5SAndreas Gohr    {
6057c9966a5SAndreas Gohr        return $this->getRemoteTag('downloadurl');
6067c9966a5SAndreas Gohr    }
6077c9966a5SAndreas Gohr
6087c9966a5SAndreas Gohr    /**
6097c9966a5SAndreas Gohr     * Get the bug tracker URL of the extension if there is any
6107c9966a5SAndreas Gohr     *
6117c9966a5SAndreas Gohr     * @return string
6127c9966a5SAndreas Gohr     */
6137c9966a5SAndreas Gohr    public function getBugtrackerURL()
6147c9966a5SAndreas Gohr    {
6157c9966a5SAndreas Gohr        return $this->getRemoteTag('bugtracker');
6167c9966a5SAndreas Gohr    }
6177c9966a5SAndreas Gohr
6187c9966a5SAndreas Gohr    /**
6197c9966a5SAndreas Gohr     * Get the URL of the source repository if there is any
6207c9966a5SAndreas Gohr     *
6217c9966a5SAndreas Gohr     * @return string
6227c9966a5SAndreas Gohr     */
6237c9966a5SAndreas Gohr    public function getSourcerepoURL()
6247c9966a5SAndreas Gohr    {
6257c9966a5SAndreas Gohr        return $this->getRemoteTag('sourcerepo');
6267c9966a5SAndreas Gohr    }
6277c9966a5SAndreas Gohr
6287c9966a5SAndreas Gohr    /**
6297c9966a5SAndreas Gohr     * Get the donation URL of the extension if there is any
6307c9966a5SAndreas Gohr     *
6317c9966a5SAndreas Gohr     * @return string
6327c9966a5SAndreas Gohr     */
6337c9966a5SAndreas Gohr    public function getDonationURL()
6347c9966a5SAndreas Gohr    {
6357c9966a5SAndreas Gohr        return $this->getRemoteTag('donationurl');
6367c9966a5SAndreas Gohr    }
6377c9966a5SAndreas Gohr
6384fd6a1d7SAndreas Gohr    /**
6394fd6a1d7SAndreas Gohr     * Get a list of extensions that are similar to this one
6404fd6a1d7SAndreas Gohr     *
6414fd6a1d7SAndreas Gohr     * @return string[]
6424fd6a1d7SAndreas Gohr     */
6434fd6a1d7SAndreas Gohr    public function getSimilarList()
6444fd6a1d7SAndreas Gohr    {
6454fd6a1d7SAndreas Gohr        return $this->getRemoteTag('similar', []);
6464fd6a1d7SAndreas Gohr    }
6474fd6a1d7SAndreas Gohr
6484fd6a1d7SAndreas Gohr    /**
6494fd6a1d7SAndreas Gohr     * Get a list of extensions that are marked as conflicting with this one
6504fd6a1d7SAndreas Gohr     *
6514fd6a1d7SAndreas Gohr     * @return string[]
6524fd6a1d7SAndreas Gohr     */
6534fd6a1d7SAndreas Gohr    public function getConflictList()
6544fd6a1d7SAndreas Gohr    {
6554fd6a1d7SAndreas Gohr        return $this->getRemoteTag('conflicts', []);
6564fd6a1d7SAndreas Gohr    }
6574fd6a1d7SAndreas Gohr
6584fd6a1d7SAndreas Gohr    /**
6594fd6a1d7SAndreas Gohr     * Get a list of DokuWiki versions this plugin is marked as compatible with
6604fd6a1d7SAndreas Gohr     *
6614fd6a1d7SAndreas Gohr     * @return string[][] date -> version
6624fd6a1d7SAndreas Gohr     */
6634fd6a1d7SAndreas Gohr    public function getCompatibleVersions()
6644fd6a1d7SAndreas Gohr    {
6654fd6a1d7SAndreas Gohr        return $this->getRemoteTag('compatible', []);
6664fd6a1d7SAndreas Gohr    }
6674fd6a1d7SAndreas Gohr
6687c9966a5SAndreas Gohr    // endregion
6697c9966a5SAndreas Gohr
670cf2dcf1bSAndreas Gohr    // region Actions
671cf2dcf1bSAndreas Gohr
672cf2dcf1bSAndreas Gohr    /**
673cf2dcf1bSAndreas Gohr     * Install or update the extension
674cf2dcf1bSAndreas Gohr     *
675cf2dcf1bSAndreas Gohr     * @throws Exception
676cf2dcf1bSAndreas Gohr     */
677cf2dcf1bSAndreas Gohr    public function installOrUpdate()
678cf2dcf1bSAndreas Gohr    {
679cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
680160d3688SAndreas Gohr        $installer->installExtension($this);
681cf2dcf1bSAndreas Gohr    }
682cf2dcf1bSAndreas Gohr
683cf2dcf1bSAndreas Gohr    /**
684cf2dcf1bSAndreas Gohr     * Uninstall the extension
685cf2dcf1bSAndreas Gohr     * @throws Exception
686cf2dcf1bSAndreas Gohr     */
687cf2dcf1bSAndreas Gohr    public function uninstall()
688cf2dcf1bSAndreas Gohr    {
689cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
690cf2dcf1bSAndreas Gohr        $installer->uninstall($this);
691cf2dcf1bSAndreas Gohr    }
692cf2dcf1bSAndreas Gohr
693cf2dcf1bSAndreas Gohr    /**
6945732c960SAndreas Gohr     * Toggle the extension between enabled and disabled
6955732c960SAndreas Gohr     * @return void
6965732c960SAndreas Gohr     * @throws Exception
6975732c960SAndreas Gohr     */
6985732c960SAndreas Gohr    public function toggle()
6995732c960SAndreas Gohr    {
7005732c960SAndreas Gohr        if ($this->isEnabled()) {
7015732c960SAndreas Gohr            $this->disable();
7025732c960SAndreas Gohr        } else {
7035732c960SAndreas Gohr            $this->enable();
7045732c960SAndreas Gohr        }
7055732c960SAndreas Gohr    }
7065732c960SAndreas Gohr
7075732c960SAndreas Gohr    /**
708cf2dcf1bSAndreas Gohr     * Enable the extension
709*b69d74f1SAndreas Gohr     *
710cf2dcf1bSAndreas Gohr     * @throws Exception
711cf2dcf1bSAndreas Gohr     */
712cf2dcf1bSAndreas Gohr    public function enable()
713cf2dcf1bSAndreas Gohr    {
714*b69d74f1SAndreas Gohr        (new Installer())->enable($this);
715cf2dcf1bSAndreas Gohr    }
716cf2dcf1bSAndreas Gohr
717cf2dcf1bSAndreas Gohr    /**
718cf2dcf1bSAndreas Gohr     * Disable the extension
719*b69d74f1SAndreas Gohr     *
720cf2dcf1bSAndreas Gohr     * @throws Exception
721cf2dcf1bSAndreas Gohr     */
722cf2dcf1bSAndreas Gohr    public function disable()
723cf2dcf1bSAndreas Gohr    {
724*b69d74f1SAndreas Gohr        (new Installer())->disable($this);
725cf2dcf1bSAndreas Gohr    }
726cf2dcf1bSAndreas Gohr
727cf2dcf1bSAndreas Gohr    // endregion
728cf2dcf1bSAndreas Gohr
729cf2dcf1bSAndreas Gohr    // region Meta Data Management
730cf2dcf1bSAndreas Gohr
731cf2dcf1bSAndreas Gohr    /**
7327c9966a5SAndreas Gohr     * Access the Manager for this extension
733cf2dcf1bSAndreas Gohr     *
7347c9966a5SAndreas Gohr     * @return Manager
735cf2dcf1bSAndreas Gohr     */
7367c9966a5SAndreas Gohr    public function getManager()
737cf2dcf1bSAndreas Gohr    {
7387c184cfcSAndreas Gohr        if (!$this->manager instanceof Manager) {
7397c9966a5SAndreas Gohr            $this->manager = new Manager($this);
740cf2dcf1bSAndreas Gohr        }
7417c9966a5SAndreas Gohr        return $this->manager;
742cf2dcf1bSAndreas Gohr    }
743cf2dcf1bSAndreas Gohr
744cf2dcf1bSAndreas Gohr    /**
745cf2dcf1bSAndreas Gohr     * Reads the info file of the extension if available and fills the localInfo array
746cf2dcf1bSAndreas Gohr     */
747cf2dcf1bSAndreas Gohr    protected function readLocalInfo()
748cf2dcf1bSAndreas Gohr    {
749a1e045f7SAndreas Gohr        if (!$this->getCurrentDir()) return;
750cf2dcf1bSAndreas Gohr        $file = $this->currentDir . '/' . $this->type . '.info.txt';
751cf2dcf1bSAndreas Gohr        if (!is_readable($file)) return;
752cf2dcf1bSAndreas Gohr        $this->localInfo = confToHash($file, true);
753cf2dcf1bSAndreas Gohr        $this->localInfo = array_filter($this->localInfo); // remove all falsy keys
754cf2dcf1bSAndreas Gohr    }
755cf2dcf1bSAndreas Gohr
756cf2dcf1bSAndreas Gohr    /**
757cf2dcf1bSAndreas Gohr     * Fetches the remote info from the repository
758cf2dcf1bSAndreas Gohr     *
759cf2dcf1bSAndreas Gohr     * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case
760cf2dcf1bSAndreas Gohr     */
761cf2dcf1bSAndreas Gohr    protected function loadRemoteInfo()
762cf2dcf1bSAndreas Gohr    {
763cf2dcf1bSAndreas Gohr        if ($this->remoteInfo) return;
764cf2dcf1bSAndreas Gohr        $remote = Repository::getInstance();
765cf2dcf1bSAndreas Gohr        try {
766cf2dcf1bSAndreas Gohr            $this->remoteInfo = (array)$remote->getExtensionData($this->getId());
767cf2dcf1bSAndreas Gohr        } catch (Exception $e) {
768cf2dcf1bSAndreas Gohr            $this->remoteInfo = [];
769cf2dcf1bSAndreas Gohr        }
770cf2dcf1bSAndreas Gohr    }
771cf2dcf1bSAndreas Gohr
772cf2dcf1bSAndreas Gohr    /**
773cf2dcf1bSAndreas Gohr     * Read information from either local or remote info
774cf2dcf1bSAndreas Gohr     *
7757c9966a5SAndreas Gohr     * Always prefers local info over remote info. Giving multiple keys is useful when the
7767c9966a5SAndreas Gohr     * key has been renamed in the past or if local and remote keys might differ.
777cf2dcf1bSAndreas Gohr     *
778cf2dcf1bSAndreas Gohr     * @param string|string[] $tag one or multiple keys to check
779cf2dcf1bSAndreas Gohr     * @param mixed $default
780cf2dcf1bSAndreas Gohr     * @return mixed
781cf2dcf1bSAndreas Gohr     */
782cf2dcf1bSAndreas Gohr    protected function getTag($tag, $default = '')
783cf2dcf1bSAndreas Gohr    {
784cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
785cf2dcf1bSAndreas Gohr            if (isset($this->localInfo[$t])) return $this->localInfo[$t];
786cf2dcf1bSAndreas Gohr        }
7877c9966a5SAndreas Gohr
7887c9966a5SAndreas Gohr        return $this->getRemoteTag($tag, $default);
7897c9966a5SAndreas Gohr    }
7907c9966a5SAndreas Gohr
7917c9966a5SAndreas Gohr    /**
7927c9966a5SAndreas Gohr     * Read information from remote info
7937c9966a5SAndreas Gohr     *
7947c9966a5SAndreas Gohr     * @param string|string[] $tag one or mutiple keys to check
7957c9966a5SAndreas Gohr     * @param mixed $default
7967c9966a5SAndreas Gohr     * @return mixed
7977c9966a5SAndreas Gohr     */
7987c9966a5SAndreas Gohr    protected function getRemoteTag($tag, $default = '')
7997c9966a5SAndreas Gohr    {
800cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
801cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
802cf2dcf1bSAndreas Gohr            if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t];
803cf2dcf1bSAndreas Gohr        }
804cf2dcf1bSAndreas Gohr        return $default;
805cf2dcf1bSAndreas Gohr    }
806cf2dcf1bSAndreas Gohr
807cf2dcf1bSAndreas Gohr    // endregion
808a1e045f7SAndreas Gohr
809a1e045f7SAndreas Gohr    // region utilities
810a1e045f7SAndreas Gohr
811a1e045f7SAndreas Gohr    /**
812a1e045f7SAndreas Gohr     * Convert an extension id to a type and base
813a1e045f7SAndreas Gohr     *
814a1e045f7SAndreas Gohr     * @param string $id
815a1e045f7SAndreas Gohr     * @return array [type, base]
816a1e045f7SAndreas Gohr     */
817a1e045f7SAndreas Gohr    protected function idToTypeBase($id)
818a1e045f7SAndreas Gohr    {
819a1e045f7SAndreas Gohr        [$type, $base] = sexplode(':', $id, 2);
820a1e045f7SAndreas Gohr        if ($base === null) {
821a1e045f7SAndreas Gohr            $base = $type;
822a1e045f7SAndreas Gohr            $type = self::TYPE_PLUGIN;
823a1e045f7SAndreas Gohr        } elseif ($type === self::TYPE_TEMPLATE) {
824a1e045f7SAndreas Gohr            $type = self::TYPE_TEMPLATE;
825a1e045f7SAndreas Gohr        } else {
826a1e045f7SAndreas Gohr            throw new RuntimeException('Invalid extension id: ' . $id);
827a1e045f7SAndreas Gohr        }
828a1e045f7SAndreas Gohr
829a1e045f7SAndreas Gohr        return [$type, $base];
830a1e045f7SAndreas Gohr    }
831*b69d74f1SAndreas Gohr
8327c9966a5SAndreas Gohr    /**
8337c9966a5SAndreas Gohr     * @return string
8347c9966a5SAndreas Gohr     */
8357c9966a5SAndreas Gohr    public function __toString()
8367c9966a5SAndreas Gohr    {
8377c9966a5SAndreas Gohr        return $this->getId();
8387c9966a5SAndreas Gohr    }
8397c9966a5SAndreas Gohr
840a1e045f7SAndreas Gohr    // endregion
841cf2dcf1bSAndreas Gohr}
842