Issues (1686)

sources/ElkArte/Menu/MenuContext.php (6 issues)

1
<?php
2
3
/**
4
 * The menu context class
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 dev
11
 *
12
 */
13
14
namespace ElkArte\Menu;
15
16
use ElkArte\Cache\Cache;
17
use ElkArte\Helper\HttpReq;
18
use ElkArte\Helper\ValuesContainer;
19
use ElkArte\User;
20
21
/**
22
 * Class MenuContext
23
 *
24
 * The MenuContext class is responsible for setting up the context for the menu on each page load.
25
 */
26
class MenuContext
27
{
28
	/** @var ValuesContainer details of the user to which we are building the menu */
29
	private $user;
30
31
	/** @var Cache|object The cache variable. */
32
	private $cache;
33
34
	/** @var int cache age */
35
	private $cacheTime;
36
37
	/** @var bool if the action needs to call a hook to determine the real action */
38
	private $needs_action_hook;
39
40
	public function __construct()
41
	{
42
		global $modSettings;
43
44
		$this->user = User::$info;
45
		$this->cache = Cache::instance();
46
		$this->cacheTime = $modSettings['lastActive'] * 60;
47
	}
48
49
	/**
50
	 * Sets up all the top menu buttons
51
	 *
52
	 * What it does:
53
	 *
54
	 * - Defines every master item in the menu, as well as any sub-items
55
	 * - Sets the counter for the menu items
56
	 * - Ensures the chosen action is set so the menu is highlighted
57
	 * - Saves them in the cache if it is available and on
58
	 * - Places the results in $context
59
	 */
60
	public function setupMenuContext()
61
	{
62
		global $context;
63
64
		$this->setupUserPermissions();
65
66
		call_integration_hook('integrate_setup_allow');
67
68
		$this->setupHeaderCallbacks();
69
70
		// Update the Moderation menu items with action item totals
71
		if ($context['allow_moderation_center'])
72
		{
73
			// Get the numbers for the menu ...
74
			require_once(SUBSDIR . '/Moderation.subs.php');
75
			$menu_count = loadModeratorMenuCounts();
76
		}
77
78
		$menu_count['unread_messages'] = $context['user']['unread_messages'];
79
		$menu_count['mentions'] = $context['user']['mentions'];
80
81
		// All the buttons we can possibly want and then some, try pulling the final list of buttons from cache first.
82
		$this->setupMenuButtons($menu_count);
83
84
		$this->setupCurrentAction();
85
86
		// Not all actions are simple.
87
		if (!empty($this->needs_action_hook))
88
		{
89
			call_integration_hook('integrate_current_action', [&$current_action]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $current_action seems to be never defined.
Loading history...
90
		}
91
	}
92
93
	/**
94
	 * Sets up some core menu item permissions based on the user
95
	 */
96
	private function setupUserPermissions()
97
	{
98
		global $context, $modSettings;
99
100
		$context['allow_search'] = empty($modSettings['allow_guestAccess']) ? $this->user->is_guest === false && allowedTo('search_posts') : (allowedTo('search_posts'));
0 ignored issues
show
Bug Best Practice introduced by
The property is_guest does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
101
		$context['allow_admin'] = allowedTo(['admin_forum', 'manage_boards', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_attachments', 'manage_smileys']);
102
		$context['allow_edit_profile'] = $this->user->is_guest === false && allowedTo(['profile_view_own', 'profile_view_any', 'profile_identity_own', 'profile_identity_any', 'profile_extra_own', 'profile_extra_any', 'profile_remove_own', 'profile_remove_any', 'moderate_forum', 'manage_membergroups', 'profile_title_own', 'profile_title_any']);
103
		$context['allow_memberlist'] = allowedTo('view_mlist');
104
		$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
105
		$context['allow_moderation_center'] = $context['user']['can_mod'];
106
		$context['allow_pm'] = allowedTo('pm_read');
107
	}
108
109
	/**
110
	 * Sets up the header callbacks.
111
	 *
112
	 * @return void
113
	 */
114
	private function setupHeaderCallbacks()
115
	{
116
		global $context;
117
118
		if ($context['allow_search'])
119
		{
120
			$context['theme_header_callbacks'] = elk_array_insert($context['theme_header_callbacks'], 'login_bar', ['search_bar'], 'after');
121
		}
122
123
		// Add in a top section notice callback
124
		$context['theme_header_callbacks'][] = 'header_bar';
125
	}
126
127
	/**
128
	 * Set up the menu buttons.
129
	 *
130
	 * @param array $menu_count The count of menus.
131
	 *
132
	 * @return void
133
	 */
134
	private function setUpMenuButtons($menu_count)
135
	{
136
		global $context, $modSettings;
137
138
		// Check the cache
139
		if ((time() - $this->cacheTime <= $modSettings['settings_updated'])
140
			|| ($menu_buttons = $this->cache->get('menu_buttons-' . implode('_', $this->user->groups) . '-' . $this->user->language, $this->cacheTime)) === null)
0 ignored issues
show
It seems like $this->user->groups can also be of type null; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
			|| ($menu_buttons = $this->cache->get('menu_buttons-' . implode('_', /** @scrutinizer ignore-type */ $this->user->groups) . '-' . $this->user->language, $this->cacheTime)) === null)
Loading history...
Bug Best Practice introduced by
The property language does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property groups does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
141
		{
142
			// Start things up: this is what we know by default
143
			require_once(SUBSDIR . '/Menu.subs.php');
144
			$buttons = loadDefaultMenuButtons();
145
146
			// Allow editing menu buttons easily.
147
			call_integration_hook('integrate_menu_buttons', [&$buttons, &$menu_count]);
148
149
			// Now we put the buttons in the context so the theme can use them.
150
			$menu_buttons = $this->initializeButtonProperties($buttons, $menu_count);
151
152
			if ($this->cache->levelHigherThan(1))
153
			{
154
				$this->cache->put('menu_buttons-' . implode('_', $this->user->groups) . '-' . $this->user->language, $menu_buttons, $this->cacheTime);
155
			}
156
		}
157
158
		if (!empty($menu_buttons['profile']['sub_buttons']['logout']))
159
		{
160
			$menu_buttons['profile']['sub_buttons']['logout']['href'] .= ';' . $context['session_var'] . '=' . $context['session_id'];
161
		}
162
163
		$context['menu_buttons'] = $menu_buttons;
164
	}
165
166
	/**
167
	 * Initializes the properties of the buttons.
168
	 *
169
	 * @param array $buttons The array of buttons.
170
	 * @param array $menu_count The count of menus.
171
	 *
172
	 * @return array The array of buttons with initialized properties.
173
	 */
174
	private function initializeButtonProperties($buttons, $menu_count)
175
	{
176
		$menu_buttons = [];
177
		foreach ($buttons as $act => $button)
178
		{
179
			if (!empty($button['show']))
180
			{
181
				$button = $this->setButtonProperties($button, $menu_count);
182
				$menu_buttons[$act] = $button;
183
			}
184
		}
185
186
		return $menu_buttons;
187
	}
188
189
	/**
190
	 * Set the properties of a button based on the menu count and other criteria.
191
	 *
192
	 * @param array $button The button that needs to be updated.
193
	 * @param array $menu_count The menu count data.
194
	 * @return array The updated button.
195
	 */
196
	private function setButtonProperties($button, $menu_count)
197
	{
198
		$button['active_button'] = false;
199
200
		$button = $this->setButtonActionHook($button);
201
		$button = $this->setButtonCounter($button, $menu_count);
202
203
		return $this->setSubButtonCounter($button, $menu_count);
204
	}
205
206
	/**
207
	 * Sets the action hook flag for the button.
208
	 *
209
	 * @param array $button The button that needs to be checked.
210
	 * @return array The updated button.
211
	 */
212
	private function setButtonActionHook($button)
213
	{
214
		if (isset($button['action_hook']))
215
		{
216
			$this->needs_action_hook = true;
217
		}
218
219
		return $button;
220
	}
221
222
	/**
223
	 * Set the counter and indicator of the button based on the menu count.
224
	 *
225
	 * @param array $button The button that needs to be updated.
226
	 * @param array $menu_count The menu count data.
227
	 * @return array The updated button.
228
	 */
229
	private function setButtonCounter($button, $menu_count)
230
	{
231
		if (isset($button['counter']) && !empty($menu_count[$button['counter']]))
232
		{
233
			$button['alttitle'] = $button['title'] . ' [' . $menu_count[$button['counter']] . ']';
234
			$this->addCountsToTitle($button['title'], $menu_count[$button['counter']], 0);
235
			$button['indicator'] = true;
236
		}
237
238
		return $button;
239
	}
240
241
	/**
242
	 * Sets the counter for sub buttons of a given button
243
	 *
244
	 * @param array $button The button containing sub buttons
245
	 * @param array $menu_count The count of items for each sub button
246
	 *
247
	 * @return array The modified button with updated counters for sub buttons
248
	 */
249
	private function setSubButtonCounter($button, $menu_count)
250
	{
251
		if (isset($button['sub_buttons']))
252
		{
253
			foreach ($button['sub_buttons'] as $key => $subButton)
254
			{
255
				if (empty($subButton['show']))
256
				{
257
					unset($button['sub_buttons'][$key]);
258
					continue;
259
				}
260
261
				if (isset($subButton['counter']) && !empty($menu_count[$subButton['counter']]))
262
				{
263
					$button['sub_buttons'][$key]['alttitle'] = $subButton['title'] . ' [' . $menu_count[$subButton['counter']] . ']';
264
					$this->addCountsToTitle($button['sub_buttons'][$key]['title'], $menu_count[$subButton['counter']], 1);
265
266
					// And any counter on its sub menu
267
					$button = $this->setSubButtonCounts($button, $key, $subButton, $menu_count);
268
				}
269
			}
270
		}
271
272
		return $button;
273
	}
274
275
	/**
276
	 * Sets the sub button counts for a given button.
277
	 *
278
	 * @param array $button The original button array.
279
	 * @param int $key The key of the sub button.
280
	 * @param array $subButton The sub button array.
281
	 * @param array $menu_count The count of menus.
282
	 *
283
	 * @return array The updated button array with sub button counts set.
284
	 */
285
	private function setSubButtonCounts($button, $key, $subButton, $menu_count)
286
	{
287
		if (empty($subButton['sub_buttons']))
288
		{
289
			return $button;
290
		}
291
292
		foreach ($subButton['sub_buttons'] as $key2 => $subButton2)
293
		{
294
			$button['sub_buttons'][$key]['sub_buttons'][$key2] = $subButton2;
295
296
			if (empty($subButton2['show']))
297
			{
298
				unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
299
			}
300
			elseif (isset($subButton2['counter']) && !empty($menu_count[$subButton2['counter']]))
301
			{
302
				$button['sub_buttons'][$key]['sub_buttons'][$key2]['alttitle'] = $subButton2['title'] . ' [' . $menu_count[$subButton2['counter']] . ']';
303
				$this->addCountsToTitle($button['sub_buttons'][$key]['sub_buttons'][$key2]['title'], $menu_count[$subButton2['counter']], 1);
304
				unset($menu_count[$subButton2['counter']]);
305
			}
306
		}
307
308
		return $button;
309
	}
310
311
	/**
312
	 * Adds counts to the title.
313
	 *
314
	 * @param string $title The title to add counts to.
315
	 * @param array $counts The array of counts.
316
	 * @param string $notice The notice to use for formatting.
317
	 *
318
	 * @return void Does not return anything.
319
	 */
320
	private function addCountsToTitle(&$title, $counts, $notice)
321
	{
322
		global $settings;
323
324
		if (!empty($settings['menu_numeric_notice'][$notice]))
325
		{
326
			$title .= sprintf($settings['menu_numeric_notice'][$notice], $counts);
0 ignored issues
show
$counts of type array is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

326
			$title .= sprintf($settings['menu_numeric_notice'][$notice], /** @scrutinizer ignore-type */ $counts);
Loading history...
327
		}
328
	}
329
330
	/**
331
	 * Sets up the current action.
332
	 *
333
	 * @return void
334
	 * @global array $context The global context array.
335
	 *
336
	 */
337
	private function setupCurrentAction()
338
	{
339
		global $context;
340
341
		if (isset($context['menu_buttons'][$context['current_action']]))
342
		{
343
			$current_action = $context['current_action'];
344
		}
345
		elseif ($context['current_action'] === 'profile')
346
		{
347
			$current_action = 'pm';
348
		}
349
		elseif ($context['current_action'] === 'theme')
350
		{
351
352
			$sa = HttpReq::instance()->getRequest('sa', 'trim', '');
353
			$current_action = $sa === 'pick' ? 'profile' : 'admin';
354
		}
355
		else
356
		{
357
			$current_action = 'home';
358
		}
359
360
		// Set the current action
361
		$context['current_action'] = $current_action;
362
363
		// Not all actions are simple.
364
		if (!empty($this->needs_action_hook))
365
		{
366
			call_integration_hook('integrate_current_action', [&$current_action]);
367
		}
368
369
		if (isset($context['menu_buttons'][$current_action]))
370
		{
371
			$context['menu_buttons'][$current_action]['active_button'] = true;
372
		}
373
	}
374
}
375