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