xref: /dokuwiki/inc/Extension/PluginTrait.php (revision 884caed926ca0aa0af6ce3f34ae3aa7317a3361a)
1<?php
2
3namespace dokuwiki\Extension;
4
5use dokuwiki\Logger;
6use dokuwiki\MailUtils;
7
8/**
9 * Provides standard DokuWiki plugin behaviour
10 */
11trait PluginTrait
12{
13    protected $localised = false;        // set to true by setupLocale() after loading language dependent strings
14    protected $lang = [];           // array to hold language dependent strings, best accessed via ->getLang()
15    protected $configloaded = false;     // set to true by loadConfig() after loading plugin configuration variables
16    protected $conf = [];           // array to hold plugin settings, best accessed via ->getConf()
17
18    /**
19     * @see PluginInterface::getInfo()
20     */
21    public function getInfo()
22    {
23        $class = $this::class;
24        $parts = sexplode('_', $class, 3);
25        $ext = $parts[2];
26
27        if (empty($ext)) {
28            throw new \RuntimeException('Class does not follow the plugin naming convention');
29        }
30
31        // class like action_plugin_myplugin_ajax belongs to plugin 'myplugin'
32        $ext = strtok($ext, '_');
33
34        $base = [
35            'base' => $ext,
36            'author' => 'Unknown',
37            'email' => 'unknown@example.com',
38            'date' => '0000-00-00',
39            'name' => $ext . ' plugin',
40            'desc' => 'Unknown purpose - bad plugin.info.txt',
41            'url' => 'https://www.dokuwiki.org/plugins/' . $ext,
42        ];
43
44        $file = DOKU_PLUGIN . '/' . $ext . '/plugin.info.txt';
45        if (file_exists($file)) {
46            $raw = confToHash($file);
47
48            // check if all required fields are present
49            $msg = 'Extension %s does not provide a valid %s in %s';
50            foreach (array_keys($base) as $line) {
51                if (empty($raw[$line])) Logger::error(sprintf($msg, $ext, $line, $file));
52            }
53
54            return array_merge($base, $raw);
55        }
56
57        Logger::error(sprintf('Extension %s does not provide a plugin.info.txt in %s', $ext, $file));
58        return $base;
59    }
60
61    /**
62     * @see PluginInterface::isSingleton()
63     */
64    public function isSingleton()
65    {
66        return true;
67    }
68
69    /**
70     * @see PluginInterface::loadHelper()
71     */
72    public function loadHelper($name, $msg = true)
73    {
74        $obj = plugin_load('helper', $name);
75        if (is_null($obj) && $msg) msg("Helper plugin $name is not available or invalid.", -1);
76        return $obj;
77    }
78
79    // region introspection methods
80
81    /**
82     * @see PluginInterface::getPluginType()
83     */
84    public function getPluginType()
85    {
86        [$t] = explode('_', $this::class, 2);
87        return $t;
88    }
89
90    /**
91     * @see PluginInterface::getPluginName()
92     */
93    public function getPluginName()
94    {
95        [/* t */, /* p */, $n] = sexplode('_', $this::class, 4, '');
96        return $n;
97    }
98
99    /**
100     * @see PluginInterface::getPluginComponent()
101     */
102    public function getPluginComponent()
103    {
104        [/* t */, /* p */, /* n */, $c] = sexplode('_', $this::class, 4, '');
105        return $c;
106    }
107
108    // endregion
109    // region localization methods
110
111    /**
112     * @see PluginInterface::getLang()
113     */
114    public function getLang($id)
115    {
116        if (!$this->localised) $this->setupLocale();
117
118        return ($this->lang[$id] ?? '');
119    }
120
121    /**
122     * @see PluginInterface::locale_xhtml()
123     */
124    public function locale_xhtml($id)
125    {
126        // plugin-bundled locale files are authored in DokuWiki syntax; render
127        // them as 'dw' regardless of the configured wiki syntax preference
128        return p_cached_output($this->localFN($id), 'xhtml', '', 'dw');
129    }
130
131    /**
132     * @see PluginInterface::localFN()
133     */
134    public function localFN($id, $ext = 'txt')
135    {
136        global $conf;
137        $plugin = $this->getPluginName();
138        $file = DOKU_CONF . 'plugin_lang/' . $plugin . '/' . $conf['lang'] . '/' . $id . '.' . $ext;
139        if (!file_exists($file)) {
140            $file = DOKU_PLUGIN . $plugin . '/lang/' . $conf['lang'] . '/' . $id . '.' . $ext;
141            if (!file_exists($file)) {
142                //fall back to english
143                $file = DOKU_PLUGIN . $plugin . '/lang/en/' . $id . '.' . $ext;
144            }
145        }
146        return $file;
147    }
148
149    /**
150     * @see PluginInterface::setupLocale()
151     */
152    public function setupLocale()
153    {
154        if ($this->localised) return;
155
156        global $conf, $config_cascade; // definitely don't invoke "global $lang"
157        $path = DOKU_PLUGIN . $this->getPluginName() . '/lang/';
158
159        $lang = [];
160
161        // don't include once, in case several plugin components require the same language file
162        @include($path . 'en/lang.php');
163        foreach ($config_cascade['lang']['plugin'] as $config_file) {
164            if (file_exists($config_file . $this->getPluginName() . '/en/lang.php')) {
165                include($config_file . $this->getPluginName() . '/en/lang.php');
166            }
167        }
168
169        if ($conf['lang'] != 'en') {
170            @include($path . $conf['lang'] . '/lang.php');
171            foreach ($config_cascade['lang']['plugin'] as $config_file) {
172                if (file_exists($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php')) {
173                    include($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php');
174                }
175            }
176        }
177
178        $this->lang = $lang;
179        $this->localised = true;
180    }
181
182    // endregion
183    // region configuration methods
184
185    /**
186     * @see PluginInterface::getConf()
187     */
188    public function getConf($setting, $notset = false)
189    {
190
191        if (!$this->configloaded) {
192            $this->loadConfig();
193        }
194
195        if (isset($this->conf[$setting])) {
196            return $this->conf[$setting];
197        } else {
198            return $notset;
199        }
200    }
201
202    /**
203     * @see PluginInterface::loadConfig()
204     */
205    public function loadConfig()
206    {
207        global $conf;
208
209        $defaults = $this->readDefaultSettings();
210        $plugin = $this->getPluginName();
211
212        foreach ($defaults as $key => $value) {
213            if (isset($conf['plugin'][$plugin][$key])) continue;
214            $conf['plugin'][$plugin][$key] = $value;
215        }
216
217        $this->configloaded = true;
218        $this->conf =& $conf['plugin'][$plugin];
219    }
220
221    /**
222     * read the plugin's default configuration settings from conf/default.php
223     * this function is automatically called through getConf()
224     *
225     * @return    array    setting => value
226     */
227    protected function readDefaultSettings()
228    {
229
230        $path = DOKU_PLUGIN . $this->getPluginName() . '/conf/';
231        $conf = [];
232
233        if (file_exists($path . 'default.php')) {
234            include($path . 'default.php');
235        }
236
237        return $conf;
238    }
239
240    // endregion
241    // region output methods
242
243    /**
244     * @see PluginInterface::email()
245     */
246    public function email($email, $name = '', $class = '', $more = '')
247    {
248        if (!$email) return $name;
249        $display = MailUtils::obfuscate($email);
250        $href = MailUtils::obfuscateUrl($email);
251        if (!$name) $name = $display;
252        $class = "class='" . ($class ?: 'mail') . "'";
253        return "<a href='mailto:$href' $class title='$display' $more>$name</a>";
254    }
255
256    /**
257     * @see PluginInterface::external_link()
258     */
259    public function external_link($link, $title = '', $class = '', $target = '', $more = '')
260    {
261        global $conf;
262
263        $link = htmlentities($link);
264        if (!$title) $title = $link;
265        if (!$target) $target = $conf['target']['extern'];
266        if ($conf['relnofollow']) $more .= ' rel="nofollow"';
267
268        if ($class) $class = " class='$class'";
269        if ($target) $target = " target='$target'";
270        if ($more) $more = " " . trim($more);
271
272        return "<a href='$link'$class$target$more>$title</a>";
273    }
274
275    /**
276     * @see PluginInterface::render_text()
277     */
278    public function render_text($text, $format = 'xhtml', $syntax = 'dw')
279    {
280        return p_render($format, p_get_instructions($text, $syntax), $info);
281    }
282
283    // endregion
284}
285