xref: /dokuwiki/lib/plugins/extension/Extension.php (revision a1e045f72e3be1dcea57282e31aeb0835ab73e23)
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{
11cf2dcf1bSAndreas Gohr    const TYPE_PLUGIN = 'plugin';
12cf2dcf1bSAndreas Gohr    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
20cf2dcf1bSAndreas Gohr    /** @var string|null The current location of this extension */
21cf2dcf1bSAndreas 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
29cf2dcf1bSAndreas Gohr    /** @var array The manager info array of the extension */
30cf2dcf1bSAndreas Gohr    protected array $managerInfo = [];
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    /**
42*a1e045f7SAndreas Gohr     * Initializes an extension from an id
43*a1e045f7SAndreas Gohr     *
44*a1e045f7SAndreas Gohr     * @param string $id The id of the extension
45*a1e045f7SAndreas Gohr     * @return Extension
46*a1e045f7SAndreas Gohr     */
47*a1e045f7SAndreas Gohr    public static function createFromId($id)
48*a1e045f7SAndreas Gohr    {
49*a1e045f7SAndreas Gohr        $extension = new self();
50*a1e045f7SAndreas Gohr        $extension->initFromId($id);
51*a1e045f7SAndreas Gohr        return $extension;
52*a1e045f7SAndreas Gohr    }
53*a1e045f7SAndreas Gohr
54*a1e045f7SAndreas Gohr    protected function initFromId($id)
55*a1e045f7SAndreas Gohr    {
56*a1e045f7SAndreas Gohr        [$type, $base] = $this->idToTypeBase($id);
57*a1e045f7SAndreas Gohr        $this->type = $type;
58*a1e045f7SAndreas Gohr        $this->base = $base;
59*a1e045f7SAndreas Gohr        $this->readLocalInfo();
60*a1e045f7SAndreas Gohr    }
61*a1e045f7SAndreas Gohr
62*a1e045f7SAndreas 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);
83cf2dcf1bSAndreas Gohr        $this->currentDir = realpath($dir);
84cf2dcf1bSAndreas Gohr
85cf2dcf1bSAndreas Gohr        if ($type === null || $type === self::TYPE_TEMPLATE) {
86cf2dcf1bSAndreas Gohr            if (
87cf2dcf1bSAndreas Gohr                file_exists($dir . '/template.info.php') ||
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
127*a1e045f7SAndreas 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    /**
143cf2dcf1bSAndreas Gohr     * @return string The extension id (same as base but prefixed with "template:" for templates)
144cf2dcf1bSAndreas Gohr     */
145cf2dcf1bSAndreas Gohr    public function getId()
146cf2dcf1bSAndreas Gohr    {
147cf2dcf1bSAndreas Gohr        if ($this->type === self::TYPE_TEMPLATE) {
148cf2dcf1bSAndreas Gohr            return self::TYPE_TEMPLATE . ':' . $this->base;
149cf2dcf1bSAndreas Gohr        }
150cf2dcf1bSAndreas Gohr        return $this->base;
151cf2dcf1bSAndreas Gohr    }
152cf2dcf1bSAndreas Gohr
153cf2dcf1bSAndreas Gohr    /**
154cf2dcf1bSAndreas Gohr     * Get the base name of this extension
155cf2dcf1bSAndreas Gohr     *
156cf2dcf1bSAndreas Gohr     * @return string
157cf2dcf1bSAndreas Gohr     */
158cf2dcf1bSAndreas Gohr    public function getBase()
159cf2dcf1bSAndreas Gohr    {
160cf2dcf1bSAndreas Gohr        return $this->base;
161cf2dcf1bSAndreas Gohr    }
162cf2dcf1bSAndreas Gohr
163cf2dcf1bSAndreas Gohr    /**
164cf2dcf1bSAndreas Gohr     * Get the type of the extension
165cf2dcf1bSAndreas Gohr     *
166cf2dcf1bSAndreas Gohr     * @return string "plugin"|"template"
167cf2dcf1bSAndreas Gohr     */
168cf2dcf1bSAndreas Gohr    public function getType()
169cf2dcf1bSAndreas Gohr    {
170cf2dcf1bSAndreas Gohr        return $this->type;
171cf2dcf1bSAndreas Gohr    }
172cf2dcf1bSAndreas Gohr
173cf2dcf1bSAndreas Gohr    /**
174cf2dcf1bSAndreas Gohr     * The current directory of the extension
175cf2dcf1bSAndreas Gohr     *
176cf2dcf1bSAndreas Gohr     * @return string|null
177cf2dcf1bSAndreas Gohr     */
178cf2dcf1bSAndreas Gohr    public function getCurrentDir()
179cf2dcf1bSAndreas Gohr    {
180cf2dcf1bSAndreas Gohr        // recheck that the current currentDir is still valid
181cf2dcf1bSAndreas Gohr        if ($this->currentDir && !is_dir($this->currentDir)) {
182cf2dcf1bSAndreas Gohr            $this->currentDir = null;
183cf2dcf1bSAndreas Gohr        }
184cf2dcf1bSAndreas Gohr
185cf2dcf1bSAndreas Gohr        // if the extension is installed, then the currentDir is the install dir!
186cf2dcf1bSAndreas Gohr        if (!$this->currentDir && $this->isInstalled()) {
187cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
188cf2dcf1bSAndreas Gohr        }
189cf2dcf1bSAndreas Gohr
190cf2dcf1bSAndreas Gohr        return $this->currentDir;
191cf2dcf1bSAndreas Gohr    }
192cf2dcf1bSAndreas Gohr
193cf2dcf1bSAndreas Gohr    /**
194cf2dcf1bSAndreas Gohr     * Get the directory where this extension should be installed in
195cf2dcf1bSAndreas Gohr     *
196cf2dcf1bSAndreas Gohr     * Note: this does not mean that the extension is actually installed there
197cf2dcf1bSAndreas Gohr     *
198cf2dcf1bSAndreas Gohr     * @return string
199cf2dcf1bSAndreas Gohr     */
200cf2dcf1bSAndreas Gohr    public function getInstallDir()
201cf2dcf1bSAndreas Gohr    {
202cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
203cf2dcf1bSAndreas Gohr            $dir = dirname(tpl_incdir()) . $this->base;
204cf2dcf1bSAndreas Gohr        } else {
205cf2dcf1bSAndreas Gohr            $dir = DOKU_PLUGIN . $this->base;
206cf2dcf1bSAndreas Gohr        }
207cf2dcf1bSAndreas Gohr
208cf2dcf1bSAndreas Gohr        return realpath($dir);
209cf2dcf1bSAndreas Gohr    }
210cf2dcf1bSAndreas Gohr
211cf2dcf1bSAndreas Gohr
212cf2dcf1bSAndreas Gohr    /**
213cf2dcf1bSAndreas Gohr     * Get the display name of the extension
214cf2dcf1bSAndreas Gohr     *
215cf2dcf1bSAndreas Gohr     * @return string
216cf2dcf1bSAndreas Gohr     */
217cf2dcf1bSAndreas Gohr    public function getDisplayName()
218cf2dcf1bSAndreas Gohr    {
219cf2dcf1bSAndreas Gohr        return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType()));
220cf2dcf1bSAndreas Gohr    }
221cf2dcf1bSAndreas Gohr
222cf2dcf1bSAndreas Gohr    /**
223cf2dcf1bSAndreas Gohr     * Get the author name of the extension
224cf2dcf1bSAndreas Gohr     *
225cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the author info is missing
226cf2dcf1bSAndreas Gohr     */
227cf2dcf1bSAndreas Gohr    public function getAuthor()
228cf2dcf1bSAndreas Gohr    {
229cf2dcf1bSAndreas Gohr        return $this->getTag('author');
230cf2dcf1bSAndreas Gohr    }
231cf2dcf1bSAndreas Gohr
232cf2dcf1bSAndreas Gohr    /**
233cf2dcf1bSAndreas Gohr     * Get the email of the author of the extension if there is any
234cf2dcf1bSAndreas Gohr     *
235cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the email info is missing
236cf2dcf1bSAndreas Gohr     */
237cf2dcf1bSAndreas Gohr    public function getEmail()
238cf2dcf1bSAndreas Gohr    {
239cf2dcf1bSAndreas Gohr        // email is only in the local data
240cf2dcf1bSAndreas Gohr        return $this->localInfo['email'] ?? '';
241cf2dcf1bSAndreas Gohr    }
242cf2dcf1bSAndreas Gohr
243cf2dcf1bSAndreas Gohr    /**
244cf2dcf1bSAndreas Gohr     * Get the email id, i.e. the md5sum of the email
245cf2dcf1bSAndreas Gohr     *
246cf2dcf1bSAndreas Gohr     * @return string Empty string if no email is available
247cf2dcf1bSAndreas Gohr     */
248cf2dcf1bSAndreas Gohr    public function getEmailID()
249cf2dcf1bSAndreas Gohr    {
250cf2dcf1bSAndreas Gohr        if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid'];
251cf2dcf1bSAndreas Gohr        if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']);
252cf2dcf1bSAndreas Gohr        return '';
253cf2dcf1bSAndreas Gohr    }
254cf2dcf1bSAndreas Gohr
255cf2dcf1bSAndreas Gohr    /**
256cf2dcf1bSAndreas Gohr     * Get the description of the extension
257cf2dcf1bSAndreas Gohr     *
258cf2dcf1bSAndreas Gohr     * @return string Empty string if no description is available
259cf2dcf1bSAndreas Gohr     */
260cf2dcf1bSAndreas Gohr    public function getDescription()
261cf2dcf1bSAndreas Gohr    {
262cf2dcf1bSAndreas Gohr        return $this->getTag(['desc', 'description']);
263cf2dcf1bSAndreas Gohr    }
264cf2dcf1bSAndreas Gohr
265cf2dcf1bSAndreas Gohr    /**
266cf2dcf1bSAndreas Gohr     * Get the URL of the extension, usually a page on dokuwiki.org
267cf2dcf1bSAndreas Gohr     *
268cf2dcf1bSAndreas Gohr     * @return string
269cf2dcf1bSAndreas Gohr     */
270cf2dcf1bSAndreas Gohr    public function getURL()
271cf2dcf1bSAndreas Gohr    {
272cf2dcf1bSAndreas Gohr        return $this->getTag(
273cf2dcf1bSAndreas Gohr            'url',
274cf2dcf1bSAndreas Gohr            'https://www.dokuwiki.org/' .
275cf2dcf1bSAndreas Gohr            ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase()
276cf2dcf1bSAndreas Gohr        );
277cf2dcf1bSAndreas Gohr    }
278cf2dcf1bSAndreas Gohr
279cf2dcf1bSAndreas Gohr    /**
280cf2dcf1bSAndreas Gohr     * Is this extension a template?
281cf2dcf1bSAndreas Gohr     *
282cf2dcf1bSAndreas Gohr     * @return bool false if it is a plugin
283cf2dcf1bSAndreas Gohr     */
284cf2dcf1bSAndreas Gohr    public function isTemplate()
285cf2dcf1bSAndreas Gohr    {
286cf2dcf1bSAndreas Gohr        return $this->type === self::TYPE_TEMPLATE;
287cf2dcf1bSAndreas Gohr    }
288cf2dcf1bSAndreas Gohr
289cf2dcf1bSAndreas Gohr    /**
290cf2dcf1bSAndreas Gohr     * Is the extension installed locally?
291cf2dcf1bSAndreas Gohr     *
292cf2dcf1bSAndreas Gohr     * @return bool
293cf2dcf1bSAndreas Gohr     */
294cf2dcf1bSAndreas Gohr    public function isInstalled()
295cf2dcf1bSAndreas Gohr    {
296cf2dcf1bSAndreas Gohr        return is_dir($this->getInstallDir());
297cf2dcf1bSAndreas Gohr    }
298cf2dcf1bSAndreas Gohr
299cf2dcf1bSAndreas Gohr    /**
300cf2dcf1bSAndreas Gohr     * Is the extension under git control?
301cf2dcf1bSAndreas Gohr     *
302cf2dcf1bSAndreas Gohr     * @return bool
303cf2dcf1bSAndreas Gohr     */
304cf2dcf1bSAndreas Gohr    public function isGitControlled()
305cf2dcf1bSAndreas Gohr    {
306cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) return false;
307cf2dcf1bSAndreas Gohr        return file_exists($this->getInstallDir() . '/.git');
308cf2dcf1bSAndreas Gohr    }
309cf2dcf1bSAndreas Gohr
310cf2dcf1bSAndreas Gohr    /**
311cf2dcf1bSAndreas Gohr     * If the extension is bundled
312cf2dcf1bSAndreas Gohr     *
313cf2dcf1bSAndreas Gohr     * @return bool If the extension is bundled
314cf2dcf1bSAndreas Gohr     */
315cf2dcf1bSAndreas Gohr    public function isBundled()
316cf2dcf1bSAndreas Gohr    {
317cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
318cf2dcf1bSAndreas Gohr        return $this->remoteInfo['bundled'] ?? in_array(
319cf2dcf1bSAndreas Gohr            $this->getId(),
320cf2dcf1bSAndreas Gohr            [
321cf2dcf1bSAndreas Gohr                'authad',
322cf2dcf1bSAndreas Gohr                'authldap',
323cf2dcf1bSAndreas Gohr                'authpdo',
324cf2dcf1bSAndreas Gohr                'authplain',
325cf2dcf1bSAndreas Gohr                'acl',
326cf2dcf1bSAndreas Gohr                'config',
327cf2dcf1bSAndreas Gohr                'extension',
328cf2dcf1bSAndreas Gohr                'info',
329cf2dcf1bSAndreas Gohr                'popularity',
330cf2dcf1bSAndreas Gohr                'revert',
331cf2dcf1bSAndreas Gohr                'safefnrecode',
332cf2dcf1bSAndreas Gohr                'styling',
333cf2dcf1bSAndreas Gohr                'testing',
334cf2dcf1bSAndreas Gohr                'usermanager',
335cf2dcf1bSAndreas Gohr                'logviewer',
336cf2dcf1bSAndreas Gohr                'template:dokuwiki'
337cf2dcf1bSAndreas Gohr            ]
338cf2dcf1bSAndreas Gohr        );
339cf2dcf1bSAndreas Gohr    }
340cf2dcf1bSAndreas Gohr
341cf2dcf1bSAndreas Gohr    /**
342cf2dcf1bSAndreas Gohr     * Is the extension protected against any modification (disable/uninstall)
343cf2dcf1bSAndreas Gohr     *
344cf2dcf1bSAndreas Gohr     * @return bool if the extension is protected
345cf2dcf1bSAndreas Gohr     */
346cf2dcf1bSAndreas Gohr    public function isProtected()
347cf2dcf1bSAndreas Gohr    {
348cf2dcf1bSAndreas Gohr        // never allow deinstalling the current auth plugin:
349cf2dcf1bSAndreas Gohr        global $conf;
350cf2dcf1bSAndreas Gohr        if ($this->getId() == $conf['authtype']) return true;
351cf2dcf1bSAndreas Gohr
352cf2dcf1bSAndreas Gohr        // FIXME disallow current template to be uninstalled
353cf2dcf1bSAndreas Gohr
354cf2dcf1bSAndreas Gohr        /** @var PluginController $plugin_controller */
355cf2dcf1bSAndreas Gohr        global $plugin_controller;
356cf2dcf1bSAndreas Gohr        $cascade = $plugin_controller->getCascade();
357cf2dcf1bSAndreas Gohr        return ($cascade['protected'][$this->getId()] ?? false);
358cf2dcf1bSAndreas Gohr    }
359cf2dcf1bSAndreas Gohr
360cf2dcf1bSAndreas Gohr    /**
361cf2dcf1bSAndreas Gohr     * Is the extension installed in the correct directory?
362cf2dcf1bSAndreas Gohr     *
363cf2dcf1bSAndreas Gohr     * @return bool
364cf2dcf1bSAndreas Gohr     */
365cf2dcf1bSAndreas Gohr    public function isInWrongFolder()
366cf2dcf1bSAndreas Gohr    {
367cf2dcf1bSAndreas Gohr        return $this->getInstallDir() != $this->currentDir;
368cf2dcf1bSAndreas Gohr    }
369cf2dcf1bSAndreas Gohr
370cf2dcf1bSAndreas Gohr    /**
371cf2dcf1bSAndreas Gohr     * Is the extension enabled?
372cf2dcf1bSAndreas Gohr     *
373cf2dcf1bSAndreas Gohr     * @return bool
374cf2dcf1bSAndreas Gohr     */
375cf2dcf1bSAndreas Gohr    public function isEnabled()
376cf2dcf1bSAndreas Gohr    {
377cf2dcf1bSAndreas Gohr        global $conf;
378cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
379cf2dcf1bSAndreas Gohr            return ($conf['template'] == $this->getBase());
380cf2dcf1bSAndreas Gohr        }
381cf2dcf1bSAndreas Gohr
382cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
383cf2dcf1bSAndreas Gohr        global $plugin_controller;
384cf2dcf1bSAndreas Gohr        return $plugin_controller->isEnabled($this->base);
385cf2dcf1bSAndreas Gohr    }
386cf2dcf1bSAndreas Gohr
387cf2dcf1bSAndreas Gohr    // endregion
388cf2dcf1bSAndreas Gohr
389cf2dcf1bSAndreas Gohr    // region Actions
390cf2dcf1bSAndreas Gohr
391cf2dcf1bSAndreas Gohr    /**
392cf2dcf1bSAndreas Gohr     * Install or update the extension
393cf2dcf1bSAndreas Gohr     *
394cf2dcf1bSAndreas Gohr     * @throws Exception
395cf2dcf1bSAndreas Gohr     */
396cf2dcf1bSAndreas Gohr    public function installOrUpdate()
397cf2dcf1bSAndreas Gohr    {
398cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
399cf2dcf1bSAndreas Gohr        $installer->installFromUrl(
400cf2dcf1bSAndreas Gohr            $this->getURL(),
401cf2dcf1bSAndreas Gohr            $this->getBase(),
402cf2dcf1bSAndreas Gohr        );
403cf2dcf1bSAndreas Gohr    }
404cf2dcf1bSAndreas Gohr
405cf2dcf1bSAndreas Gohr    /**
406cf2dcf1bSAndreas Gohr     * Uninstall the extension
407cf2dcf1bSAndreas Gohr     * @throws Exception
408cf2dcf1bSAndreas Gohr     */
409cf2dcf1bSAndreas Gohr    public function uninstall()
410cf2dcf1bSAndreas Gohr    {
411cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
412cf2dcf1bSAndreas Gohr        $installer->uninstall($this);
413cf2dcf1bSAndreas Gohr    }
414cf2dcf1bSAndreas Gohr
415cf2dcf1bSAndreas Gohr    /**
416cf2dcf1bSAndreas Gohr     * Enable the extension
417cf2dcf1bSAndreas Gohr     * @todo I'm unsure if this code should be here or part of Installer
418cf2dcf1bSAndreas Gohr     * @throws Exception
419cf2dcf1bSAndreas Gohr     */
420cf2dcf1bSAndreas Gohr    public function enable()
421cf2dcf1bSAndreas Gohr    {
422cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) throw new Exception('notimplemented');
423cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) throw new Exception('notinstalled');
424cf2dcf1bSAndreas Gohr        if ($this->isEnabled()) throw new Exception('alreadyenabled');
425cf2dcf1bSAndreas Gohr
426cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
427cf2dcf1bSAndreas Gohr        global $plugin_controller;
428cf2dcf1bSAndreas Gohr        if (!$plugin_controller->enable($this->base)) {
429cf2dcf1bSAndreas Gohr            throw new Exception('pluginlistsaveerror');
430cf2dcf1bSAndreas Gohr        }
431cf2dcf1bSAndreas Gohr        Installer::purgeCache();
432cf2dcf1bSAndreas Gohr    }
433cf2dcf1bSAndreas Gohr
434cf2dcf1bSAndreas Gohr    /**
435cf2dcf1bSAndreas Gohr     * Disable the extension
436cf2dcf1bSAndreas Gohr     * @todo I'm unsure if this code should be here or part of Installer
437cf2dcf1bSAndreas Gohr     * @throws Exception
438cf2dcf1bSAndreas Gohr     */
439cf2dcf1bSAndreas Gohr    public function disable()
440cf2dcf1bSAndreas Gohr    {
441cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) throw new Exception('notimplemented');
442cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) throw new Exception('notinstalled');
443cf2dcf1bSAndreas Gohr        if (!$this->isEnabled()) throw new Exception('alreadydisabled');
444cf2dcf1bSAndreas Gohr        if ($this->isProtected()) throw new Exception('error_disable_protected');
445cf2dcf1bSAndreas Gohr
446cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
447cf2dcf1bSAndreas Gohr        global $plugin_controller;
448cf2dcf1bSAndreas Gohr        if (!$plugin_controller->disable($this->base)) {
449cf2dcf1bSAndreas Gohr            throw new Exception('pluginlistsaveerror');
450cf2dcf1bSAndreas Gohr        }
451cf2dcf1bSAndreas Gohr        Installer::purgeCache();
452cf2dcf1bSAndreas Gohr    }
453cf2dcf1bSAndreas Gohr
454cf2dcf1bSAndreas Gohr    // endregion
455cf2dcf1bSAndreas Gohr
456cf2dcf1bSAndreas Gohr    // region Meta Data Management
457cf2dcf1bSAndreas Gohr
458cf2dcf1bSAndreas Gohr    /**
459cf2dcf1bSAndreas Gohr     * This updates the timestamp and URL in the manager.dat file
460cf2dcf1bSAndreas Gohr     *
461cf2dcf1bSAndreas Gohr     * It is called by Installer when installing or updating an extension
462cf2dcf1bSAndreas Gohr     *
463cf2dcf1bSAndreas Gohr     * @param $url
464cf2dcf1bSAndreas Gohr     */
465cf2dcf1bSAndreas Gohr    public function updateManagerInfo($url)
466cf2dcf1bSAndreas Gohr    {
467cf2dcf1bSAndreas Gohr        $this->managerInfo['downloadurl'] = $url;
468cf2dcf1bSAndreas Gohr        if (isset($this->managerInfo['installed'])) {
469cf2dcf1bSAndreas Gohr            // it's an update
470cf2dcf1bSAndreas Gohr            $this->managerInfo['updated'] = date('r');
471cf2dcf1bSAndreas Gohr        } else {
472cf2dcf1bSAndreas Gohr            // it's a new install
473cf2dcf1bSAndreas Gohr            $this->managerInfo['installed'] = date('r');
474cf2dcf1bSAndreas Gohr        }
475cf2dcf1bSAndreas Gohr
476cf2dcf1bSAndreas Gohr        $managerpath = $this->getInstallDir() . '/manager.dat';
477cf2dcf1bSAndreas Gohr        $data = '';
478cf2dcf1bSAndreas Gohr        foreach ($this->managerInfo as $k => $v) {
479cf2dcf1bSAndreas Gohr            $data .= $k . '=' . $v . DOKU_LF;
480cf2dcf1bSAndreas Gohr        }
481cf2dcf1bSAndreas Gohr        io_saveFile($managerpath, $data);
482cf2dcf1bSAndreas Gohr    }
483cf2dcf1bSAndreas Gohr
484cf2dcf1bSAndreas Gohr    /**
485cf2dcf1bSAndreas Gohr     * Reads the manager.dat file and fills the managerInfo array
486cf2dcf1bSAndreas Gohr     */
487cf2dcf1bSAndreas Gohr    protected function readManagerInfo()
488cf2dcf1bSAndreas Gohr    {
489cf2dcf1bSAndreas Gohr        if ($this->managerInfo) return;
490cf2dcf1bSAndreas Gohr
491cf2dcf1bSAndreas Gohr        $managerpath = $this->getInstallDir() . '/manager.dat';
492cf2dcf1bSAndreas Gohr        if (!is_readable($managerpath)) return;
493cf2dcf1bSAndreas Gohr
494cf2dcf1bSAndreas Gohr        $file = (array)@file($managerpath);
495cf2dcf1bSAndreas Gohr        foreach ($file as $line) {
496cf2dcf1bSAndreas Gohr            [$key, $value] = sexplode('=', $line, 2, '');
497cf2dcf1bSAndreas Gohr            $key = trim($key);
498cf2dcf1bSAndreas Gohr            $value = trim($value);
499cf2dcf1bSAndreas Gohr            // backwards compatible with old plugin manager
500cf2dcf1bSAndreas Gohr            if ($key == 'url') $key = 'downloadurl';
501cf2dcf1bSAndreas Gohr            $this->managerInfo[$key] = $value;
502cf2dcf1bSAndreas Gohr        }
503cf2dcf1bSAndreas Gohr    }
504cf2dcf1bSAndreas Gohr
505cf2dcf1bSAndreas Gohr    /**
506cf2dcf1bSAndreas Gohr     * Reads the info file of the extension if available and fills the localInfo array
507cf2dcf1bSAndreas Gohr     */
508cf2dcf1bSAndreas Gohr    protected function readLocalInfo()
509cf2dcf1bSAndreas Gohr    {
510*a1e045f7SAndreas Gohr        if (!$this->getCurrentDir()) return;
511cf2dcf1bSAndreas Gohr        $file = $this->currentDir . '/' . $this->type . '.info.txt';
512cf2dcf1bSAndreas Gohr        if (!is_readable($file)) return;
513cf2dcf1bSAndreas Gohr        $this->localInfo = confToHash($file, true);
514cf2dcf1bSAndreas Gohr        $this->localInfo = array_filter($this->localInfo); // remove all falsy keys
515cf2dcf1bSAndreas Gohr    }
516cf2dcf1bSAndreas Gohr
517cf2dcf1bSAndreas Gohr    /**
518cf2dcf1bSAndreas Gohr     * Fetches the remote info from the repository
519cf2dcf1bSAndreas Gohr     *
520cf2dcf1bSAndreas Gohr     * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case
521cf2dcf1bSAndreas Gohr     */
522cf2dcf1bSAndreas Gohr    protected function loadRemoteInfo()
523cf2dcf1bSAndreas Gohr    {
524cf2dcf1bSAndreas Gohr        if ($this->remoteInfo) return;
525cf2dcf1bSAndreas Gohr        $remote = Repository::getInstance();
526cf2dcf1bSAndreas Gohr        try {
527cf2dcf1bSAndreas Gohr            $this->remoteInfo = (array)$remote->getExtensionData($this->getId());
528cf2dcf1bSAndreas Gohr        } catch (Exception $e) {
529cf2dcf1bSAndreas Gohr            $this->remoteInfo = [];
530cf2dcf1bSAndreas Gohr        }
531cf2dcf1bSAndreas Gohr    }
532cf2dcf1bSAndreas Gohr
533cf2dcf1bSAndreas Gohr    /**
534cf2dcf1bSAndreas Gohr     * Read information from either local or remote info
535cf2dcf1bSAndreas Gohr     *
536cf2dcf1bSAndreas Gohr     * Always prefers local info over remote info
537cf2dcf1bSAndreas Gohr     *
538cf2dcf1bSAndreas Gohr     * @param string|string[] $tag one or multiple keys to check
539cf2dcf1bSAndreas Gohr     * @param mixed $default
540cf2dcf1bSAndreas Gohr     * @return mixed
541cf2dcf1bSAndreas Gohr     */
542cf2dcf1bSAndreas Gohr    protected function getTag($tag, $default = '')
543cf2dcf1bSAndreas Gohr    {
544cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
545cf2dcf1bSAndreas Gohr            if (isset($this->localInfo[$t])) return $this->localInfo[$t];
546cf2dcf1bSAndreas Gohr        }
547cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
548cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
549cf2dcf1bSAndreas Gohr            if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t];
550cf2dcf1bSAndreas Gohr        }
551cf2dcf1bSAndreas Gohr
552cf2dcf1bSAndreas Gohr        return $default;
553cf2dcf1bSAndreas Gohr    }
554cf2dcf1bSAndreas Gohr
555cf2dcf1bSAndreas Gohr    // endregion
556*a1e045f7SAndreas Gohr
557*a1e045f7SAndreas Gohr    // region utilities
558*a1e045f7SAndreas Gohr
559*a1e045f7SAndreas Gohr    /**
560*a1e045f7SAndreas Gohr     * Convert an extension id to a type and base
561*a1e045f7SAndreas Gohr     *
562*a1e045f7SAndreas Gohr     * @param string $id
563*a1e045f7SAndreas Gohr     * @return array [type, base]
564*a1e045f7SAndreas Gohr     */
565*a1e045f7SAndreas Gohr    protected function idToTypeBase($id)
566*a1e045f7SAndreas Gohr    {
567*a1e045f7SAndreas Gohr        [$type, $base] = sexplode(':', $id, 2);
568*a1e045f7SAndreas Gohr        if ($base === null) {
569*a1e045f7SAndreas Gohr            $base = $type;
570*a1e045f7SAndreas Gohr            $type = self::TYPE_PLUGIN;
571*a1e045f7SAndreas Gohr        } elseif ($type === self::TYPE_TEMPLATE) {
572*a1e045f7SAndreas Gohr            $type = self::TYPE_TEMPLATE;
573*a1e045f7SAndreas Gohr        } else {
574*a1e045f7SAndreas Gohr            throw new RuntimeException('Invalid extension id: ' . $id);
575*a1e045f7SAndreas Gohr        }
576*a1e045f7SAndreas Gohr
577*a1e045f7SAndreas Gohr        return [$type, $base];
578*a1e045f7SAndreas Gohr    }
579*a1e045f7SAndreas Gohr    // endregion
580cf2dcf1bSAndreas Gohr}
581