PageRenderTime 61ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/web/system/UsersModule/Controller/AdminController.php

https://github.com/antoniom/core
PHP | 2540 lines | 1949 code | 191 blank | 400 comment | 335 complexity | 0dc34229062caee81f8f605a41ba28c9 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0, MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Copyright Zikula Foundation 2009 - Zikula Application Framework
  4. *
  5. * This work is contributed to the Zikula Foundation under one or more
  6. * Contributor Agreements and licensed to You under the following license:
  7. *
  8. * @license GNU/LGPLv3 (or at your option, any later version).
  9. * @package Zikula
  10. * @subpackage Users
  11. *
  12. * Please see the NOTICE file distributed with this source code for further
  13. * information regarding copyright and licensing.
  14. */
  15. namespace UsersModule\Controller;
  16. use Zikula\Core\Event\GenericEvent;
  17. use Zikula_View, SecurityUtil, ModUtil, UserUtil, DataUtil, DateUtil, LogUtil, System, FileUtil;
  18. use DateTime, DateTimeZone, Exception;
  19. use Zikula_Session;
  20. use \Zikula\Framework\Exception\ForbiddenException;
  21. use UsersModule\Constants as UsersConstant;
  22. use Zikula\Framework\Exception\FatalException;
  23. use Zikula\Core\Hook\ProcessHook;
  24. use Zikula\Core\Hook\ValidationProviders;
  25. use Zikula\Core\Hook\ValidationHook;
  26. /**
  27. * Administrator-initiated actions for the Users module.
  28. */
  29. class AdminController extends \Zikula\Framework\Controller\AbstractController
  30. {
  31. /**
  32. * Post initialise.
  33. *
  34. * Run after construction.
  35. *
  36. * @return void
  37. */
  38. protected function postInitialize()
  39. {
  40. // Disable caching by default.
  41. $this->view->setCaching(Zikula_View::CACHE_DISABLED);
  42. }
  43. /**
  44. * Determines if the user currently logged in has administrative access for the Users module.
  45. *
  46. * @return bool True if the current user is logged in and has administrator access for the Users
  47. * module; otherwise false.
  48. */
  49. private function currentUserIsAdmin()
  50. {
  51. return UserUtil::isLoggedIn() && SecurityUtil::checkPermission('Users::', '::', ACCESS_ADMIN);
  52. }
  53. /**
  54. * Redirects users to the "view" page.
  55. *
  56. * @return string HTML string containing the rendered view template.
  57. */
  58. public function indexAction()
  59. {
  60. // Security check will be done in view()
  61. return $this->redirect(ModUtil::url($this->name, 'admin', 'view'));
  62. }
  63. /**
  64. * Shows all items and lists the administration options.
  65. *
  66. * Parameters passed via GET:
  67. * --------------------------
  68. * numeric startnum The ordinal number at which to start displaying user records.
  69. * string letter The first letter of the user names to display.
  70. * string sort The field on which to sort the data.
  71. * string sortdir Either 'ASC' for an ascending sort (a to z) or 'DESC' for a descending sort (z to a).
  72. *
  73. * Parameters passed via POST:
  74. * ---------------------------
  75. * None.
  76. *
  77. * Parameters passed via SESSION:
  78. * ------------------------------
  79. * None.
  80. *
  81. * @return string HTML string containing the rendered template.
  82. *
  83. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have moderate access, or if the method of accessing this function is improper.
  84. */
  85. public function viewAction()
  86. {
  87. if (!SecurityUtil::checkPermission('Users::', '::', ACCESS_MODERATE)) {
  88. throw new \Zikula\Framework\Exception\ForbiddenException();
  89. }
  90. // we need this value multiple times, so we keep it
  91. $itemsPerPage = $this->getVar(UsersConstant::MODVAR_ITEMS_PER_PAGE);
  92. $sort = $this->request->query->get('sort', isset($args['sort']) ? $args['sort'] : 'uname');
  93. $sortDirection = $this->request->query->get('sortdir', isset($args['sortdir']) ? $args['sortdir'] : 'ASC');
  94. $sortArgs = array(
  95. $sort => $sortDirection,
  96. );
  97. if (!isset($sortArgs['uname'])) {
  98. $sortArgs['uname'] = 'ASC';
  99. }
  100. $getAllArgs = array(
  101. 'startnum' => $this->request->query->get('startnum', isset($args['startnum']) ? $args['startnum'] : null),
  102. 'numitems' => $itemsPerPage,
  103. 'letter' => $this->request->query->get('letter', isset($args['letter']) ? $args['letter'] : null),
  104. 'sort' => $sortArgs,
  105. );
  106. // Get all users as specified by the arguments.
  107. $userList = ModUtil::apiFunc($this->name, 'user', 'getAll', $getAllArgs);
  108. // Get all groups
  109. $groups = ModUtil::apiFunc('GroupsModule', 'user', 'getall');
  110. // check what groups can access the user
  111. $userGroupsAccess = array();
  112. $groupsArray = array();
  113. $canSeeGroups = !empty($groups);
  114. foreach ($groups as $group) {
  115. $userGroupsAccess[$group['gid']] = array('gid' => $group['gid']);
  116. // rewrite the groups array with the group id as key and the group name as value
  117. $groupsArray[$group['gid']] = array('name' => DataUtil::formatForDisplayHTML($group['name']));
  118. }
  119. // Get the current user's uid
  120. $currentUid = UserUtil::getVar('uid');
  121. // Determine the available options
  122. $currentUserHasReadAccess = SecurityUtil::checkPermission($this->name . '::', 'ANY', ACCESS_READ);
  123. $currentUserHasModerateAccess = SecurityUtil::checkPermission($this->name . '::', 'ANY', ACCESS_MODERATE);
  124. $currentUserHasEditAccess = SecurityUtil::checkPermission($this->name . '::', 'ANY', ACCESS_EDIT);
  125. $currentUserHasDeleteAccess = SecurityUtil::checkPermission($this->name . '::', 'ANY', ACCESS_DELETE);
  126. $availableOptions = array(
  127. 'lostUsername' => $currentUserHasModerateAccess,
  128. 'lostPassword' => $currentUserHasModerateAccess,
  129. 'toggleForcedPasswordChange' => $currentUserHasEditAccess,
  130. 'modify' => $currentUserHasEditAccess,
  131. 'deleteUsers' => $currentUserHasDeleteAccess,
  132. );
  133. // Loop through each returned item adding in the options that the user has over
  134. // each item based on the permissions the user has.
  135. foreach ($userList as $key => $userObj) {
  136. $isCurrentUser = ($userObj['uid'] == $currentUid);
  137. $isGuestAccount = ($userObj['uid'] == 1);
  138. $isAdminAccount = ($userObj['uid'] == 2);
  139. $hasUsersPassword = (!empty($userObj['pass']) && ($userObj['pass'] != UsersConstant::PWD_NO_USERS_AUTHENTICATION));
  140. $currentUserHasReadAccess = !$isGuestAccount && SecurityUtil::checkPermission($this->name . '::', "{$userObj['uname']}::{$userObj['uid']}", ACCESS_READ);
  141. $currentUserHasModerateAccess = !$isGuestAccount && SecurityUtil::checkPermission($this->name . '::', "{$userObj['uname']}::{$userObj['uid']}", ACCESS_MODERATE);
  142. $currentUserHasEditAccess = !$isGuestAccount && SecurityUtil::checkPermission($this->name . '::', "{$userObj['uname']}::{$userObj['uid']}", ACCESS_EDIT);
  143. $currentUserHasDeleteAccess = !$isGuestAccount && !$isAdminAccount && !$isCurrentUser && SecurityUtil::checkPermission($this->name . '::', "{$userObj['uname']}::{$userObj['uid']}", ACCESS_DELETE);
  144. $userList[$key]['options'] = array(
  145. 'lostUsername' => $currentUserHasModerateAccess,
  146. 'lostPassword' => $currentUserHasModerateAccess,
  147. 'toggleForcedPasswordChange'=> $hasUsersPassword && $currentUserHasEditAccess,
  148. 'modify' => $currentUserHasEditAccess,
  149. 'deleteUsers' => $currentUserHasDeleteAccess,
  150. );
  151. if ($isGuestAccount) {
  152. $userList[$key]['userGroupsView'] = array();
  153. } else {
  154. // get user groups
  155. $userGroups = ModUtil::apiFunc('GroupsModule', 'user', 'getusergroups', array(
  156. 'uid' => $userObj['uid'],
  157. 'clean' => 1
  158. ));
  159. // we need an associative array by the key to compare with the groups that the user can see
  160. $userGroupsByKey = array();
  161. foreach ($userGroups as $userGroup) {
  162. $userGroupsByKey[$userGroup['gid']] = array('gid' => $userGroup['gid']);
  163. }
  164. $userList[$key]['userGroupsView'] = array_intersect_key($userGroupsAccess, $userGroupsByKey);
  165. }
  166. // format the dates
  167. if (!empty($userObj['user_regdate']) && ($userObj['user_regdate'] != '0000-00-00 00:00:00') && ($userObj['user_regdate'] != '1970-01-01 00:00:00')) {
  168. $userList[$key]['user_regdate'] = DateUtil::formatDatetime($userObj['user_regdate'], $this->__('%m-%d-%Y'));
  169. } else {
  170. $userList[$key]['user_regdate'] = '---';
  171. }
  172. if (!empty($userObj['lastlogin']) && ($userObj['lastlogin'] != '0000-00-00 00:00:00') && ($userObj['lastlogin'] != '1970-01-01 00:00:00')) {
  173. $userList[$key]['lastlogin'] = DateUtil::formatDatetime($userObj['lastlogin'], $this->__('%m-%d-%Y'));
  174. } else {
  175. $userList[$key]['lastlogin'] = '---';
  176. }
  177. $userList[$key]['_Users_mustChangePassword'] = (isset($userObj['__ATTRIBUTES__']) && isset($userObj['__ATTRIBUTES__']['_Users_mustChangePassword']) && $userObj['__ATTRIBUTES__']['_Users_mustChangePassword']);
  178. }
  179. $pager = array(
  180. 'numitems' => ModUtil::apiFunc($this->name, 'user', 'countItems', array('letter' => $getAllArgs['letter'])),
  181. 'itemsperpage' => $itemsPerPage,
  182. );
  183. // Assign the items to the template & return output
  184. return $this->response($this->view->assign('usersitems', $userList)
  185. ->assign('pager', $pager)
  186. ->assign('allGroups', $groupsArray)
  187. ->assign('canSeeGroups', $canSeeGroups)
  188. ->assign('sort', $sort)
  189. ->assign('sortdir', $sortDirection)
  190. ->assign('available_options', $availableOptions)
  191. ->fetch('Admin/view.tpl'));
  192. }
  193. /**
  194. * Add a new user to the system.
  195. *
  196. * Parameters passed via GET:
  197. * --------------------------
  198. * None.
  199. *
  200. * Parameters passed via POST:
  201. * ---------------------------
  202. * See the definition of {@link Users_Controller_FormData_NewUserForm}.
  203. *
  204. * Parameters passed via SESSION:
  205. * ------------------------------
  206. * None.
  207. *
  208. * @return string HTML string containing the rendered template.
  209. *
  210. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have add access, or if the method of accessing this function is improper.
  211. */
  212. public function newUserAction()
  213. {
  214. // The user must have ADD access to submit a new user record.
  215. if (!SecurityUtil::checkPermission($this->name . '::', '::', ACCESS_ADD)) {
  216. throw new \Zikula\Framework\Exception\ForbiddenException();
  217. }
  218. // When new user registration is disabled, the user must have ADMIN access instead of ADD access.
  219. if (!$this->getVar(UsersConstant::MODVAR_REGISTRATION_ENABLED, false) && !SecurityUtil::checkPermission($this->name . '::', '::', ACCESS_ADMIN)) {
  220. $registrationUnavailableReason = $this->getVar(UsersConstant::MODVAR_REGISTRATION_DISABLED_REASON, $this->__('Sorry! New user registration is currently disabled.'));
  221. $this->registerError($registrationUnavailableReason);
  222. // TODO - The home page typically does not display errors.
  223. return $this->redirect(System::getHomepageUrl());
  224. }
  225. $proceedToForm = true;
  226. $formData = new FormData\NewUserForm('users_newuser', $this->container);
  227. $errorFields = array();
  228. $errorMessages = array();
  229. if ($this->request->getMethod() == 'POST') {
  230. // Returning from a form POST operation. Process the input.
  231. $this->checkCsrfToken();
  232. $formData->setFromRequestCollection($this->request->request->all());
  233. $registrationArgs = array(
  234. 'checkMode' => 'new',
  235. 'emailagain' => $formData->getField('emailagain')->getData(),
  236. 'setpass' => (bool)$formData->getField('setpass')->getData(),
  237. 'antispamanswer' => '',
  238. );
  239. $registrationArgs['passagain'] = $registrationArgs['setpass'] ? $formData->getField('passagain')->getData() : '';
  240. $registrationInfo = array(
  241. 'uname' => $formData->getField('uname')->getData(),
  242. 'pass' => $registrationArgs['setpass'] ? $formData->getField('pass')->getData() : '',
  243. 'passreminder' => $registrationArgs['setpass'] ? $this->__('(Password provided by site administrator)') : '',
  244. 'email' => mb_strtolower($formData->getField('email')->getData()),
  245. );
  246. $registrationArgs['reginfo'] = $registrationInfo;
  247. $sendPass = $formData->getField('sendpass')->getData();
  248. if ($formData->isValid()) {
  249. $errorFields = ModUtil::apiFunc($this->name, 'registration', 'getRegistrationErrors', $registrationArgs);
  250. } else {
  251. $errorFields = $formData->getErrorMessages();
  252. }
  253. $event = new GenericEvent($registrationInfo, array(), new ValidationProviders());
  254. $this->dispatcher->dispatch('module.users.ui.validate_edit.new_user', $event);
  255. $validators = $event->getData();
  256. $hook = new ValidationHook($validators);
  257. $this->dispatchHooks('users.ui_hooks.user.validate_edit', $hook);
  258. $validators = $hook->getValidators();
  259. if (empty($errorFields) && !$validators->hasErrors()) {
  260. // TODO - Future functionality to suppress e-mail notifications, see ticket #2351
  261. //$currentUserEmail = UserUtil::getVar('email');
  262. //$adminNotifyEmail = $this->getVar('reg_notifyemail', '');
  263. //$adminNotification = (strtolower($currentUserEmail) != strtolower($adminNotifyEmail));
  264. $registeredObj = ModUtil::apiFunc($this->name, 'registration', 'registerNewUser', array(
  265. 'reginfo' => $registrationInfo,
  266. 'sendpass' => $sendPass,
  267. 'usernotification' => true,
  268. 'adminnotification' => true,
  269. ));
  270. if (isset($registeredObj) && $registeredObj) {
  271. $event = new GenericEvent($registeredObj);
  272. $this->dispatcher->dispatch('module.users.ui.process_edit.new_user', $event);
  273. $hook = new ProcessHook($registeredObj['uid']);
  274. $this->dispatchHooks('users.ui_hooks.user.process_edit', $hook);
  275. if ($registeredObj['activated'] == UsersConstant::ACTIVATED_PENDING_REG) {
  276. $this->registerStatus($this->__('Done! Created new registration application.'));
  277. } elseif (isset($registeredObj['activated'])) {
  278. $this->registerStatus($this->__('Done! Created new user account.'));
  279. } else {
  280. $this->registerError($this->__('Warning! New user information has been saved, however there may have been an issue saving it properly.'));
  281. }
  282. $proceedToForm = false;
  283. } else {
  284. $this->registerError($this->__('Error! Could not create the new user account or registration application.'));
  285. }
  286. }
  287. } elseif (!$this->request->getMethod() == 'GET') {
  288. throw new \Zikula\Framework\Exception\ForbiddenException();
  289. }
  290. if ($proceedToForm) {
  291. return $this->response($this->view->assign_by_ref('formData', $formData)
  292. ->assign('mode', 'new')
  293. ->assign('errorMessages', $errorMessages)
  294. ->assign('errorFields', $errorFields)
  295. ->fetch('Admin/newuser.tpl'));
  296. } else {
  297. return $this->redirect(ModUtil::url($this->name, 'admin', 'view'));
  298. }
  299. }
  300. /**
  301. * Renders a user search form used by both the search operation and the mail users operation.
  302. *
  303. * @param string $callbackFunc Either 'search' or 'mailUsers', indicating which operation is calling this function.
  304. *
  305. * @return string The rendered output from the template, appropriate for the indicated operation.
  306. */
  307. protected function renderSearchForm($callbackFunc = 'search')
  308. {
  309. // get group items
  310. $groups = ModUtil::apiFunc('GroupsModule', 'user', 'getAll');
  311. return $this->response($this->view->assign('groups', $groups)
  312. ->assign('callbackFunc', $callbackFunc)
  313. ->fetch('Admin/search.tpl'));
  314. }
  315. /**
  316. * Gathers the user input from a rendered search form, and also makes the appropriate hook calls.
  317. *
  318. * Parameters passed via GET:
  319. * --------------------------
  320. * None.
  321. *
  322. * Parameters passed via POST:
  323. * ---------------------------
  324. * string uname A fragment of a user name on which to search using an SQL LIKE clause. The user name will be
  325. * surrounded by wildcards.
  326. * integer ugroup A group id in which to search (only users who are members of the specified group are returned).
  327. * string email A fragment of an e-mail address on which to search using an SQL LIKE clause. The e-mail address
  328. * will be surrounded by wildcards.
  329. * string regdateafter An SQL date-time (in the form '1970-01-01 00:00:00'); only user accounts with a registration date
  330. * after the date specified will be returned.
  331. * string regdatebefore An SQL date-time (in the form '1970-01-01 00:00:00'); only user accounts with a registration date
  332. * before the date specified will be returned.
  333. * array dynadata An array of search values to be passed to the designated profile module. Only those user records
  334. * also satisfying the profile module's search of its dataare returned.
  335. *
  336. * Parameters passed via SESSION:
  337. * ------------------------------
  338. * None.
  339. *
  340. * @param string $callbackFunc Either 'search' or 'mailUsers', indicating which operation is calling this function.
  341. *
  342. * @return array|boolean An array of search results, which may be empty; false if the search was unsuccessful.
  343. */
  344. protected function getSearchResults($callbackFunc = 'search')
  345. {
  346. $findUsersArgs = array(
  347. 'uname' => $this->request->request->get('uname', null),
  348. 'email' => $this->request->request->get('email', null),
  349. 'ugroup' => $this->request->request->get('ugroup', null),
  350. 'regdateafter' => $this->request->request->get('regdateafter', null),
  351. 'regdatebefore' => $this->request->request->get('regdatebefore', null),
  352. );
  353. if ($callbackFunc == 'mailUsers') {
  354. $processEditEvent = $this->dispatcher->dispatch('users.mailuserssearch.process_edit', new GenericEvent(null, array(), $findUsersArgs));
  355. } else {
  356. $processEditEvent = $this->dispatcher->dispatch('users.search.process_edit', new GenericEvent(null, array(), $findUsersArgs));
  357. }
  358. $findUsersArgs = $processEditEvent->getData();
  359. // call the api
  360. return ModUtil::apiFunc($this->name, 'admin', 'findUsers', $findUsersArgs);
  361. }
  362. /**
  363. * Displays a user account search form, or the search results from a post.
  364. *
  365. * Parameters passed via GET:
  366. * --------------------------
  367. * None.
  368. *
  369. * Parameters passed via POST:
  370. * ---------------------------
  371. * See the definition of {@link getSearchResults()}.
  372. *
  373. * Parameters passed via SESSION:
  374. * ------------------------------
  375. * None.
  376. *
  377. * @return string HTML string containing the rendered template.
  378. *
  379. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have moderate access, or if the method of accessing this function is improper.
  380. */
  381. public function searchAction()
  382. {
  383. if (!SecurityUtil::checkPermission($this->name . '::', '::', ACCESS_MODERATE)) {
  384. throw new \Zikula\Framework\Exception\ForbiddenException();
  385. }
  386. if ($this->request->getMethod() == 'POST') {
  387. $this->checkCsrfToken();
  388. $usersList = $this->getSearchResults();
  389. if ($usersList) {
  390. $currentUid = UserUtil::getVar('uid');
  391. $actions = array();
  392. foreach ($usersList as $key => $user) {
  393. $actions[$key] = array(
  394. 'modifyUrl' => false,
  395. 'deleteUrl' => false,
  396. );
  397. if ($user['uid'] != 1) {
  398. if (SecurityUtil::checkPermission($this->name.'::', $user['uname'].'::'.$user['uid'], ACCESS_EDIT)) {
  399. $actions[$key]['modifyUrl'] = ModUtil::url($this->name, 'admin', 'modify', array('userid' => $user['uid']));
  400. }
  401. if (($currentUid != $user['uid'])
  402. && SecurityUtil::checkPermission($this->name.'::', $user['uname'].'::'.$user['uid'], ACCESS_DELETE)) {
  403. $actions[$key]['deleteUrl'] = ModUtil::url($this->name, 'admin', 'deleteusers', array('userid' => $user['uid']));
  404. }
  405. }
  406. }
  407. } else {
  408. $this->registerError($this->__('Sorry! No matching users found.'));
  409. }
  410. }
  411. if (isset($usersList) && $usersList) {
  412. return $this->response($this->view->assign('items', $usersList)
  413. ->assign('actions', $actions)
  414. ->assign('deleteUsers', SecurityUtil::checkPermission($this->name . '::', '::', ACCESS_ADMIN))
  415. ->fetch('Admin/search_results.tpl'));
  416. } elseif ($this->request->getMethod() == 'GET' || ($this->request->getMethod() == 'POST' && (!isset
  417. ($usersList) || !$usersList))) {
  418. return $this->renderSearchForm('search');
  419. } else {
  420. throw new \Zikula\Framework\Exception\ForbiddenException();
  421. }
  422. }
  423. /**
  424. * Search for users and then compose an email to them.
  425. *
  426. * Parameters passed via GET:
  427. * --------------------------
  428. * None.
  429. *
  430. * Parameters passed via POST:
  431. * ---------------------------
  432. * string formid The form id posting to this function. Used to determine the workflow.
  433. *
  434. * See also the definition of {@link getSearchResults()}.
  435. *
  436. * Parameters passed via SESSION:
  437. * ------------------------------
  438. * None.
  439. *
  440. * @return string HTML string containing the rendered template.
  441. *
  442. * @throws \Zikula\Framework\Exception\FatalException Thrown if the function enters an unknown state.
  443. *
  444. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have comment access, or if the method of accessing this function is improper.
  445. */
  446. public function mailUsersAction()
  447. {
  448. if (!SecurityUtil::checkPermission($this->name . '::MailUsers', '::', ACCESS_COMMENT)) {
  449. throw new \Zikula\Framework\Exception\ForbiddenException();
  450. }
  451. if ($this->request->getMethod() == 'POST') {
  452. $this->checkCsrfToken();
  453. $formId = $this->request->request->get('formid', 'UNKNOWN');
  454. if ($formId == 'users_search') {
  455. $userList = $this->getSearchResults('mailUsers');
  456. if (!isset($userList) || !$userList) {
  457. $this->registerError($this->__('Sorry! No matching users found.'));
  458. }
  459. } elseif ($formId == 'users_mailusers') {
  460. $uid = $this->request->request->get('userid', null);
  461. $sendmail = $this->request->request->get('sendmail', null);
  462. $mailSent = ModUtil::apiFunc($this->name, 'admin', 'sendmail', array(
  463. 'uid' => $uid,
  464. 'sendmail' => $sendmail,
  465. ));
  466. } else {
  467. throw new \Zikula\Framework\Exception\FatalException($this->__f('An unknown form type was received by %1$s.', array('mailUsers')));
  468. }
  469. } elseif (!$this->request->getMethod() == 'GET') {
  470. throw new \Zikula\Framework\Exception\ForbiddenException();
  471. }
  472. if ($this->request->getMethod() == 'GET' || (($formId == 'users_search') && (!isset($userList) || !$userList)) || (($formId == 'users_mailusers') && !$mailSent)) {
  473. return $this->renderSearchForm('mailUsers');
  474. } elseif ($formId == 'users_search') {
  475. return $this->response($this->view->assign('items', $userList)
  476. ->assign('mailusers', SecurityUtil::checkPermission($this->name . '::MailUsers', '::', ACCESS_COMMENT))
  477. ->fetch('Admin/mailusers.tpl'));
  478. } elseif ($formId == 'users_mailusers') {
  479. return $this->redirect(ModUtil::url($this->name, 'admin', 'index'));
  480. } else {
  481. throw new \Zikula\Framework\Exception\FatalException($this->__f('The %1$s function has entered an unknown state.', array('mailUsers')));
  482. }
  483. }
  484. /**
  485. * Display a form to edit one user account, and process that edit request.
  486. *
  487. * Parameters passed via GET:
  488. * --------------------------
  489. * numeric userid The user id of the user to be modified.
  490. * string uname The user name of the user to be modified.
  491. *
  492. * Parameters passed via POST:
  493. * ---------------------------
  494. * array access_permissions An array used to modify a user's group membership.
  495. *
  496. * See also the definition of {@link Users_Controller_FormData_ModifyUserForm}.
  497. *
  498. * Parameters passed via SESSION:
  499. * ------------------------------
  500. * None.
  501. *
  502. * @return string HTML string containing the rendered template.
  503. *
  504. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have edit access, or if the method of accessing this function is improper.
  505. */
  506. public function modifyAction()
  507. {
  508. // security check for generic edit access
  509. if (!SecurityUtil::checkPermission('Users::', 'ANY', ACCESS_EDIT)) {
  510. throw new \Zikula\Framework\Exception\ForbiddenException();
  511. }
  512. $proceedToForm = true;
  513. $formData = new FormData\ModifyUserForm('users_modify', $this->container);
  514. if ($this->request->getMethod() == 'POST') {
  515. $this->checkCsrfToken();
  516. $formData->setFromRequestCollection($this->request->getPost());
  517. $accessPermissions = $this->request->request->get('access_permissions', null);
  518. $user = $formData->toUserArray(true);
  519. $originalUser = UserUtil::getVars($user['uid']);
  520. $userAttributes = isset($originalUser['__ATTRIBUTES__']) ? $originalUser['__ATTRIBUTES__'] : array();
  521. // security check for this record
  522. if (!SecurityUtil::checkPermission('Users::', "{$originalUser['uname']}::{$originalUser['uid']}", ACCESS_EDIT)) {
  523. throw new \Zikula\Framework\Exception\ForbiddenException();
  524. }
  525. if ($formData->isValid()) {
  526. $registrationArgs = array(
  527. 'checkmode' => 'modify',
  528. 'emailagain' => $formData->getField('emailagain')->getData(),
  529. 'setpass' => (bool)$formData->getField('setpass')->getData(),
  530. 'antispamanswer' => '',
  531. );
  532. $registrationArgs['passagain'] = $registrationArgs['setpass'] ? $formData->getField('passagain')->getData() : '';
  533. $registrationArgs['reginfo'] = $user;
  534. $errorFields = ModUtil::apiFunc($this->name, 'registration', 'getRegistrationErrors', $registrationArgs);
  535. } else {
  536. $errorFields = $formData->getErrorMessages();
  537. }
  538. $event = new GenericEvent($user, array(), new ValidationProviders());
  539. $this->dispatcher->dispatch('module.users.ui.validate_edit.modify_user', $event);
  540. $validators = $event->getData();
  541. $hook = new ValidationHook($validators);
  542. $this->dispatchHooks('users.ui_hooks.user.validate_edit', $hook);
  543. $validators = $hook->getValidators();
  544. if (!$errorFields && !$validators->hasErrors()) {
  545. if ($originalUser['uname'] != $user['uname']) {
  546. // UserUtil::setVar does not allow uname to be changed.
  547. // UserUtil::setVar('uname', $user['uname'], $originalUser['uid']);
  548. $updatedUserObj = $this->entityManager->find('UsersModule\Entity\User', $originalUser['uid']);
  549. $updatedUserObj['uname'] = $user['uname'];
  550. $this->entityManager->flush();
  551. $eventArgs = array(
  552. 'action' => 'setVar',
  553. 'field' => 'uname',
  554. 'attribute' => null,
  555. );
  556. $eventData = array(
  557. 'old_value' => $originalUser['uname'],
  558. );
  559. $updateEvent = new GenericEvent($updatedUserObj, $eventArgs, $eventData);
  560. $this->dispatcher->dispatch('user.account.update', $updateEvent);
  561. }
  562. if ($originalUser['email'] != $user['email']) {
  563. UserUtil::setVar('email', $user['email'], $originalUser['uid']);
  564. }
  565. if ($originalUser['activated'] != $user['activated']) {
  566. UserUtil::setVar('activated', $user['activated'], $originalUser['uid']);
  567. }
  568. if ($originalUser['theme'] != $user['theme']) {
  569. UserUtil::setVar('theme', $user['theme'], $originalUser['uid']);
  570. }
  571. if ($formData->getField('setpass')->getData()) {
  572. UserUtil::setPassword($user['pass'], $originalUser['uid']);
  573. UserUtil::setVar('passreminder', $user['passreminder'], $originalUser['uid']);
  574. }
  575. $user = UserUtil::getVars($user['uid'], true);
  576. // TODO - This all needs to move to a Groups module hook.
  577. if (isset($accessPermissions)) {
  578. // Fixing a high numitems to be sure to get all groups
  579. $groups = ModUtil::apiFunc('GroupsModule', 'user', 'getAll', array('numitems' => 10000));
  580. $curUserGroupMembership = ModUtil::apiFunc('GroupsModule', 'user', 'getUserGroups', array('uid' => $user['uid']));
  581. foreach ($groups as $group) {
  582. if (in_array($group['gid'], $accessPermissions)) {
  583. // Check if the user is already in the group
  584. $userIsMember = false;
  585. if ($curUserGroupMembership) {
  586. foreach ($curUserGroupMembership as $alreadyMemberOf) {
  587. if ($group['gid'] == $alreadyMemberOf['gid']) {
  588. $userIsMember = true;
  589. break;
  590. }
  591. }
  592. }
  593. if ($userIsMember == false) {
  594. // User is not in this group
  595. ModUtil::apiFunc('GroupsModule', 'admin', 'addUser', array(
  596. 'gid' => $group['gid'],
  597. 'uid' => $user['uid']
  598. ));
  599. $curUserGroupMembership[] = $group;
  600. }
  601. } else {
  602. // We don't need to do a complex check, if the user is not in the group, the SQL will not return
  603. // an error anyway.
  604. ModUtil::apiFunc('GroupsModule', 'admin', 'removeUser', array(
  605. 'gid' => $group['gid'],
  606. 'uid' => $user['uid']
  607. ));
  608. }
  609. }
  610. }
  611. $event = new GenericEvent($user);
  612. $this->dispatcher->dispatch('module.users.ui.process_edit.modify_user', $event);
  613. $hook = new ProcessHook($user['uid']);
  614. $this->dispatchHooks('users.ui_hooks.user.process_edit', $hook);
  615. $this->registerStatus($this->__("Done! Saved user's account information."));
  616. $proceedToForm = false;
  617. }
  618. } elseif ($this->request->getMethod() == 'GET') {
  619. $uid = $this->request->query->get('userid', null);
  620. $uname = $this->request->query->get('uname', null);
  621. // check arguments
  622. if (is_null($uid) && is_null($uname)) {
  623. $this->registerError(LogUtil::getErrorMsgArgs());
  624. $proceedToForm = false;
  625. }
  626. // retreive userid from uname
  627. if (is_null($uid) && !empty($uname)) {
  628. $uid = UserUtil::getIdFromName($uname);
  629. }
  630. // warning for guest account
  631. if ($uid == 1) {
  632. $this->registerError($this->__("Error! You can't edit the guest account."));
  633. $proceedToForm = false;
  634. }
  635. // get the user vars
  636. $originalUser = UserUtil::getVars($uid);
  637. if ($originalUser == false) {
  638. $this->registerError($this->__('Sorry! No such user found.'));
  639. $proceedToForm = false;
  640. }
  641. $userAttributes = isset($originalUser['__ATTRIBUTES__']) ? $originalUser['__ATTRIBUTES__'] : array();
  642. $formData->setFromArray($originalUser);
  643. $formData->getField('emailagain')->setData($originalUser['email']);
  644. $formData->getField('pass')->setData('');
  645. $accessPermissions = array();
  646. $errorFields = array();
  647. } else {
  648. throw new \Zikula\Framework\Exception\ForbiddenException();
  649. }
  650. if ($proceedToForm) {
  651. // security check for this record
  652. if (!SecurityUtil::checkPermission('Users::', "{$originalUser['uname']}::{$originalUser['uid']}", ACCESS_EDIT)) {
  653. throw new \Zikula\Framework\Exception\ForbiddenException();
  654. }
  655. // groups
  656. $gidsUserMemberOf = array();
  657. $allGroups = ModUtil::apiFunc('GroupsModule', 'user', 'getall');
  658. if (!empty($accessPermissions)) {
  659. $gidsUserMemberOf = $accessPermissions;
  660. $accessPermissions = array();
  661. } else {
  662. $groupsUserMemberOf = ModUtil::apiFunc('GroupsModule', 'user', 'getusergroups', array('uid' => $originalUser['uid']));
  663. foreach ($groupsUserMemberOf as $user_group) {
  664. $gidsUserMemberOf[] = $user_group['gid'];
  665. }
  666. }
  667. foreach ($allGroups as $group) {
  668. if (SecurityUtil::checkPermission('Groups::', "{$group['gid']}::", ACCESS_EDIT)) {
  669. $accessPermissions[$group['gid']] = array();
  670. $accessPermissions[$group['gid']]['name'] = $group['name'];
  671. if (in_array($group['gid'], $gidsUserMemberOf) || in_array($group['gid'], $gidsUserMemberOf)) {
  672. $accessPermissions[$group['gid']]['access'] = true;
  673. } else {
  674. $accessPermissions[$group['gid']]['access'] = false;
  675. }
  676. }
  677. }
  678. if (!isset($userAttributes['realname'])) {
  679. $userAttributes['realname'] = '';
  680. }
  681. return $this->response($this->view->assign_by_ref('formData', $formData)
  682. ->assign('user_attributes', $userAttributes)
  683. ->assign('defaultGroupId', ModUtil::getVar('Groups', 'defaultgroup', 1))
  684. ->assign('primaryAdminGroupId', ModUtil::getVar('Groups', 'primaryadmingroup', 2))
  685. ->assign('accessPermissions', $accessPermissions)
  686. ->assign('errorFields', $errorFields)
  687. ->fetch('Admin/modify.tpl'));
  688. } else {
  689. return $this->redirect(ModUtil::url($this->name, 'admin', 'view'));
  690. }
  691. }
  692. /**
  693. * Allows an administrator to send a user his user name via email.
  694. *
  695. * Parameters passed via GET:
  696. * --------------------------
  697. * numeric userid The user id of the user to be modified.
  698. *
  699. * Parameters passed via POST:
  700. * ---------------------------
  701. * numeric userid The user id of the user to be modified.
  702. *
  703. * Parameters passed via SESSION:
  704. * ------------------------------
  705. * None.
  706. *
  707. * @return void
  708. *
  709. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have moderate access.
  710. *
  711. * @todo The link on the view page should be a mini form, and should post.
  712. *
  713. * @todo This should have a confirmation page.
  714. */
  715. public function lostUsernameAction()
  716. {
  717. if ($this->request->getMethod() == 'POST') {
  718. $this->checkCsrfToken();
  719. $uid = $this->request->request->get('userid', null);
  720. } else {
  721. $this->checkCsrfToken($this->request->query->get('csrftoken'));
  722. $uid = $this->request->query->get('userid', null);
  723. }
  724. if (!isset($uid) || !is_numeric($uid) || ((int)$uid != $uid) || ($uid <= 1)) {
  725. $this->registerError(LogUtil::getErrorMsgArgs())
  726. ->redirect(ModUtil::url($this->name, 'admin', 'view'));
  727. }
  728. $user = UserUtil::getVars($uid);
  729. if (!$user) {
  730. $this->registerError($this->__('Sorry! Unable to retrieve information for that user id.'))
  731. ->redirect(ModUtil::url($this->name, 'admin', 'view'));
  732. }
  733. if (!SecurityUtil::checkPermission('Users::', "{$user['uname']}::{$user['uid']}", ACCESS_MODERATE)) {
  734. throw new \Zikula\Framework\Exception\ForbiddenException();
  735. }
  736. $userNameSent = ModUtil::apiFunc($this->name, 'user', 'mailUname', array(
  737. 'idfield' => 'uid',
  738. 'id' => $user['uid'],
  739. 'adminRequest' => true,
  740. ));
  741. if ($userNameSent) {
  742. $this->registerStatus($this->__f('Done! The user name for \'%s\' has been sent via e-mail.', $user['uname']))
  743. ->redirect(ModUtil::url($this->name, 'admin', 'view'));
  744. } elseif (!$this->request->getSession()->getFlashBag()->has(Zikula_Session::MESSAGE_ERROR)) {
  745. $this->registerError($this->__f('Sorry! There was an unknown error while trying to send the user name for \'%s\'.', $user['uname']))
  746. ->redirect(ModUtil::url($this->name, 'admin', 'view'));
  747. }
  748. }
  749. /**
  750. * Allows an administrator to send a user a password recovery verification code.
  751. *
  752. * Parameters passed via GET:
  753. * --------------------------
  754. * numeric userid The user id of the user to be modified.
  755. *
  756. * Parameters passed via POST:
  757. * ---------------------------
  758. * None.
  759. *
  760. * Parameters passed via SESSION:
  761. * ------------------------------
  762. * None.
  763. *
  764. * @return bool True on success and redirect; otherwise false.
  765. *
  766. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have moderate access.
  767. *
  768. * @todo The link on the view page should be a mini form, and should post.
  769. *
  770. * @todo This should have a confirmation page.
  771. */
  772. public function lostPasswordAction()
  773. {
  774. $this->checkCsrfToken($this->request->query->get('csrftoken'));
  775. $uid = $this->request->query->get('userid', null);
  776. if (!isset($uid) || !is_numeric($uid) || ((int)$uid != $uid) || ($uid <= 1)) {
  777. $this->registerError(LogUtil::getErrorMsgArgs())
  778. ->redirect(ModUtil::url($this->name, 'admin', 'view'));
  779. }
  780. $user = UserUtil::getVars($uid);
  781. if (!$user) {
  782. $this->registerError($this->__('Sorry! Unable to retrieve information for that user id.'));
  783. return false;
  784. }
  785. if (!SecurityUtil::checkPermission('Users::', "{$user['uname']}::{$user['uid']}", ACCESS_MODERATE)) {
  786. throw new \Zikula\Framework\Exception\ForbiddenException();
  787. }
  788. $confirmationCodeSent = ModUtil::apiFunc($this->name, 'user', 'mailConfirmationCode', array(
  789. 'idfield' => 'uid',
  790. 'id' => $user['uid'],
  791. 'adminRequest' => true,
  792. ));
  793. if ($confirmationCodeSent) {
  794. $this->registerStatus($this->__f('Done! The password recovery verification code for %s has been sent via e-mail.', $user['uname']));
  795. }
  796. return $this->redirect(ModUtil::url($this->name, 'admin', 'view'));
  797. }
  798. /**
  799. * Display a form to confirm the deletion of one user, and then process the deletion.
  800. *
  801. * Parameters passed via GET:
  802. * --------------------------
  803. * numeric userid The user id of the user to be deleted.
  804. * string uname The user name of the user to be deleted.
  805. *
  806. * Parameters passed via POST:
  807. * ---------------------------
  808. * array userid The array of user ids of the users to be deleted.
  809. * boolean process_delete True to process the posted userid list, and delete the corresponding accounts; false or null to confirm first.
  810. *
  811. * Parameters passed via SESSION:
  812. * ------------------------------
  813. * None.
  814. *
  815. * @return string HTML string containing the rendered template.
  816. *
  817. * @throws \Zikula\Framework\Exception\ForbiddenException Thrown if the current user does not have delete access, or if the method of accessing this function is improper.
  818. */
  819. public function deleteUsersAction()
  820. {
  821. // check permissions
  822. if (!SecurityUtil::checkPermission('Users::', 'ANY', ACCESS_DELETE)) {
  823. throw new \Zikula\Framework\Exception\ForbiddenException();
  824. }
  825. $proceedToForm = false;
  826. $processDelete = false;
  827. if ($this->request->getMethod() == 'POST') {
  828. $userid = $this->request->request->get('userid', null);
  829. $processDelete = $this->request->request->get('process_delete', false);
  830. $proceedToForm = !$processDelete;
  831. } elseif ($this->request->getMethod() == 'GET') {
  832. $userid = $this->request->query->get('userid', null);
  833. $uname = $this->request->query->get('uname', null);
  834. // retreive userid from uname
  835. if (empty($userid) && !empty($uname)) {
  836. $userid = UserUtil::getIdFromName($uname);
  837. }
  838. $proceedToForm = true;
  839. } else {
  840. throw new \Zikula\Framework\Exception\ForbiddenException();
  841. }
  842. if (empty($userid)) {
  843. $this->registerError($this->__('Sorry! No such user found.'));
  844. $proceedToForm = false;
  845. }
  846. if (!is_array($userid)) {
  847. $userid = array($userid);
  848. }
  849. $currentUser = UserUtil::getVar('uid');
  850. $users = array();
  851. foreach ($userid as $key => $uid) {
  852. if ($uid == 1) {
  853. $this->registerError($this->__("Error! You can't delete the guest account."));
  854. $proceedToForm = false;
  855. $processDelete = false;
  856. } elseif ($uid == 2) {
  857. $this->registerError($this->__("Error! You can't delete the primary administrator account."));
  858. $proceedToForm = false;
  859. $processDelete = false;
  860. } elseif ($uid == $currentUser) {
  861. $this->registerError($this->__("Error! You can't delete the account you are currently logged into."));
  862. $proceedToForm = false;
  863. $processDelete = false;
  864. }
  865. // get the user vars
  866. $users[$key] = UserUtil::getVars($uid);
  867. if ($users[$key] == false) {
  868. $this->registerError($this->__('Sorry! No such user found.'));
  869. $proceedToForm = false;
  870. $processDelete = false;
  871. }
  872. }
  873. if ($processDelete) {
  874. $valid = true;
  875. foreach ($userid as $uid) {
  876. $event = new GenericEvent(null, array('id' => $uid), new ValidationProviders());
  877. $validators = $this->dispatcher->dispatch('module.users.ui.validate_delete', $event)->getData();
  878. $hook = new ValidationHook($validators);
  879. $this->dispatchHooks('users.ui_hooks.user.validate_delete', $hook);
  880. $validators = $hook->getValidators();
  881. if ($validators->hasErrors()) {
  882. $valid = false;
  883. }
  884. }
  885. $proceedToForm = false;
  886. if ($valid) {
  887. $deleted = ModUtil::apiFunc($this->name, 'admin', 'deleteUser', array('uid' => $userid));
  888. if ($deleted) {
  889. foreach ($userid as $uid) {
  890. $event = new GenericEvent(null, array('id' => $uid));
  891. $this->dispatcher->dispatch('module.users.ui.process_delete', $event);
  892. $hook = new ProcessHook($uid);
  893. $this->dispatchHooks('users.ui_hooks.user.process_delete', $hook);
  894. }
  895. $count = count($userid);
  896. $this->registerStatus($this->_fn('Done! Deleted %1$d user account.', 'Done! Deleted %1$d user accounts.', $count, array($count)));
  897. }
  898. }
  899. }
  900. if ($proceedToForm) {
  901. return $this->response($this->view->assign('users', $users)
  902. ->fetch('Admin/deleteusers.tpl'));
  903. } else {
  904. return $this->redirect(ModUtil::url($this->name, 'admin', 'view'));
  905. }
  906. }
  907. /**
  908. * Constructs a list of various actions for a list of registrations appropriate for the current user.
  909. *
  910. * NOTE: Internal function.
  911. *
  912. * @param array $reglist The list of registration records.
  913. * @param string $restoreView Indicates where the calling function expects to return to; 'view' indicates
  914. * that the calling function expects to return to the registration list
  915. * and 'display' indicates that the calling function expects to return
  916. * to an individual registration record.
  917. *
  918. * @return array An array of valid action URLs for each registration record in the list.
  919. */
  920. protected function getActionsForRegistrations(array $reglist, $restoreView='view')
  921. {
  922. $actions = array();
  923. if (!empty($reglist)) {
  924. $approvalOrder = $this->getVar('moderation_order', UsersConstant::APPROVAL_BEFORE);
  925. // Don't try to put any visual elements here (images, titles, colors, css classes, etc.). Leave that to
  926. // the template, so that they can be customized without hacking the core code. In fact, all we really need here
  927. // is what options are enabled. The template could build everything else. We will put the URL for the action
  928. // in the array for convenience, but that could be done in the template too, really.
  929. //
  930. // Make certain that the following goes from most restricted to least (ADMIN...NONE order). Having the
  931. // security check as the outer if statement, and similar foreach loops within each saves on repeated checking
  932. // of permissions, speeding things up a bit.
  933. if (SecurityUtil::checkPermission('Users::', '::', ACCESS_ADMIN)) {
  934. $actions['count'] = 6;
  935. foreach ($reglist as $key => $reginfo) {
  936. $enableVerify = !$reginfo['isverified'];
  937. $enableApprove = !$reginfo['isapproved'];
  938. $enableForced = !$reginfo['isverified'] && isset($reginfo['pass']) && !empty($reginfo['pass']);
  939. $actions['list'][$reginfo['uid']] = array(
  940. 'display' => ModUtil::url($this->name, 'admin', 'displayRegistration', array('uid' => $reginfo['uid'])),
  941. 'modify' => ModUtil::url($this->name, 'admin', 'modifyRegistration', array('uid' => $reginfo['uid'], 'restoreview' => $restoreView)),
  942. 'verify' => $enableVerify ? ModUtil::url($this->name, 'admin', 'verifyRegistration', array('uid' => $reginfo['uid'], 'restoreview' => $restoreView)) : false,
  943. 'approve' => $enableApprove ? ModUtil::url($this->name, 'admin', 'approveRegistration', array('uid' => $reginfo['uid'])) : false,
  944. 'deny' => ModUtil::url($this->name, 'admin', 'denyRegistration', array('uid' => $reginfo['uid'])),
  945. 'approveForce' => $enableForced ? ModUtil::url($this->name, 'admin', 'approveRegistration', array('uid' => $reginfo['uid'], 'force' => true)) : false,
  946. );

Large files files are truncated, but you can click here to view the full file