SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Controllers/Admin/CategoryAdminController.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 Kohana;
  17.  
  18. use karmabunny\pdb\Exceptions\RowMissingException;
  19. use Sprout\Helpers\AdminAuth;
  20. use Sprout\Helpers\AdminError;
  21. use Sprout\Helpers\AdminPerms;
  22. use Sprout\Helpers\Category;
  23. use Sprout\Helpers\Csrf;
  24. use Sprout\Helpers\Enc;
  25. use Sprout\Helpers\Inflector;
  26. use Sprout\Helpers\Notification;
  27. use Sprout\Helpers\Pdb;
  28. use Sprout\Helpers\Url;
  29. use Sprout\Helpers\Validator;
  30. use Sprout\Helpers\View;
  31.  
  32.  
  33. /**
  34. * This is a generic controller which category controllers should extend.
  35. **/
  36. abstract class CategoryAdminController extends ManagedAdminController {
  37. protected $controller_name;
  38. protected $friendly_name;
  39. protected $main_columns = ['Name' => 'name'];
  40. protected $add_defaults = array();
  41.  
  42.  
  43. /**
  44.   * The view to use for adding new category records. Loaded in a popup
  45.   **/
  46. protected $add_view_name = 'sprout/admin/categories_add';
  47.  
  48. /**
  49.   * The view to use for editing existing category records
  50.   **/
  51. protected $edit_view_name = 'sprout/admin/categories_edit';
  52.  
  53.  
  54. /**
  55.   * Instance of the parent controller
  56.   **/
  57. protected $parent_inst;
  58.  
  59.  
  60. public function __construct()
  61. {
  62. $base_name = str_replace('_category', '', $this->controller_name);
  63. $this->table_name = Category::tableMain2cat(Inflector::plural($base_name));
  64.  
  65. $parent_class = preg_replace('/CategoryAdminController$/', 'AdminController', get_class($this));
  66.  
  67. $this->parent_inst = new $parent_class;
  68.  
  69. parent::__construct();
  70. }
  71.  
  72.  
  73. /**
  74.   * Return the instance of the parent controller
  75.   **/
  76. public final function getParentInst() {
  77. return $this->parent_inst;
  78. }
  79.  
  80.  
  81. /**
  82.   * Proxy for top nav name => the main controller class
  83.   **/
  84. public function getTopnavName()
  85. {
  86. return $this->parent_inst->getTopnavName();
  87. }
  88.  
  89. /**
  90.   * Proxy for navigation => the main controller class
  91.   **/
  92. public function _getNavigation()
  93. {
  94. return $this->parent_inst->_getNavigation();
  95. }
  96.  
  97. /**
  98.   * Proxy for tools => the main controller class
  99.   **/
  100. public function _getTools()
  101. {
  102. return $this->parent_inst->_getTools();
  103. }
  104.  
  105.  
  106. /**
  107.   * Return the fields to show in the sidebar when adding or editing a record.
  108.   * These fields are shown under a heading of "Visibility"
  109.   *
  110.   * Key is the field name, value is the field label
  111.   *
  112.   * @return array
  113.   */
  114. public function _getVisibilityFields()
  115. {
  116. return [];
  117. }
  118.  
  119.  
  120. /**
  121.   * Returns the add form for adding a record
  122.   *
  123.   * @return string The HTML code which represents the add form
  124.   **/
  125. public function _getAddForm()
  126. {
  127. if (! $this->parent_inst->catAllowAdd()) {
  128. return '<p><i>'
  129. . 'Contact ' . Enc::html(Kohana::config('branding.support_organisation')) . ' to have categories added'
  130. . '</i></p>';
  131. }
  132.  
  133. if (!empty($_SESSION['admin']['field_values'])) {
  134. $data = $_SESSION['admin']['field_values'];
  135. unset($_SESSION['admin']['field_values']);
  136. } else {
  137. $data = $this->add_defaults;
  138. }
  139.  
  140. $view = new View($this->add_view_name);
  141. $view->controller_name = $this->controller_name;
  142. $view->data = $data;
  143. if (!empty($_SESSION['admin']['field_errors'])) {
  144. $view->errors = $_SESSION['admin']['field_errors'];
  145. } else {
  146. $view->errors = [];
  147. }
  148.  
  149. $this->_addPreRender($view);
  150.  
  151. return array(
  152. 'title' => 'Adding ' . Enc::html(Inflector::singular($this->friendly_name)),
  153. 'content' => $view->render()
  154. );
  155. }
  156.  
  157.  
  158. /**
  159.   * Saves the provided POST data into a new record in the database
  160.   *
  161.   * @param int $item_id After saving, the new record id will be returned in this parameter
  162.   * @param bool True on success, false on failure
  163.   **/
  164. public function _addSave(&$item_id)
  165. {
  166. $_SESSION['admin']['field_values'] = $_POST;
  167.  
  168. $valid = new Validator($_POST);
  169. $valid->required(['name']);
  170. $valid->check('name', 'Validity::length', 0, 50);
  171. if ($valid->hasErrors()) {
  172. $_SESSION['admin']['field_errors'] = $valid->getFieldErrors();
  173. $valid->createNotifications();
  174. return false;
  175. }
  176.  
  177. Pdb::transact();
  178.  
  179. $update_fields = [];
  180. $update_fields['name'] = $_POST['name'];
  181. $update_fields['date_added'] = Pdb::now();
  182. $update_fields['date_modified'] = Pdb::now();
  183. $item_id = Pdb::insert($this->table_name, $update_fields);
  184.  
  185. Pdb::commit();
  186.  
  187. return true;
  188. }
  189.  
  190.  
  191. /**
  192.   * Return HTML which represents the form for editing a record
  193.   *
  194.   * @param int $id The id of the record to get the edit form of
  195.   **/
  196. public function _getEditForm($id)
  197. {
  198. if (! $this->parent_inst->catAllowEdit($id)) {
  199. return '<p><i>Contact ' . Enc::html(Kohana::config('branding.support_organisation'))
  200. . ' to have this category edited</i></p>';
  201. }
  202.  
  203. $id = (int) $id;
  204.  
  205. // Get the item
  206. $q = "SELECT * FROM ~{$this->table_name} WHERE id = ?";
  207. $item = Pdb::query($q, [$id], 'row');
  208. $data = $item;
  209.  
  210. if (!empty($_SESSION['admin']['field_values'])) {
  211. $data = $_SESSION['admin']['field_values'];
  212. unset ($_SESSION['admin']['field_values']);
  213. }
  214.  
  215. // Build and execute the view
  216. $view = new View($this->edit_view_name);
  217. $view->controller_name = $this->controller_name;
  218. $view->friendly_name = $this->friendly_name;
  219. $view->id = $id;
  220. $view->item = $item;
  221. $view->data = $data;
  222. if (!empty($_SESSION['admin']['field_errors'])) {
  223. $view->errors = $_SESSION['admin']['field_errors'];
  224. } else {
  225. $view->errors = [];
  226. }
  227. $view->category_archive = $this->parent_inst->getCategoryArchive();
  228.  
  229. $this->_editPreRender($view, $id);
  230.  
  231. return array(
  232. 'title' => 'Editing ' . Enc::html(Inflector::singular($this->friendly_name)) . ' <strong>' . Enc::html($this->_identifier($item)) . '</strong>',
  233. 'content' => $view->render()
  234. );
  235. }
  236.  
  237.  
  238. /**
  239.   * Saves the provided POST data the specified record
  240.   *
  241.   * @param int $item_id The record to update
  242.   * @param bool True on success, false on failure
  243.   **/
  244. public function _editSave($item_id)
  245. {
  246. if (! $this->parent_inst->catAllowEdit($item_id)) {
  247. Notification::error('Unable to edit category');
  248. Url::redirect('admin/edit/' . $this->controller_name . '/' . $item_id);
  249. }
  250.  
  251. $item_id = (int) $item_id;
  252.  
  253. // Validate
  254. $valid = new Validator($_POST);
  255. $valid->required(['name']);
  256. $valid->check('name', 'Validity::length', 0, 50);
  257. if ($valid->hasErrors()) {
  258. $_SESSION['admin']['field_values'] = $_POST;
  259. $_SESSION['admin']['field_errors'] = $valid->getFieldErrors();
  260. $valid->createNotifications();
  261. return false;
  262. }
  263.  
  264. // Start transaction
  265. Pdb::transact();
  266.  
  267. // Update item
  268. $update_fields = [];
  269. $update_fields['name'] = $_POST['name'];
  270.  
  271. if ($this->parent_inst->getCategoryArchive()) {
  272. $update_fields['show_admin'] = $_POST['show_admin'];
  273. }
  274.  
  275. Pdb::update($this->table_name, $update_fields, ['id' => $item_id]);
  276.  
  277. // Commit
  278. $res = Pdb::commit();
  279.  
  280. return true;
  281. }
  282.  
  283.  
  284. /**
  285.   * Shows delete form for deleting this category
  286.   *
  287.   * @param int $id The category id
  288.   **/
  289. public function _getDeleteForm($id)
  290. {
  291. if (! $this->parent_inst->catAllowDelete($id)) {
  292. return '<p><i>Contact ' . Enc::html(Kohana::config('branding.support_organisation'))
  293. . ' to have this category deleted.</i></p>';
  294. }
  295.  
  296. $main_controller = str_replace('_category', '', $this->controller_name);
  297. $main_table = Inflector::plural($main_controller);
  298. $cat_table = Category::tableMain2cat($main_table);
  299. $joiner_table = Category::tableMain2joiner($main_table);
  300.  
  301. $q = "SELECT * FROM ~{$cat_table} WHERE id = ?";
  302. $category = Pdb::q($q, [$id], 'row');
  303.  
  304. $q = "SELECT COUNT(main.id) AS C
  305. FROM ~{$main_table} AS main
  306. INNER JOIN ~{$joiner_table} AS joiner ON joiner.{$main_controller}_id = main.id
  307. WHERE joiner.cat_id = ?";
  308. $num_in_cat = Pdb::q($q, [$id], 'val');
  309.  
  310. $view = new View('sprout/admin/categories_delete');
  311. $view->controller_name = $this->controller_name;
  312. $view->friendly_name = $this->friendly_name;
  313. $view->main_columns = $this->main_columns;
  314.  
  315. $view->id = $id;
  316. $view->category = $category;
  317. $view->num_in_cat = $num_in_cat;
  318.  
  319. return array(
  320. 'title' => 'Delete category <strong>' . Enc::html($category['name']) . '</strong>',
  321. 'content' => $view->render()
  322. );
  323. }
  324.  
  325. /**
  326.   * Deletes a category
  327.   *
  328.   * @param int $id The record to delete
  329.   * @param bool True on success, false on failure
  330.   **/
  331. public function _deleteSave($id)
  332. {
  333. $id = (int) $id;
  334.  
  335. if (! $this->parent_inst->catAllowDelete($id)) {
  336. Notification::error('Unable to delete category');
  337. Url::redirect('admin/delete/' . $this->controller_name . '/' . $id);
  338. }
  339.  
  340. $main_controller = str_replace('_category', '', $this->controller_name);
  341. $main_table = Inflector::plural($main_controller);
  342. $cat_table = Category::tableMain2cat($main_table);
  343. $joiner_table = Category::tableMain2joiner($main_table);
  344.  
  345.  
  346. Pdb::transact();
  347.  
  348. // If required, delete the children record (might be quite slow...)
  349. if ($_POST['mode'] == 'cont') {
  350. $q = "SELECT main.id
  351. FROM ~{$main_table} AS main
  352. INNER JOIN ~{$joiner_table} AS joiner ON joiner.{$main_controller}_id = main.id
  353. WHERE joiner.cat_id = ?
  354. ORDER BY main.id";
  355. $res = Pdb::q($q, [$id], 'col');
  356.  
  357. foreach ($res as $row_id) {
  358. $res = $this->parent_inst->_deleteSave($row_id);
  359. if (! $res) return false;
  360. }
  361. }
  362.  
  363. // Delete category
  364. $this->deleteRecord($this->table_name, $id);
  365.  
  366. // Delete references to category
  367. // N.B. these will already have been deleted if the foreign keys are correctly defined
  368. Pdb::delete($joiner_table, ['cat_id' => $id]);
  369.  
  370. Pdb::commit();
  371.  
  372. return true;
  373. }
  374.  
  375.  
  376. /**
  377.   * Shows the reorder page for re-ordering the items for this category
  378.   **/
  379. public function _extraReorder($category_id)
  380. {
  381. $category_id = (int) $category_id;
  382.  
  383. if (! AdminPerms::controllerAccess($this->parent_inst->getControllerName(), 'reorder')) {
  384. return new AdminError('Access denied');
  385. }
  386.  
  387. $item_name = str_replace('_category', '', $this->controller_name);
  388. $item_table = Inflector::plural($item_name);
  389. $joiner_table = Category::tableMain2joiner($item_table);
  390.  
  391. // Load the category
  392. $q = "SELECT * FROM ~{$this->table_name} WHERE id = ?";
  393. try {
  394. $page = Pdb::q($q, [$category_id], 'row');
  395. } catch (RowMissingException $ex) {
  396. return new AdminError('Invalid id specified - category does not exist');
  397. }
  398.  
  399. // Items in the category
  400. $q = "SELECT item.*
  401. FROM ~{$item_table} AS item
  402. INNER JOIN ~{$joiner_table} AS joiner
  403. ON item.id = joiner.{$item_name}_id
  404. WHERE joiner.cat_id = ?
  405. ORDER BY joiner.record_order";
  406. $items = Pdb::q($q, [$category_id], 'arr');
  407.  
  408. if (count($items) < 2) {
  409. return new AdminError('This category does not have enough items in it for re-ordering.');
  410. }
  411.  
  412. foreach ($items as &$item) {
  413. $item['name'] = $this->parent_inst->_identifier($item);
  414. }
  415.  
  416. // View
  417. $view = new View('sprout/admin/categories_reorder');
  418. $view->id = $category_id;
  419. $view->page = $page;
  420. $view->items = $items;
  421. $view->controller_name = $this->controller_name;
  422. $view->friendly_name = $this->friendly_name;
  423.  
  424. return $view->render();
  425. }
  426.  
  427.  
  428. /**
  429.   * Saves a item reorder
  430.   **/
  431. public function reorderSave($category_id)
  432. {
  433. $category_id = (int) $category_id;
  434. AdminAuth::checkLogin();
  435. Csrf::checkOrDie();
  436.  
  437. if (!$this->parent_inst) $this->init();
  438.  
  439. if (! AdminPerms::controllerAccess($this->parent_inst->getControllerName(), 'reorder')) {
  440. Notification::error('Access denied');
  441. Url::redirect('admin/contents/' . $this->controller_name);
  442. }
  443.  
  444. $item_name = str_replace('_category', '', $this->controller_name);
  445. $joiner_table = Category::tableMain2joiner(Inflector::plural($item_name));
  446.  
  447. $record_order = 1;
  448. foreach ($_POST['items'] as $id) {
  449. $id = (int) $id;
  450.  
  451. $where = ['cat_id' => $category_id, "{$item_name}_id" => $id];
  452. Pdb::update($joiner_table, ['record_order' => $record_order], $where);
  453.  
  454. $record_order++;
  455. }
  456.  
  457. Notification::confirm('Re-order was successful');
  458. Url::redirect("admin/contents/{$item_name}?_category_id={$category_id}");
  459. }
  460.  
  461.  
  462.  
  463. /**
  464.   * Shows the reorder page for re-ordering the categories.
  465.   **/
  466. public function _extraReorderCategories()
  467. {
  468.  
  469. if (! AdminPerms::controllerAccess($this->parent_inst->getControllerName(), 'categories')) {
  470. return new AdminError('Access denied');
  471. }
  472.  
  473. // Load the category
  474. $q = "SELECT id, name FROM ~{$this->table_name} ORDER BY record_order, name";
  475. $items = Pdb::q($q, [], 'arr');
  476.  
  477. if (count($items) < 2) {
  478. return new AdminError('There are not enough categories for re-ordering.');
  479. }
  480.  
  481. // View
  482. $view = new View('sprout/admin/categories_reorder');
  483. $view->action = 'admin/call/' . $this->controller_name . '/reorderCategoriesSave';
  484. $view->items = $items;
  485.  
  486. return $view->render();
  487. }
  488.  
  489.  
  490. /**
  491.   * Saves a item reorder
  492.   **/
  493. public function reorderCategoriesSave()
  494. {
  495. AdminAuth::checkLogin();
  496. Csrf::checkOrDie();
  497.  
  498. if (! AdminPerms::controllerAccess($this->parent_inst->getControllerName(), 'categories')) {
  499. Notification::error('Access denied');
  500. Url::redirect('admin/contents/' . $this->controller_name);
  501. }
  502.  
  503.  
  504. $record_order = 1;
  505. foreach ($_POST['items'] as $id) {
  506. $id = (int) $id;
  507.  
  508. Pdb::update($this->table_name, ['record_order' => $record_order], ['id' => $id]);
  509.  
  510. $record_order++;
  511. }
  512.  
  513. $item_name = str_replace('_category', '', $this->controller_name);
  514. Notification::confirm('Category re-order was successful');
  515. Url::redirect("admin/contents/{$item_name}");
  516. }
  517.  
  518.  
  519. /**
  520.   * Archive a category
  521.   */
  522. public function ajaxArchiveAction($category_id)
  523. {
  524. if (!Csrf::check()) die('Bad token');
  525.  
  526. $data = [];
  527. $data['show_admin'] = 0;
  528. Pdb::update($this->table_name, $data, ['id' => $category_id]);
  529.  
  530. echo 'OK';
  531. }
  532.  
  533.  
  534. /**
  535.   * Unarchive a category
  536.   */
  537. public function ajaxUnarchiveAction($category_id)
  538. {
  539. if (!Csrf::check()) die('Bad token');
  540.  
  541. $data = [];
  542. $data['show_admin'] = 1;
  543. Pdb::update($this->table_name, $data, ['id' => $category_id]);
  544.  
  545. echo 'OK';
  546. }
  547.  
  548. }
  549.