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