SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Controllers/Admin/OperatorAdminController.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Controllers\Admin;
  15.  
  16. use Exception;
  17.  
  18. use Sprout\Helpers\AdminAuth;
  19. use Sprout\Helpers\AdminError;
  20. use Sprout\Helpers\AdminPerms;
  21. use Sprout\Helpers\Email;
  22. use Sprout\Helpers\EmailText;
  23. use Sprout\Helpers\Notification;
  24. use Sprout\Helpers\Pdb;
  25. use Sprout\Helpers\Security;
  26. use Sprout\Helpers\Url;
  27. use Sprout\Helpers\Validator;
  28.  
  29.  
  30. /**
  31. * Handles most processing for operators
  32. **/
  33. class OperatorAdminController extends HasCategoriesAdminController
  34. {
  35. /**
  36.   * The maximum permissable password length; this is likely limited by the hash method used
  37.   * Currently set to bcrypt's truncate length of 72
  38.   */
  39. const MAX_PASSWORD_LENGTH = 72;
  40.  
  41. protected $controller_name = 'operator';
  42. protected $friendly_name = 'Operators';
  43. protected $add_defaults = array(
  44. 'active' => 1,
  45. );
  46. protected $action_log = true;
  47.  
  48.  
  49. /**
  50.   * The categories which can be edited by the logged in user
  51.   **/
  52. private $manage_cats;
  53.  
  54.  
  55. public function __construct()
  56. {
  57. $this->main_columns = [
  58. 'Name' => 'name',
  59. 'Username' => 'username',
  60. 'Email' => 'email',
  61. ];
  62.  
  63. $this->manage_cats = AdminPerms::getManageOperatorCategories();
  64.  
  65. if (!AdminPerms::canAccess('access_operators')) {
  66. $cats = implode(',', array_keys($this->manage_cats));
  67. $this->main_where[] = "(SELECT 1 FROM ~operators_cat_join WHERE operator_id = item.id AND cat_id IN ({$cats}) LIMIT 1) = 1";
  68. }
  69.  
  70. parent::__construct();
  71. }
  72.  
  73.  
  74. /**
  75.   * Show tools - but only for full access ops
  76.   **/
  77. public function _getTools()
  78. {
  79. if (! AdminPerms::canAccess('access_operators')) return array();
  80.  
  81. $tools = parent::_getTools();
  82.  
  83. $tools[] = '<li class="config"><a href="' . EmailText::adminEditUrl('operator.welcome') . '">Edit welcome message email</a></li>';
  84.  
  85. return $tools;
  86. }
  87.  
  88.  
  89. /**
  90.   * Get contents list - but only for partial access ops
  91.   **/
  92. public function _getContents()
  93. {
  94. if (count($this->manage_cats) == 0) {
  95. return new AdminError('Access denied');
  96. }
  97.  
  98. return parent::_getContents();
  99. }
  100.  
  101.  
  102. /**
  103.   * Get add form - but only for partial access ops
  104.   **/
  105. public function _getAddForm()
  106. {
  107. if (count($this->manage_cats) == 0) {
  108. return new AdminError('Access denied');
  109. }
  110.  
  111. return parent::_getAddForm();
  112. }
  113.  
  114.  
  115. /**
  116.   * Return the sub-actions for adding; for spec {@see AdminController::renderSubActions}
  117.   * @return array
  118.   */
  119. public function _getAddSubActions()
  120. {
  121. $actions = parent::_getAddSubActions();
  122. // Add your actions here, like this: $actions[] = [ ... ];
  123. return $actions;
  124. }
  125.  
  126.  
  127. /**
  128.   * Get edit form - but only for partial access ops or for editing your own record
  129.   **/
  130. public function _getEditForm($item_id)
  131. {
  132. if (!AdminPerms::canEditOperator($item_id) and $item_id != AdminAuth::getId()) {
  133. return new AdminError('Access denied');
  134. }
  135.  
  136. return parent::_getEditForm($item_id);
  137. }
  138.  
  139.  
  140. /**
  141.   * Get delete form - but only for partial access ops
  142.   **/
  143. public function _getDeleteForm($item_id)
  144. {
  145. if (!AdminPerms::canEditOperator($item_id)) {
  146. return new AdminError('Access denied');
  147. }
  148.  
  149. if ($item_id == AdminAuth::getLocalId()) {
  150. return new AdminError('You cannot delete your own record');
  151. }
  152.  
  153. return parent::_getDeleteForm($item_id);
  154. }
  155.  
  156.  
  157.  
  158. /**
  159.   * Pre-render hook for adding
  160.   **/
  161. protected function _addPreRender($view)
  162. {
  163. parent::_addPreRender($view);
  164.  
  165. $view->cats = $this->manage_cats;
  166. }
  167.  
  168.  
  169. /**
  170.   * Saves the provided POST data into a new record in the database
  171.   *
  172.   * @param int $item_id After saving, the new record id will be returned in this parameter
  173.   * @param bool True on success, false on failure
  174.   **/
  175. public function _addSave(&$item_id)
  176. {
  177. if (count($this->manage_cats) == 0) return false;
  178.  
  179. $_SESSION['admin']['field_values'] = Validator::trim($_POST);
  180. $result = true;
  181.  
  182. $valid = new Validator($_POST);
  183. $valid->required(['name', 'email', 'username', 'password1', 'password2']);
  184. $valid->check('name', 'Validity::length', 0, 200);
  185. $valid->check('email', 'Validity::email');
  186. $valid->check('email', 'Validity::length', 0, 200);
  187. $valid->check('username', 'Validity::length', 0, 50);
  188. $valid->check('username', 'Validity::uniqueValue', 'operators', 'username', 0, 'An operator already exists with that username');
  189. $valid->check('username', 'Validity::regex', '/^[a-zA-Z0-9]+$/');
  190. $valid->check('password1', 'Validity::length', 8, self::MAX_PASSWORD_LENGTH);
  191. $valid->check('password2', 'Validity::length', 8, self::MAX_PASSWORD_LENGTH);
  192. $valid->multipleCheck(['password1', 'password2'], 'Validity::allMatch');
  193.  
  194. if ($valid->hasErrors()) {
  195. $_SESSION['admin']['field_errors'] = $valid->getFieldErrors();
  196. $result = false;
  197. }
  198.  
  199. if ($_POST['password1'] and $_POST['password1'] === $_POST['password2']) {
  200. // Check password is complex enough
  201. $complexity = Security::passwordComplexity($_POST['password1'], 8, 2, true);
  202. if (!empty($complexity)) {
  203. $_SESSION['admin']['field_errors']['password1'] = 'Not complex enough';
  204. $_SESSION['admin']['field_errors']['password2'] = 'Not complex enough';
  205. $result = false;
  206.  
  207. Notification::error('Password does not meet complexity requirements:');
  208. foreach ($complexity as $c) {
  209. Notification::error(" \xC2\xA0 \xC2\xA0 " . $c);
  210. }
  211. }
  212. }
  213.  
  214. // Check all categories are allowed
  215. if (empty($_POST['categories'])) {
  216. Notification::error('You must choose at least one category');
  217. $result = false;
  218.  
  219. } else {
  220. foreach ($_POST['categories'] as $cat_id) {
  221. if (!$this->manage_cats[$cat_id]) {
  222. Notification::error('You do not have access to manage category id ' . $cat_id);
  223. $result = false;
  224. }
  225. }
  226. }
  227.  
  228. if (! $result) return false;
  229.  
  230.  
  231. // Start transaction
  232. Pdb::transact();
  233.  
  234. // Main insert
  235. $update_fields = [];
  236. $update_fields['name'] = $_POST['name'];
  237. $update_fields['username'] = $_POST['username'];
  238. $update_fields['email'] = $_POST['email'];
  239. $update_fields['firstrun'] = 1;
  240. $update_fields['date_added'] = Pdb::now();
  241. $update_fields['date_modified'] = Pdb::now();
  242.  
  243. $item_id = Pdb::insert('operators', $update_fields);
  244.  
  245. $this->logAdd('operators', $item_id);
  246.  
  247. AdminAuth::changePassword($_POST['password1'], $item_id);
  248.  
  249. // Update the categories
  250. $this->updateCategories($item_id, $_POST['categories']);
  251.  
  252. // Commit
  253. Pdb::commit();
  254.  
  255. // Send email, if message is set
  256. $_POST['password'] = $_POST['password1'];
  257. $text = EmailText::getHtml('operator.welcome', $_POST);
  258. if (trim(strip_tags($text)) != '') {
  259. $mail = new Email();
  260. $mail->AddAddress($_POST['email']);
  261. $mail->Subject = 'New operator account created';
  262. $mail->SkinnedHTML($text);
  263. $mail->Send();
  264. }
  265.  
  266. return true;
  267. }
  268.  
  269.  
  270. /**
  271.   * Pre-render hook for editing
  272.   **/
  273. protected function _editPreRender($view, $item_id)
  274. {
  275. parent::_editPreRender($view, $item_id);
  276.  
  277. if (AdminAuth::hasDatabaseRecord() and $item_id == AdminAuth::getId()) {
  278. Url::redirect('admin/intro/my_settings');
  279. }
  280.  
  281. $view->cats = $this->manage_cats;
  282.  
  283. foreach ($view->data['categories'] as $cat_id) {
  284. if (!$this->manage_cats[$cat_id]) {
  285. Notification::error('This operator is in a category you do not have permission to manage - category id ' . $cat_id);
  286. Url::redirect('admin/intro/operator');
  287. }
  288. }
  289. }
  290.  
  291.  
  292. /**
  293.   * Saves the provided POST data the specified record
  294.   *
  295.   * @param int $item_id The record to update
  296.   * @param bool True on success, false on failure
  297.   **/
  298. public function _editSave($item_id)
  299. {
  300. $item_id = (int) $item_id;
  301.  
  302. if (AdminAuth::hasDatabaseRecord() and $item_id == AdminAuth::getId()) {
  303. Url::redirect('admin/intro/my_settings');
  304. }
  305.  
  306. $can_access = AdminPerms::canEditOperator($item_id);
  307. if (!$can_access) return false;
  308.  
  309. $_SESSION['admin']['field_values'] = Validator::trim($_POST);
  310. $result = true;
  311.  
  312. $valid = new Validator($_POST);
  313. $valid->required(['username', 'name', 'email']);
  314. $valid->check('username', 'Validity::length', 0, 50);
  315. $valid->check('username', 'Validity::uniqueValue', 'operators', 'username', $item_id, 'An operator already exists with that username');
  316. $valid->check('username', 'Validity::regex', '/^[a-zA-Z0-9]+$/');
  317. $valid->check('name', 'Validity::length', 0, 200);
  318. $valid->check('email', 'Validity::length', 0, 200);
  319. $valid->check('password1', 'Validity::length', 8, self::MAX_PASSWORD_LENGTH);
  320. $valid->check('password2', 'Validity::length', 8, self::MAX_PASSWORD_LENGTH);
  321. $valid->multipleCheck(['password1', 'password2'], 'Validity::allMatch');
  322.  
  323. if ($valid->hasErrors()) {
  324. $_SESSION['admin']['field_errors'] = $valid->getFieldErrors();
  325. $valid->createNotifications();
  326. $result = false;
  327. }
  328.  
  329. if ($_POST['password1'] and $_POST['password1'] === $_POST['password2']) {
  330. // Check password is complex enough
  331. $complexity = Security::passwordComplexity($_POST['password1'], 8, 2, true);
  332. if (!empty($complexity)) {
  333. $_SESSION['admin']['field_errors']['password1'] = 'Not complex enough';
  334. $_SESSION['admin']['field_errors']['password2'] = 'Not complex enough';
  335. $result = false;
  336.  
  337. Notification::error('Password does not meet complexity requirements:');
  338. foreach ($complexity as $c) {
  339. Notification::error(" \xC2\xA0 \xC2\xA0 " . $c);
  340. }
  341. }
  342. }
  343.  
  344. // Check all categories are allowed
  345. if (empty($_POST['categories'])) {
  346. Notification::error('You must choose at least one category');
  347. $result = false;
  348.  
  349. } else {
  350. foreach ($_POST['categories'] as $cat_id) {
  351. if (!$this->manage_cats[$cat_id]) {
  352. Notification::error('You do not have access to manage category id ' . $cat_id);
  353. $result = false;
  354. }
  355. }
  356. }
  357.  
  358. if (! $result) return false;
  359.  
  360. // Start transaction
  361. Pdb::transact();
  362.  
  363. // Update item
  364. $update_fields = array();
  365. $update_fields['name'] = $_POST['name'];
  366. $update_fields['email'] = $_POST['email'];
  367. $update_fields['username'] = $_POST['username'];
  368. $update_fields['date_modified'] = Pdb::now();
  369.  
  370. $logdata = $this->loadRecord('operators', $item_id);
  371.  
  372. Pdb::update('operators', $update_fields, ['id' => $item_id]);
  373.  
  374. $this->logEdit('operators', $item_id, $logdata);
  375.  
  376. if ($_POST['password1']) {
  377. AdminAuth::changePassword($_POST['password1'], $item_id);
  378. $this->logAction('operators', $item_id, 'Change password');
  379. }
  380.  
  381. // Update the categories
  382. $this->updateCategories($item_id, $_POST['categories']);
  383.  
  384. // Commit
  385. Pdb::commit();
  386.  
  387. return true;
  388. }
  389.  
  390.  
  391. /**
  392.   * Prevents deletion of accounts when the user doesn't have access, and deletion of self
  393.   * @param int $item_id The record to delete
  394.   * @return void
  395.   * @throws Exception if deletion not allowed
  396.   */
  397. public function _deletePreSave($item_id)
  398. {
  399. $item_id = (int) $item_id;
  400.  
  401. if (!AdminPerms::canEditOperator($item_id)) {
  402. throw new Exception('Permission denied');
  403. }
  404.  
  405. if ($item_id == AdminAuth::getLocalId()) {
  406. throw new Exception('You cannot delete your own record');
  407. }
  408. }
  409.  
  410. }
  411.  
  412.  
  413.