1<?php
2/**
3 * Elgg admin functions.
4 *
5 * Admin pages
6 * Plugins no not need to provide their own page handler to add a page to the
7 * admin area. A view placed at admin/<section>/<subsection> can be access
8 * at http://example.org/admin/<section>/<subsection>. The title of the page
9 * will be elgg_echo('admin:<section>:<subsection>'). For an example of how to
10 * add a page to the admin area, see the diagnostics plugin.
11 *
12 * Admin notices
13 * System messages (success and error messages) are used in both the main site
14 * and the admin area. There is a special presistent message for the admin area
15 * called an admin notice. It should be used when a plugin requires an
16 * administrator to take an action. @see elgg_add_admin_notice()
17 */
18
19use Elgg\Menu\MenuItems;
20use Elgg\Database\QueryBuilder;
21use Elgg\Http\ResponseBuilder;
22
23/**
24 * Get the admin users
25 *
26 * @param array $options Options array, @see elgg_get_entities() for parameters
27 *
28 * @return mixed Array of admin users or false on failure. If a count, returns int.
29 * @since 1.8.0
30 */
31function elgg_get_admins(array $options = []) {
32	$options['type'] = 'user';
33	$options['metadata_name_value_pairs'] = elgg_extract('metadata_name_value_pairs', $options, []);
34
35	$options['metadata_name_value_pairs']['admin'] = 'yes';
36
37	return elgg_get_entities($options);
38}
39
40/**
41 * Write a persistent message to the admin view.
42 * Useful to alert the admin to take a certain action.
43 * The id is a unique ID that can be cleared once the admin
44 * completes the action.
45 *
46 * eg: add_admin_notice('twitter_services_no_api',
47 * 	'Before your users can use Twitter services on this site, you must set up
48 * 	the Twitter API key in the <a href="link">Twitter Services Settings</a>');
49 *
50 * @param string $id      A unique ID that your plugin can remember
51 * @param string $message Body of the message
52 *
53 * @return ElggAdminNotice|bool
54 * @since 1.8.0
55 */
56function elgg_add_admin_notice($id, $message) {
57	return _elgg_services()->adminNotices->add($id, $message);
58}
59
60/**
61 * Remove an admin notice by ID.
62 *
63 * @param string $id The unique ID assigned in add_admin_notice()
64 *
65 * @return bool
66 * @since 1.8.0
67 */
68function elgg_delete_admin_notice($id) {
69	return _elgg_services()->adminNotices->delete($id);
70}
71
72/**
73 * Get admin notices. An admin must be logged in since the notices are private.
74 *
75 * @param array $options Query options
76 *
77 * @return \ElggObject[]|int|mixed Admin notices
78 * @since 1.8.0
79 */
80function elgg_get_admin_notices(array $options = []) {
81	return _elgg_services()->adminNotices->find($options);
82}
83
84/**
85 * Check if an admin notice is currently active. (Ignores access)
86 *
87 * @param string $id The unique ID used to register the notice.
88 *
89 * @return bool
90 * @since 1.8.0
91 */
92function elgg_admin_notice_exists($id) {
93	return _elgg_services()->adminNotices->exists($id);
94}
95
96/**
97 * Add an admin notice when a new \ElggUpgrade object is created.
98 *
99 * @param \Elgg\Event $event 'create', 'object'
100 *
101 * @return void
102 *
103 * @internal
104 */
105function _elgg_create_notice_of_pending_upgrade(\Elgg\Event $event) {
106	if (!$event->getObject() instanceof \ElggUpgrade) {
107		return;
108	}
109
110	// Link to the Upgrades section
111	$link = elgg_view('output/url', [
112		'href' => 'admin/upgrades',
113		'text' => elgg_echo('admin:view_upgrades'),
114	]);
115
116	$message = elgg_echo('admin:pending_upgrades');
117
118	elgg_add_admin_notice('pending_upgrades', "$message $link");
119}
120
121/**
122 * Initialize the admin backend.
123 * @return void
124 * @internal
125 */
126function _elgg_admin_init() {
127
128	elgg_register_external_file('css', 'elgg.admin', elgg_get_simplecache_url('admin.css'));
129	elgg_register_external_file('css', 'admin/users/unvalidated', elgg_get_simplecache_url('admin/users/unvalidated.css'));
130
131	elgg_define_js('admin/users/unvalidated', [
132		'src' => elgg_get_simplecache_url('admin/users/unvalidated.js'),
133	]);
134
135	elgg_extend_view('admin.css', 'lightbox/elgg-colorbox-theme/colorbox.css');
136
137	elgg_register_ajax_view('forms/admin/user/change_email');
138
139	elgg_register_plugin_hook_handler('register', 'menu:admin_header', '_elgg_admin_header_menu');
140	elgg_register_plugin_hook_handler('register', 'menu:admin_footer', '_elgg_admin_footer_menu');
141	elgg_register_plugin_hook_handler('register', 'menu:filter:admin/upgrades', '_elgg_admin_upgrades_menu');
142	elgg_register_plugin_hook_handler('register', 'menu:page', '_elgg_admin_page_menu');
143	elgg_register_plugin_hook_handler('register', 'menu:page', '_elgg_admin_page_menu_plugin_settings');
144	elgg_register_plugin_hook_handler('register', 'menu:user:unvalidated:bulk', '_elgg_admin_user_unvalidated_bulk_menu');
145
146	// maintenance mode
147	if (elgg_get_config('elgg_maintenance_mode', null)) {
148		elgg_register_plugin_hook_handler('route', 'all', '_elgg_admin_maintenance_handler', 600);
149		elgg_register_plugin_hook_handler('action', 'all', '_elgg_admin_maintenance_action_check', 600);
150		elgg_register_external_file('css', 'maintenance', elgg_get_simplecache_url('maintenance.css'));
151
152		elgg_register_menu_item('topbar', [
153			'name' => 'maintenance_mode',
154			'href' => 'admin/configure_utilities/maintenance',
155			'text' => elgg_echo('admin:maintenance_mode:indicator_menu_item'),
156			'icon' => 'wrench',
157			'priority' => 900,
158		]);
159	}
160
161	elgg_register_simplecache_view('admin.css');
162
163	// widgets
164	$widgets = ['online_users', 'new_users', 'content_stats', 'banned_users', 'admin_welcome', 'control_panel', 'cron_status'];
165	foreach ($widgets as $widget) {
166		elgg_register_widget_type(
167				$widget,
168				elgg_echo("admin:widget:$widget"),
169				elgg_echo("admin:widget:$widget:help"),
170				['admin']
171		);
172	}
173
174	// automatic adding of widgets for admin
175	elgg_register_event_handler('make_admin', 'user', '_elgg_add_admin_widgets');
176
177	elgg_register_notification_event('user', 'user', ['make_admin', 'remove_admin']);
178	elgg_register_plugin_hook_handler('get', 'subscriptions', '_elgg_admin_get_admin_subscribers_admin_action');
179	elgg_register_plugin_hook_handler('get', 'subscriptions', '_elgg_admin_get_user_subscriber_admin_action');
180	elgg_register_plugin_hook_handler('prepare', 'notification:make_admin:user:user', '_elgg_admin_prepare_admin_notification_make_admin');
181	elgg_register_plugin_hook_handler('prepare', 'notification:make_admin:user:user', '_elgg_admin_prepare_user_notification_make_admin');
182	elgg_register_plugin_hook_handler('prepare', 'notification:remove_admin:user:user', '_elgg_admin_prepare_admin_notification_remove_admin');
183	elgg_register_plugin_hook_handler('prepare', 'notification:remove_admin:user:user', '_elgg_admin_prepare_user_notification_remove_admin');
184
185	// new users require admin validation
186	elgg_register_event_handler('login:before', 'user', '_elgg_admin_user_validation_login_attempt', 999); // allow others to throw exceptions earlier
187	elgg_register_event_handler('validate:after', 'user', '_elgg_admin_user_validation_notification');
188	elgg_register_plugin_hook_handler('cron', 'daily', '_elgg_admin_notify_admins_pending_user_validation');
189	elgg_register_plugin_hook_handler('cron', 'weekly', '_elgg_admin_notify_admins_pending_user_validation');
190	elgg_register_plugin_hook_handler('register', 'user', '_elgg_admin_check_admin_validation', 999); // allow others to also disable the user
191	elgg_register_plugin_hook_handler('response', 'action:register', '_elgg_admin_set_registration_forward_url', 999); // allow other to set forwar url first
192	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_admin_save_notification_setting');
193
194	// Add notice about pending upgrades
195	elgg_register_event_handler('create', 'object', '_elgg_create_notice_of_pending_upgrade');
196}
197
198/**
199 * Returns plugin listing and admin menu to the client (used after plugin (de)activation)
200 *
201 * @internal
202 * @return Elgg\Http\OkResponse
203 */
204function _elgg_ajax_plugins_update() {
205	elgg_admin_gatekeeper();
206	elgg_set_context('admin');
207
208	return elgg_ok_response([
209		'list' => elgg_view('admin/plugins', ['list_only' => true]),
210		'sidebar' => elgg_view('admin/sidebar'),
211	]);
212}
213
214/**
215 * Register menu items for the admin_header menu
216 *
217 * @param \Elgg\Hook $hook 'register', 'menu:admin_header'
218 *
219 * @return void|MenuItems
220 *
221 * @internal
222 * @since 3.0
223 */
224function _elgg_admin_header_menu(\Elgg\Hook $hook) {
225	if (!elgg_in_context('admin') || !elgg_is_admin_logged_in()) {
226		return;
227	}
228
229	$return = $hook->getValue();
230
231	$admin = elgg_get_logged_in_user_entity();
232
233	$return[] = \ElggMenuItem::factory([
234		'name' => 'account',
235		'text' => $admin->getDisplayName(),
236		'href' => $admin->getURL(),
237		'icon' => elgg_view('output/img', [
238			'src' => $admin->getIconURL('small'),
239			'alt' => $admin->getDisplayName(),
240		]),
241		'link_class' => 'elgg-avatar-small',
242		'priority' => 1000,
243	]);
244
245	$return[] = \ElggMenuItem::factory([
246		'name' => 'admin_logout',
247		'href' => 'action/logout',
248		'text' => elgg_echo('logout'),
249		'priority' => 900,
250	]);
251
252	$return[] = \ElggMenuItem::factory([
253		'name' => 'view_site',
254		'href' => elgg_get_site_url(),
255		'text' => elgg_echo('admin:view_site'),
256		'priority' => 800,
257	]);
258
259	if (elgg_get_config('elgg_maintenance_mode')) {
260		$return[] = \ElggMenuItem::factory([
261			'name' => 'maintenance',
262			'href' => 'admin/configure_utilities/maintenance',
263			'text' => elgg_echo('admin:configure_utilities:maintenance'),
264			'link_class' => 'elgg-maintenance-mode-warning',
265			'priority' => 700,
266		]);
267	}
268
269	return $return;
270}
271
272/**
273 * Register menu items for the admin_footer menu
274 *
275 * @param \Elgg\Hook $hook 'register', 'menu:admin_footer'
276 *
277 * @return void|\ElggMenuItem[]
278 *
279 * @internal
280 * @since 3.0
281 */
282function _elgg_admin_footer_menu(\Elgg\Hook $hook) {
283	if (!elgg_in_context('admin') || !elgg_is_admin_logged_in()) {
284		return;
285	}
286
287	$return = $hook->getValue();
288
289	$return[] = \ElggMenuItem::factory([
290		'name' => 'faq',
291		'text' => elgg_echo('admin:footer:faq'),
292		'href' => 'http://learn.elgg.org/en/stable/appendix/faqs.html',
293	]);
294
295	$return[] = \ElggMenuItem::factory([
296		'name' => 'manual',
297		'text' => elgg_echo('admin:footer:manual'),
298		'href' => 'http://learn.elgg.org/en/stable/admin/index.html',
299	]);
300
301	$return[] = \ElggMenuItem::factory([
302		'name' => 'community_forums',
303		'text' => elgg_echo('admin:footer:community_forums'),
304		'href' => 'http://elgg.org/groups/all/',
305	]);
306
307	$return[] = \ElggMenuItem::factory([
308		'name' => 'blog',
309		'text' => elgg_echo('admin:footer:blog'),
310		'href' => 'https://elgg.org/blog/all',
311	]);
312
313	return $return;
314}
315
316/**
317 * Register menu items for the page menu
318 *
319 * @param \Elgg\Hook $hook 'register' 'menu:page'
320 * @return array
321 *
322 * @internal
323 * @see _elgg_default_widgets_init() for default widgets menu items setup
324 * @since 3.0
325 */
326function _elgg_admin_page_menu(\Elgg\Hook $hook) {
327	if (!elgg_in_context('admin') || !elgg_is_admin_logged_in()) {
328		return;
329	}
330
331	$return = $hook->getValue();
332
333	// administer
334	$return[] = \ElggMenuItem::factory([
335		'name' => 'dashboard',
336		'href' => 'admin',
337		'text' => elgg_echo('admin:dashboard'),
338		'priority' => 10,
339		'section' => 'administer',
340	]);
341
342	$return[] = \ElggMenuItem::factory([
343		'name' => 'plugins',
344		'href' => 'admin/plugins',
345		'text' => elgg_echo('admin:plugins'),
346		'priority' => 30,
347		'section' => 'administer',
348	]);
349
350	$return[] = \ElggMenuItem::factory([
351		'name' => 'users',
352		'text' => elgg_echo('admin:users'),
353		'priority' => 40,
354		'section' => 'administer',
355	]);
356	$return[] = \ElggMenuItem::factory([
357		'name' => 'users:online',
358		'text' => elgg_echo('admin:users:online'),
359		'href' => 'admin/users/online',
360		'priority' => 10,
361		'section' => 'administer',
362		'parent_name' => 'users',
363	]);
364	$return[] = \ElggMenuItem::factory([
365		'name' => 'users:admins',
366		'text' => elgg_echo('admin:users:admins'),
367		'href' => 'admin/users/admins',
368		'priority' => 20,
369		'section' => 'administer',
370		'parent_name' => 'users',
371	]);
372	$return[] = \ElggMenuItem::factory([
373		'name' => 'users:newest',
374		'text' => elgg_echo('admin:users:newest'),
375		'href' => 'admin/users/newest',
376		'priority' => 30,
377		'section' => 'administer',
378		'parent_name' => 'users',
379	]);
380	$return[] = \ElggMenuItem::factory([
381		'name' => 'users:add',
382		'text' => elgg_echo('admin:users:add'),
383		'href' => 'admin/users/add',
384		'priority' => 40,
385		'section' => 'administer',
386		'parent_name' => 'users',
387	]);
388
389	$return[] = \ElggMenuItem::factory([
390		'name' => 'users:unvalidated',
391		'text' => elgg_echo('admin:users:unvalidated'),
392		'href' => 'admin/users/unvalidated',
393		'priority' => 50,
394		'section' => 'administer',
395		'parent_name' => 'users',
396	]);
397	$return[] = \ElggMenuItem::factory([
398		'name' => 'upgrades',
399		'href' => 'admin/upgrades',
400		'text' => elgg_echo('admin:upgrades'),
401		'priority' => 600,
402		'section' => 'administer',
403	]);
404
405	$return[] = \ElggMenuItem::factory([
406		'name' => 'administer_utilities',
407		'text' => elgg_echo('admin:administer_utilities'),
408		'priority' => 50,
409		'section' => 'administer',
410	]);
411
412	// configure
413	$return[] = \ElggMenuItem::factory([
414		'name' => 'settings:basic',
415		'href' => 'admin/site_settings',
416		'text' => elgg_echo('admin:site_settings'),
417		'priority' => 10,
418		'section' => 'configure',
419	]);
420	$return[] = \ElggMenuItem::factory([
421		'name' => 'security',
422		'href' => 'admin/security',
423		'text' => elgg_echo('admin:security'),
424		'priority' => 30,
425		'section' => 'configure',
426	]);
427
428	$return[] = \ElggMenuItem::factory([
429		'name' => 'configure_utilities',
430		'text' => elgg_echo('admin:configure_utilities'),
431		'priority' => 600,
432		'section' => 'configure',
433	]);
434	$return[] = \ElggMenuItem::factory([
435		'name' => 'configure_utilities:maintenance',
436		'text' => elgg_echo('admin:configure_utilities:maintenance'),
437		'href' => 'admin/configure_utilities/maintenance',
438		'section' => 'configure',
439		'parent_name' => 'configure_utilities',
440	]);
441	$return[] = \ElggMenuItem::factory([
442		'name' => 'configure_utilities:menu_items',
443		'text' => elgg_echo('admin:configure_utilities:menu_items'),
444		'href' => 'admin/configure_utilities/menu_items',
445		'section' => 'configure',
446		'parent_name' => 'configure_utilities',
447	]);
448	$return[] = \ElggMenuItem::factory([
449		'name' => 'configure_utilities:robots',
450		'text' => elgg_echo('admin:configure_utilities:robots'),
451		'href' => 'admin/configure_utilities/robots',
452		'section' => 'configure',
453		'parent_name' => 'configure_utilities',
454	]);
455
456	// information
457	$return[] = \ElggMenuItem::factory([
458		'name' => 'information:performance',
459		'href' => 'admin/performance',
460		'text' => elgg_echo('admin:performance'),
461		'section' => 'information',
462		'priority' => 70,
463	]);
464	$return[] = \ElggMenuItem::factory([
465		'name' => 'information:security',
466		'href' => 'admin/security/information',
467		'text' => elgg_echo('admin:security'),
468		'section' => 'information',
469		'priority' => 60,
470	]);
471	$return[] = \ElggMenuItem::factory([
472		'name' => 'statistics',
473		'href' => 'admin/statistics',
474		'text' => elgg_echo('admin:statistics'),
475		'section' => 'information',
476		'priority' => 80,
477	]);
478	$return[] = \ElggMenuItem::factory([
479		'name' => 'server',
480		'href' => 'admin/server',
481		'text' => elgg_echo('admin:server'),
482		'section' => 'information',
483		'priority' => 50,
484	]);
485
486	return $return;
487}
488
489/**
490 * Register plugin settings menu items for the admin page menu
491 *
492 * @note Plugin settings are alphabetically sorted in the submenu
493 *
494 * @param \Elgg\Hook $hook 'register' 'menu:page'
495 * @return array
496 *
497 * @internal
498 * @since 3.0
499 */
500function _elgg_admin_page_menu_plugin_settings(\Elgg\Hook $hook) {
501	if (!elgg_in_context('admin') || !elgg_is_admin_logged_in()) {
502		return;
503	}
504
505	// plugin settings
506	$active_plugins = elgg_get_plugins('active');
507	if (empty($active_plugins)) {
508		// nothing added because no items
509		return;
510	}
511
512	$plugins_with_settings = [];
513
514	foreach ($active_plugins as $plugin) {
515		$plugin_id = $plugin->getID();
516
517		if (!elgg_view_exists("plugins/{$plugin_id}/settings") ) {
518			continue;
519		}
520		$plugin_name = $plugin->getDisplayName();
521		$plugins_with_settings[$plugin_name] = [
522			'name' => $plugin_id,
523			'href' => "admin/plugin_settings/$plugin_id",
524			'text' => $plugin_name,
525			'parent_name' => 'plugin_settings',
526			'section' => 'configure',
527		];
528	}
529
530	if (empty($plugins_with_settings)) {
531		return;
532	}
533
534	$return = $hook->getValue();
535
536	$return[] = \ElggMenuItem::factory([
537		'name' => 'plugin_settings',
538		'text' => elgg_echo('admin:plugin_settings'),
539		'section' => 'configure',
540	]);
541
542	ksort($plugins_with_settings);
543	$priority = 0;
544	foreach ($plugins_with_settings as $plugin_item) {
545		$priority += 10;
546		$plugin_item['priority'] = $priority;
547		$return[] = \ElggMenuItem::factory($plugin_item);
548	}
549
550	return $return;
551}
552
553/**
554 * Register menu items to the bulk actions for unvalidated users
555 *
556 * @elgg_plugin_hook register menu:user:unvalidated:bulk
557 *
558 * @param \Elgg\Hook $hook 'register' 'menu:user:unvalidated:bulk'
559 *
560 * @return void|ElggMenuItem[]
561 *
562 * @since 3.0
563 * @internal
564 */
565function _elgg_admin_user_unvalidated_bulk_menu(\Elgg\Hook $hook) {
566
567	if (!elgg_is_admin_logged_in()) {
568		return;
569	}
570
571	$return = $hook->getValue();
572
573	$return[] = ElggMenuItem::factory([
574		'name' => 'select_all',
575		'text' => elgg_view('input/checkbox', [
576			'name' => 'select_all',
577			'label' => elgg_echo('all'),
578			'id' => 'admin-users-unvalidated-bulk-select',
579		]),
580		'href' => false,
581		'priority' => 100,
582		'deps' => 'admin/users/unvalidated',
583	]);
584
585	$return[] = ElggMenuItem::factory([
586		'id' => 'admin-users-unvalidated-bulk-validate',
587		'name' => 'bulk_validate',
588		'text' => elgg_echo('validate'),
589		'href' => 'action/admin/user/bulk/validate',
590		'confirm' => true,
591		'priority' => 400,
592		'section' => 'right',
593		'deps' => 'admin/users/unvalidated',
594	]);
595
596	$return[] = ElggMenuItem::factory([
597		'id' => 'admin-users-unvalidated-bulk-delete',
598		'name' => 'bulk_delete',
599		'text' => elgg_echo('delete'),
600		'href' => 'action/admin/user/bulk/delete',
601		'confirm' => elgg_echo('deleteconfirm:plural'),
602		'priority' => 500,
603		'section' => 'right',
604		'deps' => 'admin/users/unvalidated',
605	]);
606
607	return $return;
608}
609
610/**
611 * Handle admin pages.  Expects corresponding views as admin/section/subsection
612 *
613 * @param array $page Array of pages
614 *
615 * @return bool
616 * @internal
617 */
618function _elgg_admin_page_handler($page) {
619	elgg_admin_gatekeeper();
620	elgg_set_context('admin');
621
622	elgg_unregister_external_file('css', 'elgg');
623	elgg_require_js('elgg/admin');
624
625	// default to dashboard
626	if (!isset($page[0]) || empty($page[0])) {
627		$page = ['dashboard'];
628	}
629
630	// was going to fix this in the page_handler() function but
631	// it's commented to explicitly return a string if there's a trailing /
632	if (empty($page[count($page) - 1])) {
633		array_pop($page);
634	}
635
636	$vars = ['page' => $page];
637
638	// special page for plugin settings since we create the form for them
639	if ($page[0] == 'plugin_settings') {
640		if (isset($page[1]) && (elgg_view_exists("plugins/{$page[1]}/settings"))) {
641			$view = 'admin/plugin_settings';
642			$plugin = elgg_get_plugin_from_id($page[1]);
643			$vars['plugin'] = $plugin; // required for plugin settings backward compatibility
644			$vars['entity'] = $plugin;
645
646			$title = elgg_echo("admin:{$page[0]}") . ': ' . $plugin->getDisplayName();
647		} else {
648			throw new \Elgg\PageNotFoundException();
649		}
650	} else {
651		$view = 'admin/' . implode('/', $page);
652		$title = elgg_echo("admin:{$page[0]}");
653		if (count($page) > 1) {
654			$title .= ' : ' . elgg_echo('admin:' .  implode(':', $page));
655		}
656	}
657
658	// gets content and prevents direct access to 'components' views
659	if ($page[0] == 'components' || !($content = elgg_view($view, $vars))) {
660		throw new \Elgg\PageNotFoundException();
661	}
662
663	$body = elgg_view_layout('admin', ['content' => $content, 'title' => $title]);
664	echo elgg_view_page($title, $body, 'admin');
665	return true;
666}
667
668/**
669 * When in maintenance mode, should the given URL be handled normally?
670 *
671 * @param string $current_url Current page URL
672 * @return bool
673 *
674 * @internal
675 */
676function _elgg_admin_maintenance_allow_url($current_url) {
677	$site_path = preg_replace('~^https?~', '', elgg_get_site_url());
678	$current_path = preg_replace('~^https?~', '', $current_url);
679	if (0 === elgg_strpos($current_path, $site_path)) {
680		$current_path = ($current_path === $site_path) ? '' : elgg_substr($current_path, elgg_strlen($site_path));
681	} else {
682		$current_path = false;
683	}
684
685	// allow plugins to control access for specific URLs/paths
686	$params = [
687		'current_path' => $current_path,
688		'current_url' => $current_url,
689	];
690	return (bool) elgg_trigger_plugin_hook('maintenance:allow', 'url', $params, false);
691}
692
693/**
694 * Handle requests when in maintenance mode
695 *
696 * @param \Elgg\Hook $hook 'route', 'all'
697 *
698 * @return void|false
699 *
700 * @internal
701 */
702function _elgg_admin_maintenance_handler(\Elgg\Hook $hook) {
703	if (elgg_is_admin_logged_in()) {
704		return;
705	}
706
707	$info = $hook->getValue();
708
709	if ($info['identifier'] == 'action' && $info['segments'][0] == 'login') {
710		return;
711	}
712
713	if (_elgg_admin_maintenance_allow_url(current_page_url())) {
714		return;
715	}
716
717	elgg_unregister_plugin_hook_handler('register', 'menu:login', '_elgg_login_menu_setup');
718
719	echo elgg_view_resource('maintenance');
720
721	return false;
722}
723
724/**
725 * Prevent non-admins from using actions
726 *
727 * @param \Elgg\Hook $hook 'action', 'all'
728 *
729 * @return bool
730 * @internal
731 */
732function _elgg_admin_maintenance_action_check(\Elgg\Hook $hook) {
733	if (elgg_is_admin_logged_in()) {
734		return true;
735	}
736
737	if ($hook->getType() == 'login') {
738		$username = get_input('username');
739
740		$user = get_user_by_username($username);
741
742		if (!$user) {
743			$users = get_user_by_email($username);
744			if (!empty($users)) {
745				$user = $users[0];
746			}
747		}
748
749		if ($user && $user->isAdmin()) {
750			return true;
751		}
752	}
753
754	if (_elgg_admin_maintenance_allow_url(current_page_url())) {
755		return true;
756	}
757
758	register_error(elgg_echo('actionunauthorized'));
759
760	return false;
761}
762
763/**
764 * Adds default admin widgets to the admin dashboard.
765 *
766 * @param \Elgg\Event $event 'make_admin', 'user'
767 *
768 * @return void
769 * @internal
770 */
771function _elgg_add_admin_widgets(\Elgg\Event $event) {
772	$user = $event->getObject();
773
774	elgg_call(ELGG_IGNORE_ACCESS, function() use ($user) {
775		// check if the user already has widgets
776		if (elgg_get_widgets($user->guid, 'admin')) {
777			return;
778		}
779
780		// In the form column => array of handlers in order, top to bottom
781		$adminWidgets = [
782			1 => ['control_panel', 'admin_welcome'],
783			2 => ['online_users', 'new_users', 'content_stats'],
784		];
785
786		foreach ($adminWidgets as $column => $handlers) {
787			foreach ($handlers as $position => $handler) {
788				$guid = elgg_create_widget($user->getGUID(), $handler, 'admin');
789				if ($guid !== false) {
790					$widget = get_entity($guid);
791					/* @var \ElggWidget $widget */
792					$widget->move($column, $position);
793				}
794			}
795		}
796	});
797}
798
799/**
800 * Add the current site admins to the subscribers when making/removing an admin user
801 *
802 * @param \Elgg\Hook $hook 'get', 'subscribers'
803 *
804 * @return void|array
805 */
806function _elgg_admin_get_admin_subscribers_admin_action(\Elgg\Hook $hook) {
807
808	if (!_elgg_config()->security_notify_admins) {
809		return;
810	}
811
812	$event = $hook->getParam('event');
813	if (!$event instanceof \Elgg\Notifications\SubscriptionNotificationEvent) {
814		return;
815	}
816
817	if (!in_array($event->getAction(), ['make_admin', 'remove_admin'])) {
818		return;
819	}
820
821	$user = $event->getObject();
822	if (!$user instanceof \ElggUser) {
823		return;
824	}
825
826	/* @var $admin_batch \Elgg\BatchResult */
827	$admin_batch = elgg_get_admins([
828		'limit' => false,
829		'wheres' => [
830			function (QueryBuilder $qb, $main_alias) use ($user) {
831				return $qb->compare("{$main_alias}.guid", '!=', $user->guid, ELGG_VALUE_GUID);
832			},
833		],
834		'batch' => true,
835	]);
836
837	$return_value = $hook->getValue();
838
839	/* @var $admin \ElggUser */
840	foreach ($admin_batch as $admin) {
841		$return_value[$admin->guid] = ['email'];
842	}
843
844	return $return_value;
845}
846
847/**
848 * Prepare the notification content for site admins about making a site admin
849 *
850 * @param \Elgg\Hook $hook 'prepare', 'notification:make_admin:user:'
851 *
852 * @return void|\Elgg\Notifications\Notification
853 */
854function _elgg_admin_prepare_admin_notification_make_admin(\Elgg\Hook $hook) {
855
856	$return_value = $hook->getValue();
857	if (!$return_value instanceof \Elgg\Notifications\Notification) {
858		return;
859	}
860
861	$recipient = $hook->getParam('recipient');
862	$object = $hook->getParam('object');
863	$actor = $hook->getParam('sender');
864	$language = $hook->getParam('language');
865
866	if (!($recipient instanceof ElggUser) || !($object instanceof ElggUser) || !($actor instanceof ElggUser)) {
867		return;
868	}
869
870	if ($recipient->getGUID() === $object->getGUID()) {
871		// recipient is the user being acted on, this is handled elsewhere
872		return;
873	}
874
875	$site = elgg_get_site_entity();
876
877	$return_value->subject = elgg_echo('admin:notification:make_admin:admin:subject', [$site->getDisplayName()], $language);
878	$return_value->body = elgg_echo('admin:notification:make_admin:admin:body', [
879		$recipient->getDisplayName(),
880		$actor->getDisplayName(),
881		$object->getDisplayName(),
882		$site->getDisplayName(),
883		$object->getURL(),
884		$site->getURL(),
885	], $language);
886
887	$return_value->url = elgg_normalize_url('admin/users/admins');
888
889	return $return_value;
890}
891
892/**
893 * Prepare the notification content for site admins about removing a site admin
894 *
895 * @param \Elgg\Hook $hook 'prepare', 'notification:remove_admin:user:user'
896 *
897 * @return void|\Elgg\Notifications\Notification
898 */
899function _elgg_admin_prepare_admin_notification_remove_admin(\Elgg\Hook $hook) {
900
901	$return_value = $hook->getValue();
902	if (!$return_value instanceof \Elgg\Notifications\Notification) {
903		return;
904	}
905
906	$recipient = $hook->getParam('recipient');
907	$object = $hook->getParam('object');
908	$actor = $hook->getParam('sender');
909	$language = $hook->getParam('language');
910
911	if (!($recipient instanceof ElggUser) || !($object instanceof ElggUser) || !($actor instanceof ElggUser)) {
912		return;
913	}
914
915	if ($recipient->getGUID() === $object->getGUID()) {
916		// recipient is the user being acted on, this is handled elsewhere
917		return;
918	}
919
920	$site = elgg_get_site_entity();
921
922	$return_value->subject = elgg_echo('admin:notification:remove_admin:admin:subject', [$site->getDisplayName()], $language);
923	$return_value->body = elgg_echo('admin:notification:remove_admin:admin:body', [
924		$recipient->getDisplayName(),
925		$actor->getDisplayName(),
926		$object->getDisplayName(),
927		$site->getDisplayName(),
928		$object->getURL(),
929		$site->getURL(),
930	], $language);
931
932	$return_value->url = elgg_normalize_url('admin/users/admins');
933
934	return $return_value;
935}
936
937/**
938 * Add the user to the subscribers when making/removing the admin role
939 *
940 * @param \Elgg\Hook $hook 'get', 'subscribers'
941 *
942 * @return void|array
943 */
944function _elgg_admin_get_user_subscriber_admin_action(\Elgg\Hook $hook) {
945
946	if (!_elgg_config()->security_notify_user_admin) {
947		return;
948	}
949
950	$event = $hook->getParam('event');
951	if (!$event instanceof \Elgg\Notifications\SubscriptionNotificationEvent) {
952		return;
953	}
954
955	if (!in_array($event->getAction(), ['make_admin', 'remove_admin'])) {
956		return;
957	}
958
959	$user = $event->getObject();
960	if (!$user instanceof \ElggUser) {
961		return;
962	}
963
964	$return_value = $hook->getValue();
965
966	$return_value[$user->guid] = ['email'];
967
968	return $return_value;
969}
970
971/**
972 * Prepare the notification content for the user being made as a site admins
973 *
974 * @param \Elgg\Hook $hook 'prepare', 'notification:make_admin:user:user'
975 *
976 * @return void|\Elgg\Notifications\Notification
977 */
978function _elgg_admin_prepare_user_notification_make_admin(\Elgg\Hook $hook) {
979
980	$return_value = $hook->getValue();
981	if (!$return_value instanceof \Elgg\Notifications\Notification) {
982		return;
983	}
984
985	$recipient = $hook->getParam('recipient');
986	$object = $hook->getParam('object');
987	$actor = $hook->getParam('sender');
988	$language = $hook->getParam('language');
989
990	if (!($recipient instanceof ElggUser) || !($object instanceof ElggUser) || !($actor instanceof ElggUser)) {
991		return;
992	}
993
994	if ($recipient->guid !== $object->guid) {
995		// recipient is some other user, this is handled elsewhere
996		return;
997	}
998
999	$site = elgg_get_site_entity();
1000
1001	$return_value->subject = elgg_echo('admin:notification:make_admin:user:subject', [$site->getDisplayName()], $language);
1002	$return_value->body = elgg_echo('admin:notification:make_admin:user:body', [
1003		$recipient->getDisplayName(),
1004		$actor->getDisplayName(),
1005		$site->getDisplayName(),
1006		$site->getURL(),
1007	], $language);
1008
1009	$return_value->url = elgg_normalize_url('admin');
1010
1011	return $return_value;
1012}
1013
1014/**
1015 * Prepare the notification content for the user being removed as a site admins
1016 *
1017 * @param \Elgg\Hook $hook 'prepare', 'notification:remove_admin:user:user'
1018 *
1019 * @return void|\Elgg\Notifications\Notification
1020 */
1021function _elgg_admin_prepare_user_notification_remove_admin(\Elgg\Hook $hook) {
1022
1023	$return_value = $hook->getValue();
1024	if (!$return_value instanceof \Elgg\Notifications\Notification) {
1025		return;
1026	}
1027
1028	$recipient = $hook->getParam('recipient');
1029	$object = $hook->getParam('object');
1030	$actor = $hook->getParam('sender');
1031	$language = $hook->getParam('language');
1032
1033	if (!($recipient instanceof ElggUser) || !($object instanceof ElggUser) || !($actor instanceof ElggUser)) {
1034		return;
1035	}
1036
1037	if ($recipient->getGUID() !== $object->getGUID()) {
1038		// recipient is some other user, this is handled elsewhere
1039		return;
1040	}
1041
1042	$site = elgg_get_site_entity();
1043
1044	$return_value->subject = elgg_echo('admin:notification:remove_admin:user:subject', [$site->getDisplayName()], $language);
1045	$return_value->body = elgg_echo('admin:notification:remove_admin:user:body', [
1046		$recipient->getDisplayName(),
1047		$actor->getDisplayName(),
1048		$site->getDisplayName(),
1049		$site->getURL(),
1050	], $language);
1051
1052	$return_value->url = '';
1053
1054	return $return_value;
1055}
1056
1057/**
1058 * Add menu items to the filter menu on the admin upgrades page
1059 *
1060 * @param \Elgg\Hook $hook 'register', 'menu:filter:admin/upgrades'
1061 *
1062 * @return MenuItems
1063 * @internal
1064 */
1065function _elgg_admin_upgrades_menu(\Elgg\Hook $hook) {
1066
1067	$result = $hook->getValue();
1068
1069	$result[] = ElggMenuItem::factory([
1070		'name' => 'pending',
1071		'text' => elgg_echo('admin:upgrades:menu:pending'),
1072		'href' => 'admin/upgrades',
1073		'priority' => 100,
1074	]);
1075
1076	$result[] = ElggMenuItem::factory([
1077		'name' => 'completed',
1078		'text' => elgg_echo('admin:upgrades:menu:completed'),
1079		'href' => 'admin/upgrades/finished',
1080		'priority' => 200,
1081	]);
1082
1083	$result[] = ElggMenuItem::factory([
1084		'name' => 'db',
1085		'text' => elgg_echo('admin:upgrades:menu:db'),
1086		'href' => 'admin/upgrades/db',
1087		'priority' => 300,
1088	]);
1089
1090	return $result;
1091}
1092
1093/**
1094 * Check if new users need to be validated by an administrator
1095 *
1096 * @param \Elgg\Hook $hook 'register', 'user'
1097 *
1098 * @return void
1099 * @internal
1100 * @since 3.2
1101 */
1102function _elgg_admin_check_admin_validation(\Elgg\Hook $hook) {
1103
1104	if (!(bool) elgg_get_config('require_admin_validation')) {
1105		return;
1106	}
1107
1108	$user = $hook->getUserParam();
1109	if (!$user instanceof ElggUser) {
1110		return;
1111	}
1112
1113	elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($user) {
1114
1115		if ($user->isEnabled()) {
1116			// disable the user until validation
1117			$user->disable('admin_validation_required', false);
1118		}
1119
1120		// set validation status
1121		$user->setValidationStatus(false);
1122
1123		// store a flag in session so we can forward the user correctly
1124		$session = elgg_get_session();
1125		$session->set('admin_validation', true);
1126
1127		if (elgg_get_config('admin_validation_notification') === 'direct') {
1128			_elgg_admin_notify_admins_pending_user_validation();
1129		}
1130	});
1131}
1132
1133/**
1134 * Prevent unvalidated users from logging in
1135 *
1136 * @param \Elgg\Event $event 'login:before', 'user'
1137 *
1138 * @return void
1139 * @throws LoginException
1140 * @internal
1141 * @since 3.2
1142 */
1143function _elgg_admin_user_validation_login_attempt(\Elgg\Event $event) {
1144
1145	if (!(bool) elgg_get_config('require_admin_validation')) {
1146		return;
1147	}
1148
1149	$user = $event->getObject();
1150	if (!$user instanceof ElggUser) {
1151		return;
1152	}
1153
1154	elgg_call(ELGG_SHOW_DISABLED_ENTITIES, function() use ($user) {
1155		if ($user->isEnabled() && $user->isValidated() !== false) {
1156			return;
1157		}
1158
1159		throw new LoginException(elgg_echo('LoginException:AdminValidationPending'));
1160	});
1161}
1162
1163/**
1164 * Send a notification to all admins that there are pending user validations
1165 *
1166 * @return void
1167 * @internal
1168 * @since 3.2
1169 */
1170function _elgg_admin_notify_admins_pending_user_validation() {
1171
1172	if (empty(elgg_get_config('admin_validation_notification'))) {
1173		return;
1174	}
1175
1176	$unvalidated_count = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() {
1177		return elgg_count_entities([
1178			'type' => 'user',
1179			'metadata_name_value_pairs' => [
1180				'validated' => 0,
1181			],
1182		]);
1183	});
1184	if (empty($unvalidated_count)) {
1185		// shouldn't be able to get here because this function is triggered when a user is marked as unvalidated
1186		return;
1187	}
1188
1189	$site = elgg_get_site_entity();
1190	$admins = elgg_get_admins([
1191		'limit' => false,
1192		'batch' => true,
1193	]);
1194
1195	$url = elgg_normalize_url('admin/users/unvalidated');
1196
1197	/* @var $admin ElggUser */
1198	foreach ($admins as $admin) {
1199		$user_setting = $admin->getPrivateSetting('admin_validation_notification');
1200		if (isset($user_setting) && !(bool) $user_setting) {
1201			continue;
1202		}
1203
1204		$subject = elgg_echo('admin:notification:unvalidated_users:subject', [$site->getDisplayName()], $admin->getLanguage());
1205		$body = elgg_echo('admin:notification:unvalidated_users:body', [
1206			$admin->getDisplayName(),
1207			$unvalidated_count,
1208			$site->getDisplayName(),
1209			$url,
1210		], $admin->getLanguage());
1211
1212		$params = [
1213			'action' => 'admin:unvalidated',
1214			'object' => $admin,
1215		];
1216		notify_user($admin->guid, $site->guid, $subject, $body, $params, ['email']);
1217	}
1218}
1219
1220/**
1221 * Save a setting related to admin approval of new users
1222 *
1223 * @param \Elgg\Hook $hook 'usersettings:save', 'user'
1224 *
1225 * @return void
1226 * @internal
1227 * @since 3.2
1228 */
1229function _elgg_admin_save_notification_setting(\Elgg\Hook $hook) {
1230
1231	$user = $hook->getUserParam();
1232	if (!$user instanceof ElggUser || !$user->isAdmin()) {
1233		return;
1234	}
1235
1236	$request = $hook->getParam('request');
1237	if (!$request instanceof \Elgg\Request) {
1238		return;
1239	}
1240
1241	$value = (bool) $request->getParam('admin_validation_notification', true);
1242	$user->setPrivateSetting('admin_validation_notification', $value);
1243}
1244
1245/**
1246 * Set the correct forward url after user registration
1247 *
1248 * @param \Elgg\Hook $hook 'response', 'action:register'
1249 *
1250 * @return void|ResponseBuilder
1251 * @internal
1252 * @since 3.2
1253 */
1254function _elgg_admin_set_registration_forward_url(\Elgg\Hook $hook) {
1255
1256	$response = $hook->getValue();
1257	if (!$response instanceof ResponseBuilder) {
1258		return;
1259	}
1260
1261	$session = elgg_get_session();
1262	if (!$session->get('admin_validation')) {
1263		return;
1264	}
1265
1266	// if other plugins already have set forwarding, don't do anything
1267	if (!empty($response->getForwardURL()) && $response->getForwardURL() !== REFERER) {
1268		return;
1269	}
1270
1271	$response->setForwardURL(elgg_generate_url('account:validation:pending'));
1272
1273	return $response;
1274}
1275
1276/**
1277 * Notify the user that their account is approved
1278 *
1279 * @param \Elgg\Event $event 'validate:after', 'user'
1280 *
1281 * @return void
1282 * @internal
1283 * @since 3.2
1284 */
1285function _elgg_admin_user_validation_notification(\Elgg\Event $event) {
1286
1287	if (!(bool) elgg_get_config('require_admin_validation')) {
1288		return;
1289	}
1290
1291	$user = $event->getObject();
1292	if (!$user instanceof ElggUser) {
1293		return;
1294	}
1295
1296	$site = elgg_get_site_entity();
1297
1298	$subject = elgg_echo('account:notification:validation:subject', [$site->getDisplayName()], $user->getLanguage());
1299	$body = elgg_echo('account:notification:validation:body', [
1300		$user->getDisplayName(),
1301		$site->getDisplayName(),
1302		$site->getURL(),
1303	], $user->getLanguage());
1304
1305	$params = [
1306		'action' => 'account:validated',
1307		'object' => $user,
1308	];
1309
1310	notify_user($user->guid, $site->guid, $subject, $body, $params, ['email']);
1311}
1312
1313/**
1314 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
1315 */
1316return function(\Elgg\EventsService $events) {
1317	$events->registerHandler('init', 'system', '_elgg_admin_init');
1318};
1319