1<?php declare(strict_types = 1); 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22class CMenuItem extends CTag { 23 24 /** 25 * @var string 26 */ 27 private $action; 28 29 /** 30 * @var array 31 */ 32 private $aliases = []; 33 34 /** 35 * @var string 36 */ 37 private $icon_class; 38 39 /** 40 * @var string 41 */ 42 private $label; 43 44 /** 45 * @var CMenu 46 */ 47 private $sub_menu; 48 49 /** 50 * @var string 51 */ 52 private $title; 53 54 /** 55 * @var string 56 */ 57 private $target; 58 59 /** 60 * @var CUrl 61 */ 62 private $url; 63 64 /** 65 * @var bool 66 */ 67 private $is_selected = false; 68 69 /** 70 * Create menu item. 71 * 72 * @param string $label Menu item visual label. 73 */ 74 public function __construct(string $label) { 75 parent::__construct('li', true); 76 77 $this->label = $label; 78 } 79 80 /** 81 * Get action name. 82 * 83 * @return string|null 84 */ 85 public function getAction(): ?string { 86 return $this->action; 87 } 88 89 /** 90 * Set action name and derive a corresponding URL for menu item link. 91 * 92 * @param string $action_name Action name. 93 * 94 * @return CMenuItem 95 */ 96 public function setAction(string $action_name): self { 97 return $this->setUrl((new CUrl('zabbix.php'))->setArgument('action', $action_name), $action_name); 98 } 99 100 /** 101 * Get action name aliases. 102 * 103 * @return array 104 */ 105 public function getAliases(): array { 106 return $this->aliases; 107 } 108 109 /** 110 * Set action name aliases. 111 * 112 * @param array $aliases The aliases of menu item. Is able to specify the alias in following formats: 113 * - {action_name} - The alias is applicable to page with specified action name with any GET 114 * parameters in URL or without them; 115 * - {action_name}?{param}={value} - The alias is applicable to page with specified action 116 * when specified GET parameter exists in URL and have the same value; 117 * - {action_name}?{param}=* - The alias is applicable to page with specified action 118 * when specified GET parameter exists in URL and have any value; 119 * - {action_name}?!{param}={value} - The alias is applicable to page with specified action 120 * when specified GET parameter not exists in URL or have different value; 121 * - {action_name}?!{param}=* - The alias is applicable to page with specified action 122 * when specified GET parameter not exists in URL. 123 * 124 * @return CMenuItem 125 */ 126 public function setAliases(array $aliases): self { 127 foreach ($aliases as $alias) { 128 ['path' => $action_name, 'query' => $query_string] = parse_url($alias) + ['query' => '']; 129 parse_str($query_string, $query_params); 130 $this->aliases[$action_name][] = $query_params; 131 } 132 133 return $this; 134 } 135 136 /** 137 * Set icon CSS class for menu item link. 138 * 139 * @param string $icon_class 140 * 141 * @return CMenuItem 142 */ 143 public function setIcon(string $icon_class): self { 144 $this->icon_class = $icon_class; 145 146 return $this; 147 } 148 149 /** 150 * Get visual label of menu item. 151 * 152 * @return string 153 */ 154 public function getLabel(): string { 155 return $this->label; 156 } 157 158 /** 159 * Check if menu item is marked as selected. 160 * 161 * @return bool 162 */ 163 public function isSelected(): bool { 164 return $this->is_selected; 165 } 166 167 /** 168 * Mark menu item as selected. 169 * 170 * @return CMenuItem 171 */ 172 public function setSelected(): self { 173 $this->is_selected = true; 174 $this->addClass('is-selected'); 175 176 return $this; 177 } 178 179 /** 180 * Deep find menu item (including this one) by action name and mark the whole chain as selected. 181 * 182 * @param string $action_name Action name to search for. 183 * @param array $request_params Parameters of current HTTP request to compare in search process. 184 * @param bool $expand Add 'is-expanded' class for selected submenus. 185 * 186 * @return bool True, if menu item was selected. 187 */ 188 public function setSelectedByAction(string $action_name, array $request_params, bool $expand = true): bool { 189 if (array_key_exists($action_name, $this->aliases)) { 190 foreach ($this->aliases[$action_name] as $alias_params) { 191 $no_unacceptable_params = true; 192 $unacceptable_params = []; 193 foreach ($alias_params as $name => $value) { 194 if ($name[0] === '!') { 195 $unacceptable_params[substr($name, 1)] = $value; 196 unset($alias_params[$name]); 197 } 198 } 199 200 if ($unacceptable_params) { 201 $unacceptable_params_existing = array_intersect_assoc($unacceptable_params, $request_params); 202 foreach ($unacceptable_params as $name => $value) { 203 if ($value === '*' && array_key_exists($name, $request_params)) { 204 $unacceptable_params_existing[$name] = '*'; 205 } 206 } 207 208 $no_unacceptable_params = array_diff_assoc($unacceptable_params, $unacceptable_params_existing) 209 ? true 210 : false; 211 } 212 213 $alias_params_diff = array_diff_assoc($alias_params, $request_params); 214 foreach ($alias_params_diff as $name => $value) { 215 if ($value === '*') { 216 unset($alias_params_diff[$name]); 217 } 218 } 219 220 if ($no_unacceptable_params && !$alias_params_diff) { 221 $this->setSelected(); 222 223 return true; 224 } 225 } 226 } 227 228 if ($this->sub_menu !== null && $this->sub_menu->setSelectedByAction($action_name, $request_params, $expand)) { 229 $this->setSelected(); 230 231 return true; 232 } 233 234 return false; 235 } 236 237 /** 238 * Get submenu of menu item or create new one, if not exists. 239 * 240 * @return CMenu 241 */ 242 public function getSubMenu(): CMenu { 243 if ($this->sub_menu === null) { 244 $this->setSubMenu(new CMenu()); 245 } 246 247 return $this->sub_menu; 248 } 249 250 /** 251 * Set submenu for menu item. 252 * 253 * @param CMenu $sub_menu 254 * 255 * @return CMenuItem 256 */ 257 public function setSubMenu(CMenu $sub_menu): self { 258 $this->sub_menu = $sub_menu->addClass('submenu'); 259 $this->addClass('has-submenu'); 260 261 return $this; 262 } 263 264 /** 265 * Check if menu item has submenu. 266 * 267 * @return bool 268 */ 269 public function hasSubMenu(): bool { 270 return ($this->sub_menu !== null); 271 } 272 273 /** 274 * Set target attribute for the menu item link. 275 * 276 * @param string $target 277 * 278 * @return CMenuItem 279 */ 280 public function setTarget(string $target): self { 281 $this->target = $target; 282 283 return $this; 284 } 285 286 /** 287 * Set title attribute for the menu item link. 288 * 289 * @param string $title 290 * 291 * @return CMenuItem 292 */ 293 public function setTitle($title): self { 294 $this->title = $title; 295 296 return $this; 297 } 298 299 /** 300 * Get url of the menu item link. 301 * 302 * @return CUrl|null 303 */ 304 public function getUrl(): ?CUrl { 305 return $this->url; 306 } 307 308 /** 309 * Set url for the menu item link. 310 * 311 * @param CUrl $url 312 * @param string|null $action_name Associate action name to be matched by setSelected method. 313 * 314 * @return CMenuItem 315 */ 316 public function setUrl(CUrl $url, string $action_name = null): self { 317 $action = null; 318 319 if ($action_name !== null) { 320 $this->setAliases([$action_name]); 321 ['path' => $action] = parse_url($action_name); 322 } 323 324 $this->url = $url; 325 $this->action = $action; 326 327 return $this; 328 } 329 330 public function toString($destroy = true) 331 { 332 if ($this->url !== null || $this->sub_menu !== null) { 333 $this->addItem([ 334 (new CLink($this->label, $this->sub_menu !== null ? '#' : $this->url->getUrl())) 335 ->addClass($this->icon_class) 336 ->setTitle($this->title) 337 ->setTarget($this->target), 338 $this->sub_menu 339 ]); 340 } 341 else { 342 $this->addItem( 343 (new CSpan($this->label)) 344 ->addClass($this->icon_class) 345 ->setTitle($this->title) 346 ); 347 } 348 349 return parent::toString($destroy); 350 } 351} 352