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