register.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. /**
  3. * Login
  4. *
  5. * Copyright 2010 by Jason Coward <jason@modxcms.com> and Shaun McCormick
  6. * <shaun@modxcms.com>
  7. *
  8. * Login is free software; you can redistribute it and/or modify it
  9. * under the terms of the GNU General Public License as published by the Free
  10. * Software Foundation; either version 2 of the License, or (at your option) any
  11. * later version.
  12. *
  13. * Login is distributed in the hope that it will be useful, but WITHOUT ANY
  14. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  15. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along with
  18. * Login; if not, write to the Free Software Foundation, Inc., 59 Temple
  19. * Place, Suite 330, Boston, MA 02111-1307 USA
  20. *
  21. * @package login
  22. */
  23. /**
  24. * Handle register form
  25. *
  26. * @package login
  27. * @subpackage processors
  28. */
  29. class LoginRegisterProcessor extends LoginProcessor {
  30. /** @var modUser $user */
  31. public $user;
  32. /** @var modUserProfile $profile */
  33. public $profile;
  34. /** @var array $userGroups */
  35. public $userGroups = array();
  36. public $persistParams = array();
  37. public $live = true;
  38. /**
  39. * @return mixed
  40. */
  41. public function process() {
  42. $this->cleanseFields();
  43. /* create user and profile */
  44. $this->user = $this->modx->newObject('modUser');
  45. $this->profile = $this->modx->newObject('modUserProfile');
  46. if ($this->controller->getProperty('useExtended',true,'isset')) {
  47. $this->setExtended();
  48. }
  49. $this->setUserFields();
  50. /* save user */
  51. if ($this->live) {
  52. if (!$this->user->save()) {
  53. $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] Could not save newly registered user: '.$this->user->get('id').' with username: '.$this->user->get('username'));
  54. return $this->modx->lexicon('register.user_err_save');
  55. }
  56. }
  57. $this->preparePersistentParameters();
  58. /* send activation email (if chosen) */
  59. $email = $this->profile->get('email');
  60. $activation = $this->controller->getProperty('activation',true,'isset');
  61. $activationResourceId = $this->controller->getProperty('activationResourceId','','isset');
  62. $moderated = $this->checkForModeration();
  63. if ($activation && !empty($email) && !empty($activationResourceId) && !$moderated) {
  64. $this->sendActivationEmail();
  65. } else if (!$moderated) {
  66. $this->user->set('active',true);
  67. if ($this->live) {
  68. $this->user->save();
  69. }
  70. }
  71. $this->runPostHooks();
  72. if ($this->controller->getProperty('autoLogin') == true && $this->controller->getProperty('activation') == false) {
  73. $this->autoLogin();
  74. }
  75. $this->checkForModerationRedirect();
  76. $this->checkForRegisteredRedirect();
  77. $successMsg = $this->controller->getProperty('successMsg','');
  78. $this->modx->toPlaceholder('error.message',$successMsg);
  79. return true;
  80. }
  81. /**
  82. * Remove any fields used for anti-spam, submission or moderation from the submission returns
  83. * @return void
  84. */
  85. public function cleanseFields() {
  86. $this->dictionary->remove('nospam');
  87. $this->dictionary->remove('blank');
  88. $submitVar = $this->controller->getProperty('submitVar');
  89. if (!empty($submitVar)) {
  90. $this->dictionary->remove($submitVar);
  91. }
  92. }
  93. /**
  94. * If wanted, set extra values in the form to profile extended field
  95. * @return void
  96. */
  97. public function setExtended() {
  98. /* first cut out regular and unwanted fields */
  99. $excludeExtended = $this->controller->getProperty('excludeExtended','');
  100. $excludeExtended = explode(',',$excludeExtended);
  101. $profileFields = $this->profile->toArray();
  102. $userFields = $this->user->toArray();
  103. $extended = array();
  104. $fields = $this->dictionary->toArray();
  105. $fields = $this->filterAllowedFields($fields);
  106. $userGroupField = $this->controller->getProperty('usergroupsField','');
  107. foreach ($fields as $field => $value) {
  108. if (!isset($profileFields[$field])
  109. && !isset($userFields[$field])
  110. && $field != 'password_confirm'
  111. && $field != 'passwordconfirm'
  112. && $field != $userGroupField
  113. && !in_array($field,$excludeExtended)
  114. ) {
  115. $extended[$field] = $value;
  116. }
  117. }
  118. /* now set extended data */
  119. $this->profile->set('extended',$extended);
  120. }
  121. /**
  122. * Setup the user data and create the user/profile objects
  123. *
  124. * @return void
  125. */
  126. public function setUserFields() {
  127. $fields = $this->dictionary->toArray();
  128. /* allow overriding of class key */
  129. if (empty($fields['class_key'])) $fields['class_key'] = 'modUser';
  130. $fields = $this->filterAllowedFields($fields);
  131. /* set user and profile */
  132. $this->user->fromArray($fields);
  133. $this->user->set('username',$fields[$this->controller->getProperty('usernameField','username')]);
  134. $this->user->set('active',0);
  135. $version = $this->modx->getVersionData();
  136. /* 2.1.x+ */
  137. if (version_compare($version['full_version'],'2.1.0-rc1') >= 0) {
  138. $this->user->set('password',$fields[$this->controller->getProperty('passwordField','password')]);
  139. } else { /* 2.0.x */
  140. $this->user->set('password',md5($fields[$this->controller->getProperty('passwordField','password')]));
  141. }
  142. $this->profile->fromArray($fields);
  143. $this->profile->set('email',$this->dictionary->get($this->controller->getProperty('emailField','email')));
  144. $this->user->addOne($this->profile,'Profile');
  145. /* add user groups, if set */
  146. $userGroupsField = $this->controller->getProperty('usergroupsField','');
  147. $userGroups = !empty($userGroupsField) && array_key_exists($userGroupsField,$fields) ? $fields[$userGroupsField] : array();
  148. $this->setUserGroups($userGroups);
  149. }
  150. /**
  151. * Return an array of filtered fields if the allowedFields property is set and prevents setting of certain fields
  152. * @param array $fields
  153. * @return array
  154. */
  155. public function filterAllowedFields(array $fields = array()) {
  156. $allowedFields = $this->controller->getProperty('allowedFields','');
  157. if (!empty($allowedFields)) {
  158. $allowedFields = is_array($allowedFields) ? $allowedFields : explode(',',$allowedFields);
  159. $userGroupField = $this->controller->getProperty('usergroupsField','');
  160. $usernameField = $this->controller->getProperty('usernameField','username');
  161. $passwordField = $this->controller->getProperty('passwordField','password');
  162. $emailField = $this->controller->getProperty('emailField','email');
  163. array_push($allowedFields,$usernameField,$passwordField,'password_confirm',$emailField,'class_key');
  164. if (!empty($userGroupField)) array_push($allowedFields,$userGroupField);
  165. $allowedFields = array_unique($allowedFields);
  166. foreach ($fields as $k => $v) {
  167. if (!in_array($k,$allowedFields)) unset($fields[$k]);
  168. }
  169. }
  170. return $fields;
  171. }
  172. /**
  173. * If user groups were passed, set them here
  174. * @param string $userGroups
  175. * @return array
  176. */
  177. public function setUserGroups($userGroups) {
  178. $added = array();
  179. /* if $userGroups set in form, override here; otherwise use snippet property */
  180. $this->userGroups = !empty($userGroups) ? $userGroups : $this->controller->getProperty('usergroups', '');
  181. if (!empty($this->userGroups)) {
  182. $this->userGroups = is_array($this->userGroups) ? $this->userGroups : explode(',',$this->userGroups);
  183. foreach ($this->userGroups as $userGroupMeta) {
  184. $userGroupMeta = explode(':',$userGroupMeta);
  185. if (empty($userGroupMeta[0])) continue;
  186. /* get usergroup */
  187. $pk = array();
  188. $pk[intval($userGroupMeta[0]) > 0 ? 'id' : 'name'] = trim($userGroupMeta[0]);
  189. /** @var modUserGroup $userGroup */
  190. $userGroup = $this->modx->getObject('modUserGroup',$pk);
  191. if (!$userGroup) continue;
  192. /* get role */
  193. $rolePk = !empty($userGroupMeta[1]) ? $userGroupMeta[1] : 'Member';
  194. /** @var modUserGroupRole $role */
  195. $role = $this->modx->getObject('modUserGroupRole',array('name' => $rolePk));
  196. /* create membership */
  197. /** @var modUserGroupMember $member */
  198. $member = $this->modx->newObject('modUserGroupMember');
  199. $member->set('member',0);
  200. $member->set('user_group',$userGroup->get('id'));
  201. if (!empty($role)) {
  202. $member->set('role',$role->get('id'));
  203. } else {
  204. $member->set('role',1);
  205. }
  206. $rank = (isset($userGroupMeta[2])) ? $userGroupMeta[2] : 0;
  207. $member->set('rank', $rank);
  208. $this->user->addMany($member,'UserGroupMembers');
  209. $added[] = $userGroup->get('name');
  210. }
  211. }
  212. return $added;
  213. }
  214. /**
  215. * Setup persistent parameters to go through the request cycle
  216. * @return array
  217. */
  218. public function preparePersistentParameters() {
  219. $this->persistParams = $this->controller->getProperty('persistParams','');
  220. if (!empty($this->persistParams)) $this->persistParams = $this->modx->fromJSON($this->persistParams);
  221. if (empty($this->persistParams) || !is_array($this->persistParams)) $this->persistParams = array();
  222. return $this->persistParams;
  223. }
  224. /**
  225. * Send an activation email to the user with an encrypted username and password hash, to allow for secure
  226. * activation processes that are not vulnerable to middle-man attacks.
  227. *
  228. * @return boolean
  229. */
  230. public function sendActivationEmail() {
  231. $emailProperties = $this->gatherActivationEmailProperties();
  232. /* send either to user's email or a specified activation email */
  233. $activationEmail = $this->controller->getProperty('activationEmail',$this->profile->get('email'));
  234. $subject = $this->controller->getProperty('activationEmailSubject',$this->modx->lexicon('register.activation_email_subject'));
  235. return $this->login->sendEmail($activationEmail,$this->user->get('username'),$subject,$emailProperties);
  236. }
  237. /**
  238. * Get all the properties for the activation email
  239. * @return array
  240. */
  241. public function gatherActivationEmailProperties() {
  242. /* generate a password and encode it and the username into the url */
  243. $pword = $this->modx->user->generatePassword();
  244. $confirmParams['lp'] = $this->login->base64url_encode($pword);
  245. $confirmParams['lu'] = $this->login->base64url_encode($this->user->get('username'));
  246. $confirmParams = array_merge($this->persistParams,$confirmParams);
  247. /* if using redirectBack param, set here to allow dynamic redirection
  248. * handling from other forms.
  249. */
  250. $redirectBack = $this->modx->getOption('redirectBack',$_REQUEST,$this->controller->getProperty('redirectBack',''));
  251. if (!empty($redirectBack)) {
  252. $confirmParams['redirectBack'] = $redirectBack;
  253. }
  254. $redirectBackParams = $this->modx->getOption('redirectBackParams',$_REQUEST,$this->controller->getProperty('redirectBackParams',''));
  255. if (!empty($redirectBackParams)) {
  256. $confirmParams['redirectBackParams'] = $redirectBackParams;
  257. }
  258. /* generate confirmation url */
  259. if ($this->login->inTestMode) {
  260. $confirmUrl = $this->modx->makeUrl(1,'',$confirmParams,'full');
  261. } else {
  262. $confirmUrl = $this->modx->makeUrl($this->controller->getProperty('activationResourceId',1),'',$confirmParams,'full');
  263. }
  264. /* set confirmation email properties */
  265. $emailTpl = $this->controller->getProperty('activationEmailTpl','lgnActivateEmailTpl');
  266. $emailTplAlt = $this->controller->getProperty('activationEmailTplAlt','');
  267. $emailTplType = $this->controller->getProperty('activationEmailTplType','modChunk');
  268. $emailProperties = $this->user->toArray();
  269. $emailProperties['confirmUrl'] = $confirmUrl;
  270. $emailProperties['tpl'] = $emailTpl;
  271. $emailProperties['tplAlt'] = $emailTplAlt;
  272. $emailProperties['tplType'] = $emailTplType;
  273. $emailProperties['password'] = $this->dictionary->get($this->controller->getProperty('passwordField','password'));
  274. $this->setCachePassword($pword);
  275. return $emailProperties;
  276. }
  277. public function setCachePassword($password) {
  278. /* now set new password to registry to prevent middleman attacks.
  279. * Will read from the registry on the confirmation page. */
  280. $this->modx->getService('registry', 'registry.modRegistry');
  281. $this->modx->registry->addRegister('login','registry.modFileRegister');
  282. $this->modx->registry->login->connect();
  283. $this->modx->registry->login->subscribe('/useractivation/');
  284. $this->modx->registry->login->send('/useractivation/',array($this->user->get('username') => $password),array(
  285. 'ttl' => ($this->controller->getProperty('activationttl',180)*60),
  286. ));
  287. /* set cachepwd here to prevent re-registration of inactive users */
  288. $this->user->set('cachepwd',md5($password));
  289. if ($this->live) {
  290. $success = $this->user->save();
  291. } else {
  292. $success = true;
  293. }
  294. if (!$success) {
  295. $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] Could not update cachepwd for activation for User: '.$this->user->get('username'));
  296. }
  297. return $success;
  298. }
  299. /**
  300. * Check to see if a pre/post hook told Login to set the user to moderated (inactive) status
  301. * @return boolean
  302. */
  303. public function checkForModeration() {
  304. $moderate = $this->dictionary->get('register.moderate');
  305. return !empty($moderate);
  306. }
  307. /**
  308. * Run any post-registration hooks
  309. *
  310. * @return void
  311. */
  312. public function runPostHooks() {
  313. $postHooks = $this->controller->getProperty('postHooks','');
  314. $this->controller->loadHooks('postHooks');
  315. $fields = $this->dictionary->toArray();
  316. $fields['register.user'] =& $this->user;
  317. $fields['register.profile'] =& $this->profile;
  318. $fields['register.usergroups'] = $this->userGroups;
  319. $this->controller->postHooks->loadMultiple($postHooks,$fields);
  320. /* process hooks */
  321. if ($this->controller->postHooks->hasErrors()) {
  322. $errors = array();
  323. $hookErrors = $this->controller->postHooks->getErrors();
  324. foreach ($hookErrors as $key => $error) {
  325. $errors[$key] = str_replace('[[+error]]',$error,$this->controller->getProperty('errTpl'));
  326. }
  327. $this->modx->toPlaceholders($errors,'error');
  328. $errorMsg = $this->controller->postHooks->getErrorMessage();
  329. $this->modx->toPlaceholder('message',$errorMsg,'error');
  330. }
  331. }
  332. /**
  333. * if a hook set the user as moderated, if set, send to an optional other moderation resource id
  334. * @return boolean
  335. */
  336. public function checkForModerationRedirect() {
  337. $moderated = $this->checkForModeration();
  338. if (!empty($moderated)) {
  339. $moderatedResourceId = $this->controller->getProperty('moderatedResourceId','');
  340. if (!empty($moderatedResourceId)) {
  341. if ($this->controller->getProperty('redirectUnsetDefaultParams') == false) {
  342. $persistParams = array_merge($this->persistParams,array(
  343. 'username' => $this->user->get('username'),
  344. 'email' => $this->profile->get('email'),
  345. ));
  346. }
  347. $url = $this->modx->makeUrl($moderatedResourceId,'',$persistParams,'full');
  348. if (!$this->login->inTestMode) {
  349. $this->modx->sendRedirect($url);
  350. }
  351. return true;
  352. }
  353. }
  354. return false;
  355. }
  356. /**
  357. * Check for a redirect if the user was successfully registered. If one found, redirect.
  358. *
  359. * @return boolean
  360. */
  361. public function checkForRegisteredRedirect() {
  362. /* if provided a redirect id, will redirect to that resource, with the
  363. * GET params `username` and `email` for you to use */
  364. $submittedResourceId = $this->controller->getProperty('submittedResourceId','');
  365. if (!empty($submittedResourceId)) {
  366. if ($this->controller->getProperty('redirectUnsetDefaultParams') == false) {
  367. $persistParams = array_merge($this->persistParams,array(
  368. 'username' => $this->user->get('username'),
  369. 'email' => $this->profile->get('email'),
  370. ));
  371. }
  372. $url = $this->modx->makeUrl($submittedResourceId,'',$persistParams,'full');
  373. if (!$this->login->inTestMode) {
  374. $this->modx->sendRedirect($url);
  375. }
  376. return true;
  377. }
  378. return false;
  379. }
  380. public function autoLogin() {
  381. if ($this->user == null) { return false; }
  382. $contexts = $this->controller->getProperty('authenticateContexts', $this->modx->context->get('key'));
  383. $fields = $this->dictionary->toArray();
  384. $c = array(
  385. 'login_context' => $this->modx->context->key,
  386. 'add_contexts' => $contexts,
  387. 'username' => $this->user->username,
  388. 'password' => $fields[$this->controller->getProperty('passwordField','password')],
  389. 'returnUrl' => '',
  390. );
  391. $a = $this->modx->runProcessor('security/login',$c);
  392. return true;
  393. }
  394. }
  395. return 'LoginRegisterProcessor';