PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Menu / MenuAbstract.php
matomo / app / core / Menu Last commit date
Group.php 1 year ago MenuAbstract.php 1 month ago MenuAdmin.php 1 month ago MenuTop.php 1 year ago
MenuAbstract.php
351 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Menu;
10
11 use Piwik\Cache;
12 use Piwik\Container\StaticContainer;
13 use Piwik\Plugins\SitesManager\API;
14 use Piwik\Singleton;
15 use Piwik\Plugin\Manager as PluginManager;
16 /**
17 * Base class for classes that manage one of Piwik's menus.
18 *
19 * There are three menus in Piwik, the main menu, the top menu and the admin menu.
20 * Each menu has a class that manages the menu's content. Each class invokes
21 * a different event to allow plugins to add new menu items.
22 *
23 * @static \Piwik\Menu\MenuAbstract getInstance()
24 */
25 abstract class MenuAbstract extends Singleton
26 {
27 /**
28 * @var array
29 */
30 protected $menu = null;
31 /**
32 * @var array
33 */
34 protected $menuEntries = [];
35 /**
36 * @var array
37 */
38 protected $menuEntriesToRemove = [];
39 /**
40 * @var array
41 */
42 protected $edits = [];
43 /**
44 * @var array
45 */
46 protected $renames = [];
47 /**
48 * @var bool
49 */
50 protected $orderingApplied = \false;
51 /**
52 * @var array<string, string>
53 */
54 protected $menuIcons = [];
55 /**
56 * Builds the menu, applies edits, renames
57 * and orders the entries.
58 *
59 * @return array
60 */
61 public function getMenu()
62 {
63 $this->buildMenu();
64 $this->applyEdits();
65 $this->applyRemoves();
66 $this->applyRenames();
67 $this->applyOrdering();
68 return $this->menu;
69 }
70 /**
71 * lets you register a menu icon for a certain menu category to replace the default arrow icon.
72 *
73 * @param string $menuName The translation key of a main menu category, eg 'Dashboard_Dashboard'
74 * @param string $iconCssClass The css class name of an icon, eg 'icon-user'
75 * @return void
76 */
77 public function registerMenuIcon($menuName, $iconCssClass)
78 {
79 $this->menuIcons[$menuName] = $iconCssClass;
80 }
81 /**
82 * Returns a list of available plugin menu instances.
83 *
84 * @return \Piwik\Plugin\Menu[]
85 */
86 protected function getAllMenus()
87 {
88 $cacheId = 'Menus.all';
89 $cache = Cache::getTransientCache();
90 if ($cache->contains($cacheId)) {
91 return $cache->fetch($cacheId);
92 }
93 $components = PluginManager::getInstance()->findComponents('Menu', 'Piwik\\Plugin\\Menu');
94 $menus = [];
95 foreach ($components as $component) {
96 $menus[] = StaticContainer::get($component);
97 }
98 $cache->save($cacheId, $menus);
99 return $menus;
100 }
101 /**
102 * Adds a new entry to the menu.
103 *
104 * @param string $menuName The menu's category name. Can be a translation token.
105 * @param null|string $subMenuName The menu item's name. Can be a translation token.
106 * @param string|array<string, scalar> $url The URL the admin menu entry should link to, or an array of query parameters
107 * that can be used to build the URL.
108 * @param int $order The order hint.
109 * @param string|null|false $tooltip An optional tooltip to display or false to display the tooltip.
110 * @param string|null|false $icon An icon classname, such as "icon-add". Only supported by admin menu
111 * @param string|null|false $onclick Will execute the on click handler instead of executing the link. Only supported by admin menu.
112 * @param string|null|false $attribute Will add this string as a link attribute.
113 * @param string|null|false $help Will display a help icon that will pop a notification with help information.
114 * @param int $badgeCount If non-zero then a badge will be overlaid on the icon showing the provided count
115 * @param string $cssClass If a string is provided, it will be added as an extra CSS class to the menu item
116 * @return void
117 * @since 2.7.0
118 * @api
119 */
120 public function addItem(string $menuName, ?string $subMenuName, $url, int $order = 50, $tooltip = \false, $icon = \false, $onclick = \false, $attribute = \false, $help = \false, int $badgeCount = 0, string $cssClass = '')
121 {
122 // make sure the idSite value used is numeric (hack-y fix for #3426)
123 if (isset($url['idSite']) && !is_numeric($url['idSite'])) {
124 $idSites = API::getInstance()->getSitesIdWithAtLeastViewAccess();
125 $url['idSite'] = reset($idSites);
126 }
127 $this->menuEntries[] = [$menuName, $subMenuName, $url, $order, $tooltip, $icon, $onclick, $attribute, $help, $badgeCount, $cssClass];
128 }
129 /**
130 * Removes an existing entry from the menu.
131 *
132 * @param string $menuName The menu's category name. Can be a translation token.
133 * @param string|null|false $subMenuName The menu item's name. Can be a translation token.
134 * @return void
135 * @api
136 */
137 public function remove($menuName, $subMenuName = \false)
138 {
139 $this->menuEntriesToRemove[] = array($menuName, $subMenuName);
140 }
141 /**
142 * Builds a single menu item
143 *
144 * @param string|array $url
145 * @param string|null|false $tooltip Tooltip to display.
146 * @param string|null|false $icon
147 * @param string|null|false $onclick
148 * @param string|null|false $attribute
149 * @param string|null|false $help
150 */
151 private function buildMenuItem(string $menuName, ?string $subMenuName, $url, int $order = 50, $tooltip = \false, $icon = \false, $onclick = \false, $attribute = \false, $help = \false, int $badgeCount = 0, string $cssClass = '') : void
152 {
153 if (!isset($this->menu[$menuName])) {
154 $this->menu[$menuName] = ['_hasSubmenu' => \false, '_order' => $order];
155 }
156 if (empty($subMenuName)) {
157 $this->menu[$menuName]['_url'] = $url;
158 $this->menu[$menuName]['_order'] = $order;
159 $this->menu[$menuName]['_name'] = $menuName;
160 $this->menu[$menuName]['_tooltip'] = $tooltip;
161 $this->menu[$menuName]['_attribute'] = $attribute;
162 if (!empty($this->menuIcons[$menuName])) {
163 $this->menu[$menuName]['_icon'] = $this->menuIcons[$menuName];
164 } else {
165 $this->menu[$menuName]['_icon'] = '';
166 }
167 if (!empty($onclick)) {
168 $this->menu[$menuName]['_onclick'] = $onclick;
169 }
170 $this->menu[$menuName]['_help'] = $help ?: '';
171 $this->menu[$menuName]['_badgecount'] = $badgeCount;
172 $this->menu[$menuName]['_cssClass'] = $cssClass;
173 }
174 if (!empty($subMenuName)) {
175 $this->menu[$menuName][$subMenuName]['_url'] = $url;
176 $this->menu[$menuName][$subMenuName]['_order'] = $order;
177 $this->menu[$menuName][$subMenuName]['_name'] = $subMenuName;
178 $this->menu[$menuName][$subMenuName]['_tooltip'] = $tooltip;
179 $this->menu[$menuName][$subMenuName]['_attribute'] = $attribute;
180 $this->menu[$menuName][$subMenuName]['_icon'] = $icon;
181 $this->menu[$menuName][$subMenuName]['_onclick'] = $onclick;
182 $this->menu[$menuName][$subMenuName]['_help'] = $help ?: '';
183 $this->menu[$menuName][$subMenuName]['_badgecount'] = $badgeCount;
184 $this->menu[$menuName][$subMenuName]['_cssClass'] = $cssClass;
185 $this->menu[$menuName]['_hasSubmenu'] = \true;
186 if (!array_key_exists('_tooltip', $this->menu[$menuName])) {
187 $this->menu[$menuName]['_tooltip'] = $tooltip;
188 }
189 }
190 }
191 /**
192 * Builds the menu from the $this->menuEntries variable.
193 */
194 private function buildMenu() : void
195 {
196 foreach ($this->menuEntries as $menuEntry) {
197 $this->buildMenuItem(...$menuEntry);
198 }
199 }
200 /**
201 * Renames a single menu entry.
202 *
203 * @param string $mainMenuOriginal
204 * @param string|null $subMenuOriginal
205 * @param string $mainMenuRenamed
206 * @param string|null $subMenuRenamed
207 * @phpstan-param ($subMenuOriginal is null ? null : string) $subMenuRenamed
208 * @return void
209 * @api
210 */
211 public function rename($mainMenuOriginal, $subMenuOriginal, $mainMenuRenamed, $subMenuRenamed)
212 {
213 $this->renames[] = [$mainMenuOriginal, $subMenuOriginal, $mainMenuRenamed, $subMenuRenamed];
214 }
215 /**
216 * Edits a URL of an existing menu entry.
217 *
218 * @param string $mainMenuToEdit
219 * @param string|null $subMenuToEdit
220 * @param string|array<string, scalar> $newUrl
221 * @return void
222 * @api
223 */
224 public function editUrl($mainMenuToEdit, $subMenuToEdit, $newUrl)
225 {
226 $this->edits[] = [$mainMenuToEdit, $subMenuToEdit, $newUrl];
227 }
228 /**
229 * Applies all edits to the menu.
230 */
231 private function applyEdits() : void
232 {
233 foreach ($this->edits as $edit) {
234 $mainMenuToEdit = $edit[0];
235 $subMenuToEdit = $edit[1];
236 $newUrl = $edit[2];
237 if ($subMenuToEdit === null) {
238 if (isset($this->menu[$mainMenuToEdit])) {
239 $menuDataToEdit =& $this->menu[$mainMenuToEdit];
240 } else {
241 $menuDataToEdit = null;
242 }
243 } else {
244 if (isset($this->menu[$mainMenuToEdit][$subMenuToEdit])) {
245 $menuDataToEdit =& $this->menu[$mainMenuToEdit][$subMenuToEdit];
246 } else {
247 $menuDataToEdit = null;
248 }
249 }
250 if (empty($menuDataToEdit)) {
251 $this->buildMenuItem($mainMenuToEdit, $subMenuToEdit, $newUrl);
252 } else {
253 $menuDataToEdit['_url'] = $newUrl;
254 }
255 }
256 }
257 private function applyRemoves() : void
258 {
259 foreach ($this->menuEntriesToRemove as $menuToDelete) {
260 if (empty($menuToDelete[1])) {
261 // Delete Main Menu
262 if (isset($this->menu[$menuToDelete[0]])) {
263 unset($this->menu[$menuToDelete[0]]);
264 }
265 } else {
266 // Delete Sub Menu
267 if (isset($this->menu[$menuToDelete[0]][$menuToDelete[1]])) {
268 unset($this->menu[$menuToDelete[0]][$menuToDelete[1]]);
269 }
270 }
271 }
272 }
273 /**
274 * Applies renames to the menu.
275 */
276 private function applyRenames() : void
277 {
278 foreach ($this->renames as $rename) {
279 $mainMenuOriginal = $rename[0];
280 $subMenuOriginal = $rename[1];
281 $mainMenuRenamed = $rename[2];
282 $subMenuRenamed = $rename[3];
283 // Are we changing a submenu?
284 if (!empty($subMenuOriginal)) {
285 if (isset($this->menu[$mainMenuOriginal][$subMenuOriginal])) {
286 $save = $this->menu[$mainMenuOriginal][$subMenuOriginal];
287 $save['_name'] = $subMenuRenamed;
288 unset($this->menu[$mainMenuOriginal][$subMenuOriginal]);
289 $this->menu[$mainMenuRenamed][$subMenuRenamed] = $save;
290 }
291 } elseif (isset($this->menu[$mainMenuOriginal])) {
292 // Changing a first-level element
293 $save = $this->menu[$mainMenuOriginal];
294 $save['_name'] = $mainMenuRenamed;
295 unset($this->menu[$mainMenuOriginal]);
296 $this->menu[$mainMenuRenamed] = $save;
297 }
298 }
299 }
300 /**
301 * Orders the menu according to their order.
302 */
303 private function applyOrdering() : void
304 {
305 if (empty($this->menu) || $this->orderingApplied) {
306 return;
307 }
308 uasort($this->menu, [$this, 'menuCompare']);
309 foreach ($this->menu as $key => &$element) {
310 if (is_null($element)) {
311 unset($this->menu[$key]);
312 } elseif ($element['_hasSubmenu']) {
313 uasort($element, [$this, 'menuCompare']);
314 }
315 }
316 $this->orderingApplied = \true;
317 }
318 /**
319 * Compares two menu entries. Used for ordering.
320 *
321 * @param array|string $itemOne
322 * @param array|string $itemTwo
323 * @return int
324 */
325 protected function menuCompare($itemOne, $itemTwo)
326 {
327 if (!is_array($itemOne) && !is_array($itemTwo)) {
328 return 0;
329 }
330 if (!is_array($itemOne) && is_array($itemTwo)) {
331 return -1;
332 }
333 if (is_array($itemOne) && !is_array($itemTwo)) {
334 return 1;
335 }
336 if (!isset($itemOne['_order']) && !isset($itemTwo['_order'])) {
337 return 0;
338 }
339 if (!isset($itemOne['_order']) && isset($itemTwo['_order'])) {
340 return -1;
341 }
342 if (isset($itemOne['_order']) && !isset($itemTwo['_order'])) {
343 return 1;
344 }
345 if ($itemOne['_order'] == $itemTwo['_order']) {
346 return strcmp($itemOne['_name'] ?? '', $itemTwo['_name'] ?? '');
347 }
348 return $itemOne['_order'] < $itemTwo['_order'] ? -1 : 1;
349 }
350 }
351