1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8class Services_Group_Controller
9{
10	/**
11	 * Filters for $input->replaceFilters() used in the Services_Utilities()->setVars method
12	 *
13	 * @var array
14	 */
15	private $filters = [
16		'checked' => 'groupname',
17		'items' => 'groupname',
18		'name' => 'groupname',
19		'group' => 'groupname',
20		'desc' => 'striptags',
21		'home' => 'pagename',
22		'groupstracker' => 'int',
23		'userstracker' => 'int',
24		'registrationUsersFieldIds' => 'digitscolons',
25		'userChoice' => 'word',
26		'defcat' => 'int',
27		'theme' => 'themename',
28		'color' => 'striptags',
29		'usersfield' => 'int',
30		'groupfield' => 'int',
31		'expireAfter' => 'int',
32		'anniversary' => 'digits',    // format MMDD or DD - NB: this is not an integer
33		'prorateInterval' => 'word',
34		'user' => 'username'
35	];
36
37	/**
38	 * Admin groups "perform with checked" but with no action selected
39	 *
40	 * @param $input
41	 * @throws Services_Exception
42	 * @throws Exception
43	 */
44	public function action_no_action()
45	{
46		Services_Utilities::modalException(tra('No action was selected. Please select an action before clicking OK.'));
47	}
48
49	/**
50	 * Admin groups "perform with checked" and list item action to remove selected groups
51	 *
52	 * @param $input
53	 * @return array
54	 * @throws Exception
55	 * @throws Services_Exception
56	 * @throws Services_Exception_Denied
57	 */
58	function action_remove_groups($input)
59	{
60		Services_Exception_Denied::checkGlobal('admin');
61		$util = new Services_Utilities();
62		$userlib = TikiLib::lib('user');
63		//first pass - show confirm modal popup
64		if ($util->notConfirmPost()) {
65			$util->setVars($input, $this->filters, 'checked');
66			$extras = [];
67			if ($util->itemsCount > 0) {
68				$warnings = array_filter($util->items, function ($item) use ($userlib) {
69					$groups = array_map(function ($g) {
70						return $g['groupName'];
71					}, $userlib->get_included_container_groups($item, false));
72					return ! empty($groups);
73				});
74				if (! empty($warnings)) {
75					$extras["warning"] = tr('Some categories are managed by this group. Remove it will be irreversible.');
76				}
77
78				if (count($util->items) === 1) {
79					$msg = tra('Delete the following group?');
80				} else {
81					$msg = tra('Delete the following groups?');
82				}
83				return $util->confirm($msg, tra('Delete'), ["warning" => $extras]);
84			} else {
85				Services_Utilities::modalException(tra('No groups were selected. Please select one or more groups.'));
86			}
87			//after confirm submit - perform action and return success feedback
88		} elseif ($util->checkCsrf()) {
89			$util->setDecodedVars($input, $this->filters);
90			//filter out Admins group so it can't be deleted. Anonymous and Registered are protected from deletion in
91			//in the remove groups function
92			$fitems = array_diff($util->items, ['Admins']);
93			$notDeleted = array_intersect($util->items, ['Admins']);
94
95			$logslib = TikiLib::lib('logs');
96			$deleted = [];
97			foreach ($fitems as $group) {
98				$result = $userlib->remove_group($group);
99				if ($result) {
100					$logslib->add_log('admingroups', 'removed group ' . $group);
101					$deleted[] = $group;
102				} else {
103					$notDeleted[] = $group;
104				}
105			}
106			//prepare and send feedback
107			if (count($notDeleted) > 0) {
108				if (count($notDeleted) === 1) {
109					$msg1 = tr('The following group cannot be deleted:');
110				} else {
111					$msg1 = tr('The following groups cannot be deleted:');
112				}
113				$feedback1 = [
114					'tpl' => 'action',
115					'mes' => $msg1,
116					'items' => $notDeleted,
117				];
118				Feedback::error($feedback1);
119			}
120			if (count($deleted) > 0) {
121				if (count($deleted) === 1) {
122					$msg2 = tr('The following group has been deleted:');
123				} else {
124					$msg2 = tr('The following groups have been deleted:');
125				}
126				$feedback2 = [
127					'tpl' => 'action',
128					'mes' => $msg2,
129					'items' => $deleted,
130				];
131				Feedback::success($feedback2);
132			}
133			//return to page
134			return Services_Utilities::refresh($this->extra['referer']);
135		}
136	}
137
138	/**
139	 * Process add group form
140	 *
141	 * @param $input
142	 * @return array
143	 * @throws Exception
144	 * @throws Services_Exception
145	 * @throws Services_Exception_Denied
146	 */
147	function action_new_group($input)
148	{
149		Services_Exception_Denied::checkGlobal('admin');
150		$util = new Services_Utilities();
151		//first pass - show confirm modal popup
152		if ($util->notConfirmPost()) {
153			$util->setVars($input, $this->filters);
154			if (! empty($input['name'])) {
155				$newGroupName = trim($input->name->groupname());
156				$userlib = TikiLib::lib('user');
157				if ($userlib->group_exists($newGroupName)) {
158					Services_Utilities::modalException(tra('Group already exists'));
159				} else {
160					$msg = tr('Create the group %0?', $newGroupName);
161					return $util->confirm($msg, tra('Create'));
162				}
163			} else {
164				Services_Utilities::modalException(tra('Group name cannot be empty'));
165				return;
166			}
167
168			if (! empty($input['isTplGroup']) && ! empty($input["include_groups"])) {
169				Services_Utilities::modalException(tra('Template Group cannot inherit from other groups'));
170			}
171
172
173			//after confirm submit - perform action and return feedback
174		} elseif ($util->checkCsrf()) {
175			//set parameters
176			$util->setDecodedVars($input, $this->filters);
177			$params = $this->prepareParameters($util->extra);
178			$userlib = TikiLib::lib('user');
179			//add group and inclusions
180			$newGroupId = $userlib->add_group(
181				$params['name'],
182				$params['desc'],
183				$params['home'],
184				$params['userstracker'],
185				$params['groupstracker'],
186				$params['registrationUsersFieldIds'],
187				$params['userChoice'],
188				$params['defcat'],
189				$params['theme'],
190				$params['usersfield'],
191				$params['groupfield'],
192				'n',
193				$params['expireAfter'],
194				$params['emailPattern'],
195				$params['anniversary'],
196				$params['prorateInterval'],
197				$params['color'],
198				$params['isRole'],
199				$params['isTplGroup']
200			);
201			if (isset($util->extra['include_groups'])) {
202				foreach ($util->extra['include_groups'] as $include) {
203					if ($util->extra['name'] != $include) {
204						$userlib->group_inclusion($util->extra['name'], $include);
205					}
206				}
207				$groups = $userlib->get_group_info($util->extra['include_groups']);
208
209				$templateGroups = array_filter($groups, function ($item) {
210					return $item["isTplGroup"] == "y";
211				});
212				foreach ($templateGroups as $templateGroup) {
213					$categories = TikiLib::lib('categ')->get_managed_categories($templateGroup["id"]);
214					$managedIds = array_unique(array_map(function ($item) {
215						return $item["categId"];
216					}, $categories));
217
218					foreach ($managedIds as $managedId) {
219						TikiLib::lib('categ')->manage_sub_categories($managedId);
220					}
221				}
222			}
223
224			$logslib = TikiLib::lib('logs');
225			$logslib->add_log('admingroups', 'created group ' . $util->extra['name']);
226			//prepare feedback
227			if ($newGroupId) {
228				$feedback1 = [
229					'tpl' => 'action',
230					'mes' => tr('Group %0 (ID %1) successfully created', $util->extra['name'], $newGroupId),
231				];
232				Feedback::success($feedback1);
233			} else {
234				$feedback2 = [
235					'tpl' => 'action',
236					'mes' => tr('Group %0 not created', $util->extra['name']),
237				];
238				Feedback::error($feedback2);
239			}
240			//return to page - take off query and anchor to ensure return to the first tab
241			return Services_Utilities::refresh($util->extra['referer'], 'queryAndAnchor');
242		} else {
243			//post CSRF error through js. can't just throw a services exception since the form started as a non-modal
244			//but confirmation is modal and js takes over after the confirmation is submitted
245			return ['error' => 'CSRF'];
246		}
247	}
248
249	/**
250	 * Process modify group form
251	 *
252	 * @param $input
253	 * @return array
254	 * @throws Exception
255	 * @throws Services_Exception
256	 * @throws Services_Exception_Denied
257	 */
258	function action_modify_group($input)
259	{
260		Services_Exception_Denied::checkGlobal('admin');
261		$userlib = TikiLib::lib('user');
262		$util = new Services_Utilities();
263		//first pass - show confirm modal popup
264		if ($util->notConfirmPost()) {
265			$util->setVars($input, $this->filters);
266
267			$children = $userlib->get_group_children_with_permissions($input['olgroup']);
268
269
270			if (! empty($input['name']) && isset($input['olgroup'])) {
271				$newGroupName = trim($input['name']);
272				$userlib = TikiLib::lib('user');
273				$users = $userlib->get_group_users($input['name']);
274				if (! empty($users) && $input["isRole"] == "on") {
275					Services_Utilities::modalException(tra('Role groups can\'t have users.'));
276				}
277				if (! empty($input['isTplGroup']) && ! empty($input["include_groups"]) && ! empty($input["include_groups"][0])) {
278					Services_Utilities::modalException(tra('Template Group cannot inherit from other groups'));
279				}
280				if (! empty($input['include_groups'])) {
281					$permissions = $userlib->get_group_permissions($input['olgroup']);
282					if (! empty($permissions)) {
283						$groups = $userlib->get_group_info($input['include_groups']->asArray());
284						foreach ($groups as $group) {
285							if ($group["isTplGroup"] == "y") {
286								Services_Utilities::modalException(tr('Template Group children cannot have permission: %0', $group["groupName"]));
287							}
288						}
289					}
290				}
291				$extras = [];
292				$oldIncluded = $userlib->get_included_groups($input['olgroup'], false);
293				$oldIncludes = array_diff($oldIncluded, $input['include_groups'] ? $input['include_groups']->toArray() : []);
294				$oldGroups = $userlib->get_group_info(array_values($oldIncludes));
295				$parentGroupsIds = array_map(function ($item) {
296					return $item["id"];
297				}, array_filter($oldGroups, function ($item) {
298					return $item["isTplGroup"] == "y";
299				}));
300
301				if (! empty($parentGroupsIds)) {
302					$extras["warning"] = tr('Some categories are managed by this group. Remove it will be irreversible.');
303				}
304
305
306
307				if (! empty($input['isTplGroup']) && $children["cant"] > 0) {
308					$names = [];
309					foreach ($children["data"] as $child) {
310						$names[] = $child["groupName"];
311					}
312					Services_Utilities::modalException(tr('Template Group children cannot have permission: %0', implode(",", $names)));
313				}
314				if (! empty($input['isTplGroup']) && ! empty($users)) {
315					Services_Utilities::modalException(tra('Template Group cannot have associated users'));
316				}
317				if ($input['olgroup'] !== $newGroupName && $userlib->group_exists($newGroupName)) {
318					Services_Utilities::modalException(tra('Group already exists'));
319				} else {
320					$msg = tr('Modify the group %0?', $newGroupName);
321					return $util->confirm($msg, tra('Modify'), $extras);
322				}
323			} else {
324				Services_Utilities::modalException(tra('Group name cannot be empty'));
325				return;
326			}
327			//after confirm submit - perform action and return success feedback
328		} elseif ($util->checkCsrf()) {
329			//set parameters
330			$util->setDecodedVars($input, $this->filters);
331			$params = $this->prepareParameters($util->extra);
332			$success = $userlib->change_group(
333				$params['olgroup'],
334				$params['name'],
335				$params['desc'],
336				$params['home'],
337				$params['userstracker'],
338				$params['groupstracker'],
339				$params['usersfield'],
340				$params['groupfield'],
341				$params['registrationUsersFieldIds'],
342				$params['userChoice'],
343				$params['defcat'],
344				$params['theme'],
345				'n',
346				$params['expireAfter'],
347				$params['emailPattern'],
348				$params['anniversary'],
349				$params['prorateInterval'],
350				$params['color'],
351				$params['isRole'],
352				$params['isTplGroup'],
353				$params['include_groups']
354			);
355
356
357
358			$logslib = TikiLib::lib('logs');
359			$logslib->add_log('admingroups', 'modified group ' . $params['olgroup'] . ' to ' . $params['name']);
360			//prepare feedback
361			if ($success) {
362				$feedback1 = [
363					'tpl' => 'action',
364					'mes' => tr('Group %0 successfully modified', $params['name']),
365				];
366				Feedback::success($feedback1);
367			} else {
368				$feedback2 = [
369					'tpl' => 'action',
370					'mes' => tr('Group %0 not modified', $params['name']),
371				];
372				Feedback::error($feedback2);
373			}
374			//return to page - strip query and anchor so that we return to the group listing
375			return Services_Utilities::refresh($util->extra['referer'], 'queryAndAnchor');
376		} else {
377			//post CSRF error through js. can't just throw a services exception since the form started as a non-modal
378			//but confirmation is modal and js takes over after the confirmation is submitted
379			return ['error' => 'CSRF'];
380		}
381	}
382
383	/**
384	 * Process add user to group action
385	 *
386	 * @param $input
387	 * @return array
388	 * @throws Exception
389	 * @throws Services_Exception
390	 * @throws Services_Exception_Denied
391	 */
392	function action_add_user($input)
393	{
394		Services_Exception_Denied::checkGlobal('admin');
395		$util = new Services_Utilities();
396		//first pass - show confirm modal popup
397		if ($util->notConfirmPost()) {
398			$util->setVars($input, $this->filters, 'user');
399			$userlib = TikiLib::lib('user');
400			$group = $userlib->get_group_info($input['group']);
401			if ($group["isRole"] == "y") {
402				Services_Utilities::modalException(tra('Role groups can\'t have users.'));
403			}
404			if ($util->itemsCount > 0) {
405				if ($util->itemsCount === 1) {
406					$msg = tr('Add the following user to group %0?', $input['group']);
407				} else {
408					$msg = tr('Add the following users to group %0?', $input['group']);
409				}
410				return $util->confirm(
411					$msg,
412					tra('Add'),
413					[
414						'fields' => [
415							[
416								'label' => tr('Please confirm this operation by typing your password'),
417								'field' => 'input',
418								'type' => 'password',
419								'name' => 'confirmpassword',
420								'placeholder' => tr('Password')
421							]
422						]
423					]
424				);
425			} else {
426				Services_Utilities::modalException(tra('One or more users must be selected'));
427			}
428			//after confirm submit - perform action and return success feedback
429		} elseif ($util->checkCsrf()) {
430			$userlib = TikiLib::lib('user');
431			$pass = $input->offsetGet('confirmpassword');
432			$user = isset($_SESSION['u_info']['login']) ? $_SESSION['u_info']['login'] : '';
433			$ret = $userlib->validate_user($user, $pass);
434			if (! $ret[0]) {
435				Services_Utilities::modalException(tra('Invalid password'));
436			}
437
438			$util->setDecodedVars($input, $this->filters);
439			$logslib = TikiLib::lib('logs');
440			foreach ($util->items as $user) {
441				$userlib->assign_user_to_group($user, $util->extra['group']);
442				$logslib->add_log('admingroups', 'added ' . $user . ' to ' . $util->extra['group']);
443			}
444			//prepare and send feedback
445			if (count($util->items) > 0) {
446				if (count($util->items) === 1) {
447					$msg = tr('The following user was added to group %0:', $util->extra['group']);
448				} else {
449					$msg = tr('The following users were added to group %0:', $util->extra['group']);
450				}
451				$feedback = [
452					'tpl' => 'action',
453					'mes' => $msg,
454					'items' => $util->items,
455				];
456				Feedback::success($feedback);
457			}
458			//return to page
459			return Services_Utilities::refresh($util->extra['referer']);
460		}
461	}
462
463
464	/**
465	 * Process ban user from group action
466	 *
467	 * @param $input
468	 * @return array
469	 * @throws Exception
470	 * @throws Services_Exception
471	 * @throws Services_Exception_Denied
472	 */
473	function action_ban_user($input)
474	{
475		Services_Exception_Denied::checkGlobal('admin');
476		$util = new Services_Utilities();
477		//first pass - show confirm modal popup
478		if ($util->notConfirmPost()) {
479			$util->setVars($input, $this->filters, 'user');
480			if ($util->itemsCount > 0) {
481				if ($util->itemsCount === 1) {
482					$msg = tr('Ban the following user from group %0?', $input['group']);
483				} else {
484					$msg = tr('Ban the following users from group %0?', $input['group']);
485				}
486				return $util->confirm(
487					$msg,
488					tra('Ban'),
489					[
490						'fields' => [
491							[
492								'label' => tr('Please confirm this operation by typing your password'),
493								'field' => 'input',
494								'type' => 'password',
495								'name' => 'confirmpassword',
496								'placeholder' => tr('Password')
497							]
498						]
499					]
500				);
501			} else {
502				Services_Utilities::modalException(tra('One or more users must be selected'));
503			}
504			//after confirm submit - perform action and return success feedback
505		} elseif ($util->checkCsrf()) {
506			$userlib = TikiLib::lib('user');
507			$pass = $input->offsetGet('confirmpassword');
508			$user = isset($_SESSION['u_info']['login']) ? $_SESSION['u_info']['login'] : '';
509			$ret = $userlib->validate_user($user, $pass);
510			if (! $ret[0]) {
511				Feedback::error(tra('Invalid password.'));
512				return Services_Utilities::closeModal();
513			}
514
515			$util->setDecodedVars($input, $this->filters);
516			$logslib = TikiLib::lib('logs');
517			foreach ($util->items as $user) {
518				$userlib->ban_user_from_group($user, $util->extra['group']);
519				$logslib->add_log('admingroups', 'banned ' . $user . ' from ' . $util->extra['group']);
520			}
521			//prepare and send feedback
522			if ($util->itemsCount > 0) {
523				if ($util->itemsCount === 1) {
524					$msg = tr('The following user was banned from group %0:', $util->extra['group']);
525				} else {
526					$msg = tr('The following users were banned from group %0:', $util->extra['group']);
527				}
528				$feedback = [
529					'tpl' => 'action',
530					'mes' => $msg,
531					'items' => $util->items,
532				];
533				Feedback::success($feedback);
534			}
535			//return to page
536			return Services_Utilities::refresh($util->extra['referer']);
537		}
538	}
539
540	/**
541	 * Process unban user from group action
542	 *
543	 * @param $input
544	 * @return array
545	 * @throws Exception
546	 * @throws Services_Exception
547	 * @throws Services_Exception_Denied
548	 */
549	function action_unban_user($input)
550	{
551		Services_Exception_Denied::checkGlobal('admin');
552		$util = new Services_Utilities();
553		//first pass - show confirm modal popup
554		if ($util->notConfirmPost()) {
555			$util->setVars($input, $this->filters, 'user');
556			if ($util->itemsCount > 0) {
557				if ($util->itemsCount === 1) {
558					$msg = tr('Unban the following user from group %0?', $input['group']);
559				} else {
560					$msg = tr('Unban the following users from group %0?', $input['group']);
561				}
562				return $util->confirm($msg, tra('Unban'));
563			} else {
564				Services_Utilities::modalException(tra('One or more users must be selected'));
565			}
566			//after confirm submit - perform action and return success feedback
567		} elseif ($util->checkCsrf()) {
568			$util->setDecodedVars($input, $this->filters);
569			$userlib = TikiLib::lib('user');
570			$logslib = TikiLib::lib('logs');
571			foreach ($util->items as $user) {
572				$userlib->unban_user_from_group($user, $util->extra['group']);
573				$logslib->add_log('admingroups', 'unbanned ' . $user . ' from ' . $util->extra['group']);
574			}
575			//prepare and send feedback
576			if ($util->itemsCount > 0) {
577				if (count($util->items) === 1) {
578					$msg = tr('The following user was unbanned from group %0:', $util->extra['group']);
579				} else {
580					$msg = tr('The following users were unbanned from group %0:', $util->extra['group']);
581				}
582				$feedback = [
583					'tpl' => 'action',
584					'mes' => $msg,
585					'items' => $util->items,
586				];
587				Feedback::success($feedback);
588			}
589			//return to page
590			return Services_Utilities::refresh($util->extra['referer']);
591		}
592	}
593
594	/**
595	 * Utility to prepare parameters for add_group and change group userlib functions
596	 *
597	 * @param array $extra
598	 * @return array
599	 * @throws Exception
600	 */
601	private function prepareParameters(array $extra)
602	{
603		$extra = new JitFilter($extra);
604		$extra->replaceFilters($this->filters);
605		$extra = $extra->asArray();
606		$extra['home'] = isset($extra['home']) ? $extra['home'] : '';
607		$extra['theme'] = isset($extra['theme']) ? $extra['theme'] : '';
608		$extra['color'] = isset($extra['color']) ? $extra['color'] : '';
609		$extra['defcat'] = ! empty($extra['defcat']) ? $extra['defcat'] : 0;
610		$extra['userChoice'] = isset($extra['userChoice']) && $extra['userChoice'] == 'on' ? 'y' : '';
611		$extra['expireAfter'] = empty($extra['expireAfter']) ? 0 : $extra['expireAfter'];
612		$extra['isRole'] = isset($extra['isRole']) && $extra['isRole'] == 'on' ? 'y' : '';
613		$extra['isTplGroup'] = isset($extra['isTplGroup']) && $extra['isTplGroup'] == 'on' ? 'y' : '';
614
615		$defaults = [
616			'groupstracker' => 0,
617			'groupfield' => 0,
618			'userstracker' => 0,
619			'usersfield' => 0,
620			'registrationUsersFieldIds' => ''
621		];
622		global $prefs;
623		$prefGroupTracker = isset($prefs['groupTracker']) and $prefs['groupTracker'] == 'y';
624		$prefUserTracker = isset($prefs['userTracker']) and $prefs['userTracker'] == 'y';
625		if (! empty($extra['groupstracker']) || ! empty($extra['userstracker'])) {
626			if ($prefGroupTracker || $prefUserTracker) {
627				$trklib = TikiLib::lib('trk');
628				$trackerlist = $trklib->list_trackers(0, -1, 'name_asc', '');
629				$trackers = $trackerlist['list'];
630				if ($prefGroupTracker && isset($extra['groupstracker']) && isset($trackers[$extra['groupstracker']])) {
631					$defaults['groupstracker'] = $extra['groupstracker'];
632					if (isset($extra['groupfield']) && $extra['groupfield']) {
633						$defaults['groupfield'] = $extra['groupfield'];
634					}
635				}
636				if ($prefUserTracker && isset($extra['userstracker']) && isset($trackers[$extra['userstracker']])) {
637					$defaults['userstracker'] = $extra['userstracker'];
638				}
639				if (isset($extra['usersfield']) && $extra['usersfield']) {
640					$defaults['usersfield'] = $extra['usersfield'];
641				}
642				if (! empty($extra['registrationUsersFieldIds'])) {
643					$defaults['registrationUsersFieldIds'] = $extra['registrationUsersFieldIds'];
644				}
645			}
646		}
647		$ret = array_merge($extra, $defaults);
648		return $ret;
649	}
650}
651