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