xref: /dokuwiki/lib/plugins/extension/Extension.php (revision cf2dcf1b9ac1f331d4667a6c82d326f1a3e5d4c7)
1*cf2dcf1bSAndreas Gohr<?php
2*cf2dcf1bSAndreas Gohr
3*cf2dcf1bSAndreas Gohrnamespace dokuwiki\plugin\extension;
4*cf2dcf1bSAndreas Gohr
5*cf2dcf1bSAndreas Gohruse dokuwiki\Extension\PluginController;
6*cf2dcf1bSAndreas Gohruse dokuwiki\Utf8\PhpString;
7*cf2dcf1bSAndreas Gohruse RuntimeException;
8*cf2dcf1bSAndreas Gohr
9*cf2dcf1bSAndreas Gohrclass Extension
10*cf2dcf1bSAndreas Gohr{
11*cf2dcf1bSAndreas Gohr    const TYPE_PLUGIN = 'plugin';
12*cf2dcf1bSAndreas Gohr    const TYPE_TEMPLATE = 'template';
13*cf2dcf1bSAndreas Gohr
14*cf2dcf1bSAndreas Gohr    /** @var string "plugin"|"template" */
15*cf2dcf1bSAndreas Gohr    protected string $type = self::TYPE_PLUGIN;
16*cf2dcf1bSAndreas Gohr
17*cf2dcf1bSAndreas Gohr    /** @var string The base name of this extension */
18*cf2dcf1bSAndreas Gohr    protected string $base;
19*cf2dcf1bSAndreas Gohr
20*cf2dcf1bSAndreas Gohr    /** @var string|null The current location of this extension */
21*cf2dcf1bSAndreas Gohr    protected ?string $currentDir;
22*cf2dcf1bSAndreas Gohr
23*cf2dcf1bSAndreas Gohr    /** @var array The local info array of the extension */
24*cf2dcf1bSAndreas Gohr    protected array $localInfo = [];
25*cf2dcf1bSAndreas Gohr
26*cf2dcf1bSAndreas Gohr    /** @var array The remote info array of the extension */
27*cf2dcf1bSAndreas Gohr    protected array $remoteInfo = [];
28*cf2dcf1bSAndreas Gohr
29*cf2dcf1bSAndreas Gohr    /** @var array The manager info array of the extension */
30*cf2dcf1bSAndreas Gohr    protected array $managerInfo = [];
31*cf2dcf1bSAndreas Gohr
32*cf2dcf1bSAndreas Gohr    // region Constructors
33*cf2dcf1bSAndreas Gohr
34*cf2dcf1bSAndreas Gohr    /**
35*cf2dcf1bSAndreas Gohr     * The main constructor is private to force the use of the factory methods
36*cf2dcf1bSAndreas Gohr     */
37*cf2dcf1bSAndreas Gohr    protected function __construct()
38*cf2dcf1bSAndreas Gohr    {
39*cf2dcf1bSAndreas Gohr    }
40*cf2dcf1bSAndreas Gohr
41*cf2dcf1bSAndreas Gohr    /**
42*cf2dcf1bSAndreas Gohr     * Initializes an extension from a directory
43*cf2dcf1bSAndreas Gohr     *
44*cf2dcf1bSAndreas Gohr     * The given directory might be the one where the extension has already been installed to
45*cf2dcf1bSAndreas Gohr     * or it might be the extracted source in some temporary directory.
46*cf2dcf1bSAndreas Gohr     *
47*cf2dcf1bSAndreas Gohr     * @param string $dir Where the extension code is currently located
48*cf2dcf1bSAndreas Gohr     * @param string|null $type TYPE_PLUGIN|TYPE_TEMPLATE, null for auto-detection
49*cf2dcf1bSAndreas Gohr     * @param string $base The base name of the extension, null for auto-detection
50*cf2dcf1bSAndreas Gohr     * @return Extension
51*cf2dcf1bSAndreas Gohr     */
52*cf2dcf1bSAndreas Gohr    public static function createFromDirectory($dir, $type = null, $base = null)
53*cf2dcf1bSAndreas Gohr    {
54*cf2dcf1bSAndreas Gohr        $extension = new self();
55*cf2dcf1bSAndreas Gohr        $extension->initFromDirectory($dir, $type, $base);
56*cf2dcf1bSAndreas Gohr        return $extension;
57*cf2dcf1bSAndreas Gohr    }
58*cf2dcf1bSAndreas Gohr
59*cf2dcf1bSAndreas Gohr    protected function initFromDirectory($dir, $type = null, $base = null)
60*cf2dcf1bSAndreas Gohr    {
61*cf2dcf1bSAndreas Gohr        if (!is_dir($dir)) throw new RuntimeException('Directory not found: ' . $dir);
62*cf2dcf1bSAndreas Gohr        $this->currentDir = realpath($dir);
63*cf2dcf1bSAndreas Gohr
64*cf2dcf1bSAndreas Gohr        if ($type === null || $type === self::TYPE_TEMPLATE) {
65*cf2dcf1bSAndreas Gohr            if (
66*cf2dcf1bSAndreas Gohr                file_exists($dir . '/template.info.php') ||
67*cf2dcf1bSAndreas Gohr                file_exists($dir . '/style.ini') ||
68*cf2dcf1bSAndreas Gohr                file_exists($dir . '/main.php') ||
69*cf2dcf1bSAndreas Gohr                file_exists($dir . '/detail.php') ||
70*cf2dcf1bSAndreas Gohr                file_exists($dir . '/mediamanager.php')
71*cf2dcf1bSAndreas Gohr            ) {
72*cf2dcf1bSAndreas Gohr                $this->type = self::TYPE_TEMPLATE;
73*cf2dcf1bSAndreas Gohr            }
74*cf2dcf1bSAndreas Gohr        } else {
75*cf2dcf1bSAndreas Gohr            $this->type = self::TYPE_PLUGIN;
76*cf2dcf1bSAndreas Gohr        }
77*cf2dcf1bSAndreas Gohr
78*cf2dcf1bSAndreas Gohr        $this->readLocalInfo();
79*cf2dcf1bSAndreas Gohr
80*cf2dcf1bSAndreas Gohr        if ($base !== null) {
81*cf2dcf1bSAndreas Gohr            $this->base = $base;
82*cf2dcf1bSAndreas Gohr        } elseif (isset($this->localInfo['base'])) {
83*cf2dcf1bSAndreas Gohr            $this->base = $this->localInfo['base'];
84*cf2dcf1bSAndreas Gohr        } else {
85*cf2dcf1bSAndreas Gohr            $this->base = basename($dir);
86*cf2dcf1bSAndreas Gohr        }
87*cf2dcf1bSAndreas Gohr    }
88*cf2dcf1bSAndreas Gohr
89*cf2dcf1bSAndreas Gohr    /**
90*cf2dcf1bSAndreas Gohr     * Initializes an extension from remote data
91*cf2dcf1bSAndreas Gohr     *
92*cf2dcf1bSAndreas Gohr     * @param array $data The data as returned by the repository api
93*cf2dcf1bSAndreas Gohr     * @return Extension
94*cf2dcf1bSAndreas Gohr     */
95*cf2dcf1bSAndreas Gohr    public static function createFromRemoteData($data)
96*cf2dcf1bSAndreas Gohr    {
97*cf2dcf1bSAndreas Gohr        $extension = new self();
98*cf2dcf1bSAndreas Gohr        $extension->initFromRemoteData($data);
99*cf2dcf1bSAndreas Gohr        return $extension;
100*cf2dcf1bSAndreas Gohr    }
101*cf2dcf1bSAndreas Gohr
102*cf2dcf1bSAndreas Gohr    protected function initFromRemoteData($data)
103*cf2dcf1bSAndreas Gohr    {
104*cf2dcf1bSAndreas Gohr        if (!isset($data['plugin'])) throw new RuntimeException('Invalid remote data');
105*cf2dcf1bSAndreas Gohr
106*cf2dcf1bSAndreas Gohr        [$type, $base] = sexplode(':', $data['plugin'], 2);
107*cf2dcf1bSAndreas Gohr        if ($base === null) {
108*cf2dcf1bSAndreas Gohr            $base = $type;
109*cf2dcf1bSAndreas Gohr            $type = self::TYPE_PLUGIN;
110*cf2dcf1bSAndreas Gohr        } else {
111*cf2dcf1bSAndreas Gohr            $type = self::TYPE_TEMPLATE;
112*cf2dcf1bSAndreas Gohr        }
113*cf2dcf1bSAndreas Gohr
114*cf2dcf1bSAndreas Gohr        $this->remoteInfo = $data;
115*cf2dcf1bSAndreas Gohr        $this->type = $type;
116*cf2dcf1bSAndreas Gohr        $this->base = $base;
117*cf2dcf1bSAndreas Gohr
118*cf2dcf1bSAndreas Gohr        if ($this->isInstalled()) {
119*cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
120*cf2dcf1bSAndreas Gohr            $this->readLocalInfo();
121*cf2dcf1bSAndreas Gohr        }
122*cf2dcf1bSAndreas Gohr    }
123*cf2dcf1bSAndreas Gohr
124*cf2dcf1bSAndreas Gohr    // endregion
125*cf2dcf1bSAndreas Gohr
126*cf2dcf1bSAndreas Gohr    // region Getters
127*cf2dcf1bSAndreas Gohr
128*cf2dcf1bSAndreas Gohr    /**
129*cf2dcf1bSAndreas Gohr     * @return string The extension id (same as base but prefixed with "template:" for templates)
130*cf2dcf1bSAndreas Gohr     */
131*cf2dcf1bSAndreas Gohr    public function getId()
132*cf2dcf1bSAndreas Gohr    {
133*cf2dcf1bSAndreas Gohr        if ($this->type === self::TYPE_TEMPLATE) {
134*cf2dcf1bSAndreas Gohr            return self::TYPE_TEMPLATE . ':' . $this->base;
135*cf2dcf1bSAndreas Gohr        }
136*cf2dcf1bSAndreas Gohr        return $this->base;
137*cf2dcf1bSAndreas Gohr    }
138*cf2dcf1bSAndreas Gohr
139*cf2dcf1bSAndreas Gohr    /**
140*cf2dcf1bSAndreas Gohr     * Get the base name of this extension
141*cf2dcf1bSAndreas Gohr     *
142*cf2dcf1bSAndreas Gohr     * @return string
143*cf2dcf1bSAndreas Gohr     */
144*cf2dcf1bSAndreas Gohr    public function getBase()
145*cf2dcf1bSAndreas Gohr    {
146*cf2dcf1bSAndreas Gohr        return $this->base;
147*cf2dcf1bSAndreas Gohr    }
148*cf2dcf1bSAndreas Gohr
149*cf2dcf1bSAndreas Gohr    /**
150*cf2dcf1bSAndreas Gohr     * Get the type of the extension
151*cf2dcf1bSAndreas Gohr     *
152*cf2dcf1bSAndreas Gohr     * @return string "plugin"|"template"
153*cf2dcf1bSAndreas Gohr     */
154*cf2dcf1bSAndreas Gohr    public function getType()
155*cf2dcf1bSAndreas Gohr    {
156*cf2dcf1bSAndreas Gohr        return $this->type;
157*cf2dcf1bSAndreas Gohr    }
158*cf2dcf1bSAndreas Gohr
159*cf2dcf1bSAndreas Gohr    /**
160*cf2dcf1bSAndreas Gohr     * The current directory of the extension
161*cf2dcf1bSAndreas Gohr     *
162*cf2dcf1bSAndreas Gohr     * @return string|null
163*cf2dcf1bSAndreas Gohr     */
164*cf2dcf1bSAndreas Gohr    public function getCurrentDir()
165*cf2dcf1bSAndreas Gohr    {
166*cf2dcf1bSAndreas Gohr        // recheck that the current currentDir is still valid
167*cf2dcf1bSAndreas Gohr        if ($this->currentDir && !is_dir($this->currentDir)) {
168*cf2dcf1bSAndreas Gohr            $this->currentDir = null;
169*cf2dcf1bSAndreas Gohr        }
170*cf2dcf1bSAndreas Gohr
171*cf2dcf1bSAndreas Gohr        // if the extension is installed, then the currentDir is the install dir!
172*cf2dcf1bSAndreas Gohr        if (!$this->currentDir && $this->isInstalled()) {
173*cf2dcf1bSAndreas Gohr            $this->currentDir = $this->getInstallDir();
174*cf2dcf1bSAndreas Gohr        }
175*cf2dcf1bSAndreas Gohr
176*cf2dcf1bSAndreas Gohr        return $this->currentDir;
177*cf2dcf1bSAndreas Gohr    }
178*cf2dcf1bSAndreas Gohr
179*cf2dcf1bSAndreas Gohr    /**
180*cf2dcf1bSAndreas Gohr     * Get the directory where this extension should be installed in
181*cf2dcf1bSAndreas Gohr     *
182*cf2dcf1bSAndreas Gohr     * Note: this does not mean that the extension is actually installed there
183*cf2dcf1bSAndreas Gohr     *
184*cf2dcf1bSAndreas Gohr     * @return string
185*cf2dcf1bSAndreas Gohr     */
186*cf2dcf1bSAndreas Gohr    public function getInstallDir()
187*cf2dcf1bSAndreas Gohr    {
188*cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
189*cf2dcf1bSAndreas Gohr            $dir = dirname(tpl_incdir()) . $this->base;
190*cf2dcf1bSAndreas Gohr        } else {
191*cf2dcf1bSAndreas Gohr            $dir = DOKU_PLUGIN . $this->base;
192*cf2dcf1bSAndreas Gohr        }
193*cf2dcf1bSAndreas Gohr
194*cf2dcf1bSAndreas Gohr        return realpath($dir);
195*cf2dcf1bSAndreas Gohr    }
196*cf2dcf1bSAndreas Gohr
197*cf2dcf1bSAndreas Gohr
198*cf2dcf1bSAndreas Gohr    /**
199*cf2dcf1bSAndreas Gohr     * Get the display name of the extension
200*cf2dcf1bSAndreas Gohr     *
201*cf2dcf1bSAndreas Gohr     * @return string
202*cf2dcf1bSAndreas Gohr     */
203*cf2dcf1bSAndreas Gohr    public function getDisplayName()
204*cf2dcf1bSAndreas Gohr    {
205*cf2dcf1bSAndreas Gohr        return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType()));
206*cf2dcf1bSAndreas Gohr    }
207*cf2dcf1bSAndreas Gohr
208*cf2dcf1bSAndreas Gohr    /**
209*cf2dcf1bSAndreas Gohr     * Get the author name of the extension
210*cf2dcf1bSAndreas Gohr     *
211*cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the author info is missing
212*cf2dcf1bSAndreas Gohr     */
213*cf2dcf1bSAndreas Gohr    public function getAuthor()
214*cf2dcf1bSAndreas Gohr    {
215*cf2dcf1bSAndreas Gohr        return $this->getTag('author');
216*cf2dcf1bSAndreas Gohr    }
217*cf2dcf1bSAndreas Gohr
218*cf2dcf1bSAndreas Gohr    /**
219*cf2dcf1bSAndreas Gohr     * Get the email of the author of the extension if there is any
220*cf2dcf1bSAndreas Gohr     *
221*cf2dcf1bSAndreas Gohr     * @return string Returns an empty string if the email info is missing
222*cf2dcf1bSAndreas Gohr     */
223*cf2dcf1bSAndreas Gohr    public function getEmail()
224*cf2dcf1bSAndreas Gohr    {
225*cf2dcf1bSAndreas Gohr        // email is only in the local data
226*cf2dcf1bSAndreas Gohr        return $this->localInfo['email'] ?? '';
227*cf2dcf1bSAndreas Gohr    }
228*cf2dcf1bSAndreas Gohr
229*cf2dcf1bSAndreas Gohr    /**
230*cf2dcf1bSAndreas Gohr     * Get the email id, i.e. the md5sum of the email
231*cf2dcf1bSAndreas Gohr     *
232*cf2dcf1bSAndreas Gohr     * @return string Empty string if no email is available
233*cf2dcf1bSAndreas Gohr     */
234*cf2dcf1bSAndreas Gohr    public function getEmailID()
235*cf2dcf1bSAndreas Gohr    {
236*cf2dcf1bSAndreas Gohr        if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid'];
237*cf2dcf1bSAndreas Gohr        if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']);
238*cf2dcf1bSAndreas Gohr        return '';
239*cf2dcf1bSAndreas Gohr    }
240*cf2dcf1bSAndreas Gohr
241*cf2dcf1bSAndreas Gohr    /**
242*cf2dcf1bSAndreas Gohr     * Get the description of the extension
243*cf2dcf1bSAndreas Gohr     *
244*cf2dcf1bSAndreas Gohr     * @return string Empty string if no description is available
245*cf2dcf1bSAndreas Gohr     */
246*cf2dcf1bSAndreas Gohr    public function getDescription()
247*cf2dcf1bSAndreas Gohr    {
248*cf2dcf1bSAndreas Gohr        return $this->getTag(['desc', 'description']);
249*cf2dcf1bSAndreas Gohr    }
250*cf2dcf1bSAndreas Gohr
251*cf2dcf1bSAndreas Gohr    /**
252*cf2dcf1bSAndreas Gohr     * Get the URL of the extension, usually a page on dokuwiki.org
253*cf2dcf1bSAndreas Gohr     *
254*cf2dcf1bSAndreas Gohr     * @return string
255*cf2dcf1bSAndreas Gohr     */
256*cf2dcf1bSAndreas Gohr    public function getURL()
257*cf2dcf1bSAndreas Gohr    {
258*cf2dcf1bSAndreas Gohr        return $this->getTag(
259*cf2dcf1bSAndreas Gohr            'url',
260*cf2dcf1bSAndreas Gohr            'https://www.dokuwiki.org/' .
261*cf2dcf1bSAndreas Gohr            ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase()
262*cf2dcf1bSAndreas Gohr        );
263*cf2dcf1bSAndreas Gohr    }
264*cf2dcf1bSAndreas Gohr
265*cf2dcf1bSAndreas Gohr    /**
266*cf2dcf1bSAndreas Gohr     * Is this extension a template?
267*cf2dcf1bSAndreas Gohr     *
268*cf2dcf1bSAndreas Gohr     * @return bool false if it is a plugin
269*cf2dcf1bSAndreas Gohr     */
270*cf2dcf1bSAndreas Gohr    public function isTemplate()
271*cf2dcf1bSAndreas Gohr    {
272*cf2dcf1bSAndreas Gohr        return $this->type === self::TYPE_TEMPLATE;
273*cf2dcf1bSAndreas Gohr    }
274*cf2dcf1bSAndreas Gohr
275*cf2dcf1bSAndreas Gohr    /**
276*cf2dcf1bSAndreas Gohr     * Is the extension installed locally?
277*cf2dcf1bSAndreas Gohr     *
278*cf2dcf1bSAndreas Gohr     * @return bool
279*cf2dcf1bSAndreas Gohr     */
280*cf2dcf1bSAndreas Gohr    public function isInstalled()
281*cf2dcf1bSAndreas Gohr    {
282*cf2dcf1bSAndreas Gohr        return is_dir($this->getInstallDir());
283*cf2dcf1bSAndreas Gohr    }
284*cf2dcf1bSAndreas Gohr
285*cf2dcf1bSAndreas Gohr    /**
286*cf2dcf1bSAndreas Gohr     * Is the extension under git control?
287*cf2dcf1bSAndreas Gohr     *
288*cf2dcf1bSAndreas Gohr     * @return bool
289*cf2dcf1bSAndreas Gohr     */
290*cf2dcf1bSAndreas Gohr    public function isGitControlled()
291*cf2dcf1bSAndreas Gohr    {
292*cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) return false;
293*cf2dcf1bSAndreas Gohr        return file_exists($this->getInstallDir() . '/.git');
294*cf2dcf1bSAndreas Gohr    }
295*cf2dcf1bSAndreas Gohr
296*cf2dcf1bSAndreas Gohr    /**
297*cf2dcf1bSAndreas Gohr     * If the extension is bundled
298*cf2dcf1bSAndreas Gohr     *
299*cf2dcf1bSAndreas Gohr     * @return bool If the extension is bundled
300*cf2dcf1bSAndreas Gohr     */
301*cf2dcf1bSAndreas Gohr    public function isBundled()
302*cf2dcf1bSAndreas Gohr    {
303*cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
304*cf2dcf1bSAndreas Gohr        return $this->remoteInfo['bundled'] ?? in_array(
305*cf2dcf1bSAndreas Gohr            $this->getId(),
306*cf2dcf1bSAndreas Gohr            [
307*cf2dcf1bSAndreas Gohr                'authad',
308*cf2dcf1bSAndreas Gohr                'authldap',
309*cf2dcf1bSAndreas Gohr                'authpdo',
310*cf2dcf1bSAndreas Gohr                'authplain',
311*cf2dcf1bSAndreas Gohr                'acl',
312*cf2dcf1bSAndreas Gohr                'config',
313*cf2dcf1bSAndreas Gohr                'extension',
314*cf2dcf1bSAndreas Gohr                'info',
315*cf2dcf1bSAndreas Gohr                'popularity',
316*cf2dcf1bSAndreas Gohr                'revert',
317*cf2dcf1bSAndreas Gohr                'safefnrecode',
318*cf2dcf1bSAndreas Gohr                'styling',
319*cf2dcf1bSAndreas Gohr                'testing',
320*cf2dcf1bSAndreas Gohr                'usermanager',
321*cf2dcf1bSAndreas Gohr                'logviewer',
322*cf2dcf1bSAndreas Gohr                'template:dokuwiki'
323*cf2dcf1bSAndreas Gohr            ]
324*cf2dcf1bSAndreas Gohr        );
325*cf2dcf1bSAndreas Gohr    }
326*cf2dcf1bSAndreas Gohr
327*cf2dcf1bSAndreas Gohr    /**
328*cf2dcf1bSAndreas Gohr     * Is the extension protected against any modification (disable/uninstall)
329*cf2dcf1bSAndreas Gohr     *
330*cf2dcf1bSAndreas Gohr     * @return bool if the extension is protected
331*cf2dcf1bSAndreas Gohr     */
332*cf2dcf1bSAndreas Gohr    public function isProtected()
333*cf2dcf1bSAndreas Gohr    {
334*cf2dcf1bSAndreas Gohr        // never allow deinstalling the current auth plugin:
335*cf2dcf1bSAndreas Gohr        global $conf;
336*cf2dcf1bSAndreas Gohr        if ($this->getId() == $conf['authtype']) return true;
337*cf2dcf1bSAndreas Gohr
338*cf2dcf1bSAndreas Gohr        // FIXME disallow current template to be uninstalled
339*cf2dcf1bSAndreas Gohr
340*cf2dcf1bSAndreas Gohr        /** @var PluginController $plugin_controller */
341*cf2dcf1bSAndreas Gohr        global $plugin_controller;
342*cf2dcf1bSAndreas Gohr        $cascade = $plugin_controller->getCascade();
343*cf2dcf1bSAndreas Gohr        return ($cascade['protected'][$this->getId()] ?? false);
344*cf2dcf1bSAndreas Gohr    }
345*cf2dcf1bSAndreas Gohr
346*cf2dcf1bSAndreas Gohr    /**
347*cf2dcf1bSAndreas Gohr     * Is the extension installed in the correct directory?
348*cf2dcf1bSAndreas Gohr     *
349*cf2dcf1bSAndreas Gohr     * @return bool
350*cf2dcf1bSAndreas Gohr     */
351*cf2dcf1bSAndreas Gohr    public function isInWrongFolder()
352*cf2dcf1bSAndreas Gohr    {
353*cf2dcf1bSAndreas Gohr        return $this->getInstallDir() != $this->currentDir;
354*cf2dcf1bSAndreas Gohr    }
355*cf2dcf1bSAndreas Gohr
356*cf2dcf1bSAndreas Gohr    /**
357*cf2dcf1bSAndreas Gohr     * Is the extension enabled?
358*cf2dcf1bSAndreas Gohr     *
359*cf2dcf1bSAndreas Gohr     * @return bool
360*cf2dcf1bSAndreas Gohr     */
361*cf2dcf1bSAndreas Gohr    public function isEnabled()
362*cf2dcf1bSAndreas Gohr    {
363*cf2dcf1bSAndreas Gohr        global $conf;
364*cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) {
365*cf2dcf1bSAndreas Gohr            return ($conf['template'] == $this->getBase());
366*cf2dcf1bSAndreas Gohr        }
367*cf2dcf1bSAndreas Gohr
368*cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
369*cf2dcf1bSAndreas Gohr        global $plugin_controller;
370*cf2dcf1bSAndreas Gohr        return $plugin_controller->isEnabled($this->base);
371*cf2dcf1bSAndreas Gohr    }
372*cf2dcf1bSAndreas Gohr
373*cf2dcf1bSAndreas Gohr    // endregion
374*cf2dcf1bSAndreas Gohr
375*cf2dcf1bSAndreas Gohr    // region Actions
376*cf2dcf1bSAndreas Gohr
377*cf2dcf1bSAndreas Gohr    /**
378*cf2dcf1bSAndreas Gohr     * Install or update the extension
379*cf2dcf1bSAndreas Gohr     *
380*cf2dcf1bSAndreas Gohr     * @throws Exception
381*cf2dcf1bSAndreas Gohr     */
382*cf2dcf1bSAndreas Gohr    public function installOrUpdate()
383*cf2dcf1bSAndreas Gohr    {
384*cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
385*cf2dcf1bSAndreas Gohr        $installer->installFromUrl(
386*cf2dcf1bSAndreas Gohr            $this->getURL(),
387*cf2dcf1bSAndreas Gohr            $this->getBase(),
388*cf2dcf1bSAndreas Gohr        );
389*cf2dcf1bSAndreas Gohr    }
390*cf2dcf1bSAndreas Gohr
391*cf2dcf1bSAndreas Gohr    /**
392*cf2dcf1bSAndreas Gohr     * Uninstall the extension
393*cf2dcf1bSAndreas Gohr     * @throws Exception
394*cf2dcf1bSAndreas Gohr     */
395*cf2dcf1bSAndreas Gohr    public function uninstall()
396*cf2dcf1bSAndreas Gohr    {
397*cf2dcf1bSAndreas Gohr        $installer = new Installer(true);
398*cf2dcf1bSAndreas Gohr        $installer->uninstall($this);
399*cf2dcf1bSAndreas Gohr    }
400*cf2dcf1bSAndreas Gohr
401*cf2dcf1bSAndreas Gohr    /**
402*cf2dcf1bSAndreas Gohr     * Enable the extension
403*cf2dcf1bSAndreas Gohr     * @todo I'm unsure if this code should be here or part of Installer
404*cf2dcf1bSAndreas Gohr     * @throws Exception
405*cf2dcf1bSAndreas Gohr     */
406*cf2dcf1bSAndreas Gohr    public function enable()
407*cf2dcf1bSAndreas Gohr    {
408*cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) throw new Exception('notimplemented');
409*cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) throw new Exception('notinstalled');
410*cf2dcf1bSAndreas Gohr        if ($this->isEnabled()) throw new Exception('alreadyenabled');
411*cf2dcf1bSAndreas Gohr
412*cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
413*cf2dcf1bSAndreas Gohr        global $plugin_controller;
414*cf2dcf1bSAndreas Gohr        if (!$plugin_controller->enable($this->base)) {
415*cf2dcf1bSAndreas Gohr            throw new Exception('pluginlistsaveerror');
416*cf2dcf1bSAndreas Gohr        }
417*cf2dcf1bSAndreas Gohr        Installer::purgeCache();
418*cf2dcf1bSAndreas Gohr    }
419*cf2dcf1bSAndreas Gohr
420*cf2dcf1bSAndreas Gohr    /**
421*cf2dcf1bSAndreas Gohr     * Disable the extension
422*cf2dcf1bSAndreas Gohr     * @todo I'm unsure if this code should be here or part of Installer
423*cf2dcf1bSAndreas Gohr     * @throws Exception
424*cf2dcf1bSAndreas Gohr     */
425*cf2dcf1bSAndreas Gohr    public function disable()
426*cf2dcf1bSAndreas Gohr    {
427*cf2dcf1bSAndreas Gohr        if ($this->isTemplate()) throw new Exception('notimplemented');
428*cf2dcf1bSAndreas Gohr        if (!$this->isInstalled()) throw new Exception('notinstalled');
429*cf2dcf1bSAndreas Gohr        if (!$this->isEnabled()) throw new Exception('alreadydisabled');
430*cf2dcf1bSAndreas Gohr        if ($this->isProtected()) throw new Exception('error_disable_protected');
431*cf2dcf1bSAndreas Gohr
432*cf2dcf1bSAndreas Gohr        /* @var PluginController $plugin_controller */
433*cf2dcf1bSAndreas Gohr        global $plugin_controller;
434*cf2dcf1bSAndreas Gohr        if (!$plugin_controller->disable($this->base)) {
435*cf2dcf1bSAndreas Gohr            throw new Exception('pluginlistsaveerror');
436*cf2dcf1bSAndreas Gohr        }
437*cf2dcf1bSAndreas Gohr        Installer::purgeCache();
438*cf2dcf1bSAndreas Gohr    }
439*cf2dcf1bSAndreas Gohr
440*cf2dcf1bSAndreas Gohr    // endregion
441*cf2dcf1bSAndreas Gohr
442*cf2dcf1bSAndreas Gohr    // region Meta Data Management
443*cf2dcf1bSAndreas Gohr
444*cf2dcf1bSAndreas Gohr    /**
445*cf2dcf1bSAndreas Gohr     * This updates the timestamp and URL in the manager.dat file
446*cf2dcf1bSAndreas Gohr     *
447*cf2dcf1bSAndreas Gohr     * It is called by Installer when installing or updating an extension
448*cf2dcf1bSAndreas Gohr     *
449*cf2dcf1bSAndreas Gohr     * @param $url
450*cf2dcf1bSAndreas Gohr     */
451*cf2dcf1bSAndreas Gohr    public function updateManagerInfo($url)
452*cf2dcf1bSAndreas Gohr    {
453*cf2dcf1bSAndreas Gohr        $this->managerInfo['downloadurl'] = $url;
454*cf2dcf1bSAndreas Gohr        if (isset($this->managerInfo['installed'])) {
455*cf2dcf1bSAndreas Gohr            // it's an update
456*cf2dcf1bSAndreas Gohr            $this->managerInfo['updated'] = date('r');
457*cf2dcf1bSAndreas Gohr        } else {
458*cf2dcf1bSAndreas Gohr            // it's a new install
459*cf2dcf1bSAndreas Gohr            $this->managerInfo['installed'] = date('r');
460*cf2dcf1bSAndreas Gohr        }
461*cf2dcf1bSAndreas Gohr
462*cf2dcf1bSAndreas Gohr        $managerpath = $this->getInstallDir() . '/manager.dat';
463*cf2dcf1bSAndreas Gohr        $data = '';
464*cf2dcf1bSAndreas Gohr        foreach ($this->managerInfo as $k => $v) {
465*cf2dcf1bSAndreas Gohr            $data .= $k . '=' . $v . DOKU_LF;
466*cf2dcf1bSAndreas Gohr        }
467*cf2dcf1bSAndreas Gohr        io_saveFile($managerpath, $data);
468*cf2dcf1bSAndreas Gohr    }
469*cf2dcf1bSAndreas Gohr
470*cf2dcf1bSAndreas Gohr    /**
471*cf2dcf1bSAndreas Gohr     * Reads the manager.dat file and fills the managerInfo array
472*cf2dcf1bSAndreas Gohr     */
473*cf2dcf1bSAndreas Gohr    protected function readManagerInfo()
474*cf2dcf1bSAndreas Gohr    {
475*cf2dcf1bSAndreas Gohr        if ($this->managerInfo) return;
476*cf2dcf1bSAndreas Gohr
477*cf2dcf1bSAndreas Gohr        $managerpath = $this->getInstallDir() . '/manager.dat';
478*cf2dcf1bSAndreas Gohr        if (!is_readable($managerpath)) return;
479*cf2dcf1bSAndreas Gohr
480*cf2dcf1bSAndreas Gohr        $file = (array)@file($managerpath);
481*cf2dcf1bSAndreas Gohr        foreach ($file as $line) {
482*cf2dcf1bSAndreas Gohr            [$key, $value] = sexplode('=', $line, 2, '');
483*cf2dcf1bSAndreas Gohr            $key = trim($key);
484*cf2dcf1bSAndreas Gohr            $value = trim($value);
485*cf2dcf1bSAndreas Gohr            // backwards compatible with old plugin manager
486*cf2dcf1bSAndreas Gohr            if ($key == 'url') $key = 'downloadurl';
487*cf2dcf1bSAndreas Gohr            $this->managerInfo[$key] = $value;
488*cf2dcf1bSAndreas Gohr        }
489*cf2dcf1bSAndreas Gohr    }
490*cf2dcf1bSAndreas Gohr
491*cf2dcf1bSAndreas Gohr    /**
492*cf2dcf1bSAndreas Gohr     * Reads the info file of the extension if available and fills the localInfo array
493*cf2dcf1bSAndreas Gohr     */
494*cf2dcf1bSAndreas Gohr    protected function readLocalInfo()
495*cf2dcf1bSAndreas Gohr    {
496*cf2dcf1bSAndreas Gohr        if (!$this->currentDir) return;
497*cf2dcf1bSAndreas Gohr        $file = $this->currentDir . '/' . $this->type . '.info.txt';
498*cf2dcf1bSAndreas Gohr        if (!is_readable($file)) return;
499*cf2dcf1bSAndreas Gohr        $this->localInfo = confToHash($file, true);
500*cf2dcf1bSAndreas Gohr        $this->localInfo = array_filter($this->localInfo); // remove all falsy keys
501*cf2dcf1bSAndreas Gohr    }
502*cf2dcf1bSAndreas Gohr
503*cf2dcf1bSAndreas Gohr    /**
504*cf2dcf1bSAndreas Gohr     * Fetches the remote info from the repository
505*cf2dcf1bSAndreas Gohr     *
506*cf2dcf1bSAndreas Gohr     * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case
507*cf2dcf1bSAndreas Gohr     */
508*cf2dcf1bSAndreas Gohr    protected function loadRemoteInfo()
509*cf2dcf1bSAndreas Gohr    {
510*cf2dcf1bSAndreas Gohr        if ($this->remoteInfo) return;
511*cf2dcf1bSAndreas Gohr        $remote = Repository::getInstance();
512*cf2dcf1bSAndreas Gohr        try {
513*cf2dcf1bSAndreas Gohr            $this->remoteInfo = (array)$remote->getExtensionData($this->getId());
514*cf2dcf1bSAndreas Gohr        } catch (Exception $e) {
515*cf2dcf1bSAndreas Gohr            $this->remoteInfo = [];
516*cf2dcf1bSAndreas Gohr        }
517*cf2dcf1bSAndreas Gohr    }
518*cf2dcf1bSAndreas Gohr
519*cf2dcf1bSAndreas Gohr    /**
520*cf2dcf1bSAndreas Gohr     * Read information from either local or remote info
521*cf2dcf1bSAndreas Gohr     *
522*cf2dcf1bSAndreas Gohr     * Always prefers local info over remote info
523*cf2dcf1bSAndreas Gohr     *
524*cf2dcf1bSAndreas Gohr     * @param string|string[] $tag one or multiple keys to check
525*cf2dcf1bSAndreas Gohr     * @param mixed $default
526*cf2dcf1bSAndreas Gohr     * @return mixed
527*cf2dcf1bSAndreas Gohr     */
528*cf2dcf1bSAndreas Gohr    protected function getTag($tag, $default = '')
529*cf2dcf1bSAndreas Gohr    {
530*cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
531*cf2dcf1bSAndreas Gohr            if (isset($this->localInfo[$t])) return $this->localInfo[$t];
532*cf2dcf1bSAndreas Gohr        }
533*cf2dcf1bSAndreas Gohr        $this->loadRemoteInfo();
534*cf2dcf1bSAndreas Gohr        foreach ((array)$tag as $t) {
535*cf2dcf1bSAndreas Gohr            if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t];
536*cf2dcf1bSAndreas Gohr        }
537*cf2dcf1bSAndreas Gohr
538*cf2dcf1bSAndreas Gohr        return $default;
539*cf2dcf1bSAndreas Gohr    }
540*cf2dcf1bSAndreas Gohr
541*cf2dcf1bSAndreas Gohr    // endregion
542*cf2dcf1bSAndreas Gohr}
543