xref: /dokuwiki/inc/Menu/Item/AbstractItem.php (revision 54cc7aa41e0f453bd6887b0e79242a139d84a47a)
1<?php
2
3namespace dokuwiki\Menu\Item;
4
5/**
6 * Class AbstractItem
7 *
8 * This class defines a single Item to be displayed in one of DokuWiki's menus. Plugins
9 * can extend those menus through action plugins and add their own instances of this class,
10 * overwriting some of its properties.
11 *
12 * Items may be shown multiple times in different contexts. Eg. for the default template
13 * all menus are shown in a Dropdown list on mobile, but are split into several places on
14 * desktop. The item's $context property can be used to hide the item depending on the current
15 * context.
16 *
17 * Children usually just need to overwrite the different properties, but for complex things
18 * the accessors may be overwritten instead.
19 */
20abstract class AbstractItem
21{
22
23    /** menu item is to be shown on desktop screens only */
24    const CTX_DESKTOP = 1;
25    /** menu item is to be shown on mobile screens only */
26    const CTX_MOBILE = 2;
27    /** menu item is to be shown in all contexts */
28    const CTX_ALL = 3;
29
30    /** @var string name of the action, usually the lowercase class name */
31    protected $type = '';
32    /** @var string optional keyboard shortcut */
33    protected $accesskey = '';
34    /** @var string the page id this action links to */
35    protected $id = '';
36    /** @var string the method to be used when this action is used in a form */
37    protected $method = 'get';
38    /** @var array parameters for the action (should contain the do parameter) */
39    protected $params = [];
40    /** @var bool when true, a rel=nofollow should be used */
41    protected $nofollow = true;
42    /** @var string this item's label may contain a placeholder, which is replaced with this */
43    protected $replacement = '';
44    /** @var string the full path to the SVG icon of this menu item */
45    protected $svg = DOKU_INC . 'lib/images/menu/00-default_checkbox-blank-circle-outline.svg';
46    /** @var string can be set to overwrite the default lookup in $lang.btn_* */
47    protected $label = '';
48    /** @var string the tooltip title, defaults to $label */
49    protected $title = '';
50    /** @var int the context this titme is shown in */
51    protected $context = self::CTX_ALL;
52
53    /**
54     * AbstractItem constructor.
55     *
56     * Sets the dynamic properties
57     *
58     * Children should always call the parent constructor!
59     *
60     * @throws \RuntimeException when the action is disabled
61     */
62    public function __construct()
63    {
64        global $ID;
65        $this->id = $ID;
66        $this->type = $this->getType();
67        $this->params['do'] = $this->type;
68
69        if (!actionOK($this->type)) throw new \RuntimeException("action disabled: {$this->type}");
70    }
71
72    /**
73     * Return this item's label
74     *
75     * When the label property was set, it is simply returned. Otherwise, the action's type
76     * is used to look up the translation in the main language file and, if used, the replacement
77     * is applied.
78     *
79     * @return string
80     */
81    public function getLabel()
82    {
83        if ($this->label !== '') return $this->label;
84
85        /** @var array $lang */
86        global $lang;
87        $label = $lang['btn_' . $this->type];
88        if (strpos($label, '%s')) {
89            $label = sprintf($label, $this->replacement);
90        }
91        if ($label === '') $label = '[' . $this->type . ']';
92        return $label;
93    }
94
95    /**
96     * Return this item's title
97     *
98     * This title should be used to display a tooltip (using the HTML title attribute). If
99     * a title property was not explicitly set, the label will be returned.
100     *
101     * @return string
102     */
103    public function getTitle()
104    {
105        if ($this->title === '') return $this->getLabel();
106        return $this->title;
107    }
108
109    /**
110     * Return the link this item links to
111     *
112     * Basically runs wl() on $id and $params. However if the ID is a hash it is used directly
113     * as the link
114     *
115     * Please note that the generated URL is *not* XML escaped.
116     *
117     * @return string
118     * @see wl()
119     */
120    public function getLink()
121    {
122        if ($this->id && $this->id[0] == '#') {
123            return $this->id;
124        } else {
125            return wl($this->id, $this->params, false, '&');
126        }
127    }
128
129    /**
130     * Convenience method to get the attributes for constructing an <a> element
131     *
132     * @param string|false $classprefix create a class from type with this prefix, false for no class
133     * @return array
134     * @see buildAttributes()
135     */
136    public function getLinkAttributes($classprefix = 'menuitem ')
137    {
138        $attr = ['href' => $this->getLink(), 'title' => $this->getTitle()];
139        if ($this->isNofollow()) $attr['rel'] = 'nofollow';
140        if ($this->getAccesskey()) {
141            $attr['accesskey'] = $this->getAccesskey();
142            $attr['title'] .= ' [' . $this->getAccesskey() . ']';
143        }
144        if ($classprefix !== false) $attr['class'] = $classprefix . $this->getType();
145
146        return $attr;
147    }
148
149    /**
150     * Convenience method to create a full <a> element
151     *
152     * Wraps around the label and SVG image
153     *
154     * @param string|false $classprefix create a class from type with this prefix, false for no class
155     * @param bool $svg add SVG icon to the link
156     * @return string
157     */
158    public function asHtmlLink($classprefix = 'menuitem ', $svg = true)
159    {
160        $attr = buildAttributes($this->getLinkAttributes($classprefix));
161        $html = "<a $attr>";
162        if ($svg) {
163            $html .= '<span>' . hsc($this->getLabel()) . '</span>';
164            $html .= inlineSVG($this->getSvg());
165        } else {
166            $html .= hsc($this->getLabel());
167        }
168        $html .= "</a>";
169
170        return $html;
171    }
172
173    /**
174     * Convenience method to create a <button> element inside it's own form element
175     *
176     * Uses html_btn()
177     *
178     * @return string
179     */
180    public function asHtmlButton()
181    {
182        return html_btn(
183            $this->getType(),
184            $this->id,
185            $this->getAccesskey(),
186            $this->getParams(),
187            $this->method,
188            $this->getTitle(),
189            $this->getLabel(),
190            $this->getSvg()
191        );
192    }
193
194    /**
195     * Should this item be shown in the given context
196     *
197     * @param int $ctx the current context
198     * @return bool
199     */
200    public function visibleInContext($ctx)
201    {
202        return (bool)($ctx & $this->context);
203    }
204
205    /**
206     * @return string the name of this item
207     */
208    public function getType()
209    {
210        if ($this->type === '') {
211            $this->type = strtolower(substr(strrchr(get_class($this), '\\'), 1));
212        }
213        return $this->type;
214    }
215
216    /**
217     * @return string
218     */
219    public function getAccesskey()
220    {
221        return $this->accesskey;
222    }
223
224    /**
225     * @return array
226     */
227    public function getParams()
228    {
229        return $this->params;
230    }
231
232    /**
233     * @return bool
234     */
235    public function isNofollow()
236    {
237        return $this->nofollow;
238    }
239
240    /**
241     * @return string
242     */
243    public function getSvg()
244    {
245        return $this->svg;
246    }
247
248    /**
249     * Return this Item's settings as an array as used in tpl_get_action()
250     *
251     * @return array
252     */
253    public function getLegacyData()
254    {
255        return [
256            'accesskey' => $this->accesskey ?: null,
257            'type' => $this->type,
258            'id' => $this->id,
259            'method' => $this->method,
260            'params' => $this->params,
261            'nofollow' => $this->nofollow,
262            'replacement' => $this->replacement
263        ];
264    }
265}
266