SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Controllers/AdminAjaxController.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;
  15.  
  16. use DOMDocument;
  17. use Exception;
  18.  
  19. use Kohana;
  20.  
  21. use Sprout\Helpers\Admin;
  22. use Sprout\Helpers\AdminAuth;
  23. use Sprout\Helpers\AttrEditor;
  24. use Sprout\Helpers\DocImport\DocImport;
  25. use Sprout\Helpers\Csrf;
  26. use Sprout\Helpers\Enc;
  27. use Sprout\Helpers\Form;
  28. use Sprout\Helpers\FrontEndEntrance;
  29. use Sprout\Helpers\Json;
  30. use Sprout\Helpers\LinkSpec;
  31. use Sprout\Helpers\Navigation;
  32. use Sprout\Helpers\Needs;
  33. use Sprout\Helpers\Pdb;
  34. use Sprout\Helpers\Register;
  35. use Sprout\Helpers\Session;
  36. use Sprout\Helpers\Slug;
  37. use Sprout\Helpers\Sprout;
  38. use Sprout\Helpers\Tags;
  39. use Sprout\Helpers\View;
  40. use Sprout\Helpers\Widgets;
  41.  
  42.  
  43. /**
  44.  * Handles processing of various admin AJAX methods including: widget settings; links; and categories.
  45.  */
  46. class AdminAjaxController extends Controller
  47. {
  48.  
  49. /**
  50.   * Constructor
  51.   **/
  52. public function __construct()
  53. {
  54. parent::__construct();
  55.  
  56. $this->session = Session::instance();
  57.  
  58. header('Content-type: text/html; charset=UTF-8');
  59. }
  60.  
  61.  
  62. /**
  63.   * Returns the HTML for the settings for an individual widget.
  64.   *
  65.   * @param string $widget_name The name of the widget to get settings for.
  66.   **/
  67. public function widgetSettings($widget_name)
  68. {
  69. AdminAuth::checkLogin();
  70.  
  71. // Grab data from either GET or POST
  72. if (!empty($_POST['prefix'])) {
  73. $settings = json_decode($_POST['settings'], true);
  74. $prefix = trim($_POST['prefix']);
  75.  
  76. } else if (!empty($_GET['prefix'])) {
  77. $settings = json_decode($_GET['settings'], true);
  78. $prefix = trim($_GET['prefix']);
  79.  
  80. } else {
  81. Json::error(
  82. 'Error loading widget settings; '
  83. . ' contact ' . Kohana::config('branding.support_organisation') . ' for assistance'
  84. );
  85. }
  86.  
  87. // Prep the widget
  88. try {
  89. $widget = Widgets::instantiate($widget_name);
  90. if (empty($settings)) {
  91. $settings = $widget->getDefaultSettings();
  92. }
  93. $widget->importSettings($settings);
  94. } catch (Exception $ex) {
  95. Json::error($ex);
  96. }
  97.  
  98. $info_labels = $widget->getInfoLabels();
  99. if (!$info_labels) {
  100. $info_labels = null;
  101. }
  102.  
  103. // Output back to the browser
  104. try {
  105. Form::setData([$prefix => $widget->getSettings()]);
  106. Form::setFieldIdPrefix($prefix);
  107. Form::setFieldNameFormat($prefix . '[%s]');
  108.  
  109. // TODO one day
  110. Form::setErrors([]);
  111.  
  112. // Need to call settings form first (to load Needs) but then render needs first
  113. $form = $widget->getSettingsForm();
  114. $form = Needs::dynamicNeedsLoader() . $form;
  115.  
  116. Json::confirm(array(
  117. 'settings' => $form,
  118. 'edit_url' => $widget->getEditUrl(),
  119. 'description' => Enc::html($widget->getFriendlyDesc()),
  120. 'info_labels' => $info_labels,
  121. ));
  122. } catch (Exception $ex) {
  123. Json::error($ex);
  124. }
  125. }
  126.  
  127.  
  128. /**
  129.   * AJAX-loaded popup content for UI to manage widget display conditions
  130.   *
  131.   * @return void Outputs HTML directly
  132.   */
  133. public function widgetDispConds()
  134. {
  135. AdminAuth::checkLogin();
  136.  
  137. Form::setData($_POST);
  138.  
  139. $cond_list_params = [
  140. 'fields' => Register::getDisplayConditions(),
  141. 'url' => 'admin_ajax/widget_disp_cond_params',
  142. ];
  143.  
  144. $view = new View('sprout/admin/widget_disp_conds');
  145. $view->cond_list_params = $cond_list_params;
  146. echo $view->render();
  147. }
  148.  
  149.  
  150. /**
  151.   * Callback url for {@see Fb::conditionsList} for the widget display conditions
  152.   *
  153.   * Input is GET params 'field', 'op', 'val'
  154.   *
  155.   * Output is JSON with two keys, 'op' and 'val'. They are both
  156.   * HTML strings containing {@see Form} fields for the operator
  157.   * dropdown and the values dropdown/textbox
  158.   *
  159.   * @return void Outputs JSON and then terminates
  160.   */
  161. public function widgetDispCondParams()
  162. {
  163. AdminAuth::checkLogin();
  164.  
  165. Form::setData($_GET);
  166.  
  167. try {
  168. $inst = Sprout::instance($_GET['field'], ['Sprout\\Helpers\\DisplayConditions\\DisplayCondition']);
  169. } catch (Exception $ex) {
  170. Json::error($ex);
  171. }
  172.  
  173. $op = Form::dropdown('op', ['-dropdown-top' => ' '], $inst->getOperators());
  174.  
  175. $type = $inst->getParamType();
  176. switch ($type) {
  177. case 'text':
  178. $val = Form::text('val');
  179. break;
  180. case 'dropdown':
  181. $val = Form::dropdown('val', ['-dropdown-top' => ' '], $inst->getParamValues());
  182. break;
  183. default:
  184. Json::error('Invalid param type "' . $type . '"');
  185. }
  186.  
  187. Json::out([
  188. 'op' => $op,
  189. 'val' => $val,
  190. ]);
  191. }
  192.  
  193.  
  194. /**
  195.   * Popup for adding an addon
  196.   **/
  197. public function addAddon($area_id, $field_name)
  198. {
  199. AdminAuth::checkLogin();
  200.  
  201. $area_id = (int) $area_id;
  202. $field_name = trim(preg_replace('/[^_0-9a-zA-Z]/', '', $field_name));
  203.  
  204. $areas = Kohana::config('sprout.widget_areas');
  205. $area = $areas[$area_id];
  206. $avail_widgets = $area->getWidgets();
  207.  
  208. // Prep list and sort
  209. $widgets = array();
  210. foreach ($avail_widgets as $name) {
  211. $inst = Widgets::instantiate($name);
  212. $widgets[$inst->getFriendlyName()] = array(
  213. $name,
  214. $inst->getFriendlyName(),
  215. $inst->getFriendlyDesc(),
  216. $inst->getNotAvailableReason(),
  217. );
  218. }
  219. ksort($widgets);
  220.  
  221. // Render view
  222. $view = new View('sprout/ajax/add_addon');
  223. $view->field_name = $field_name;
  224. $view->widgets = $widgets;
  225. echo $view->render();
  226. }
  227.  
  228.  
  229. public function footerCompat()
  230. {
  231. AdminAuth::checkLogin();
  232.  
  233. echo new View('sprout/admin/footer_compat');
  234. }
  235.  
  236.  
  237. public function getTagSuggestions($table = null)
  238. {
  239. AdminAuth::checkLogin();
  240.  
  241. $ret = Tags::suggestTags($table, $_GET['prefix']);
  242. Json::out($ret);
  243. }
  244.  
  245. public function getEntranceArguments($class)
  246. {
  247. AdminAuth::checkLogin();
  248.  
  249. $inst = Sprout::instance($class);
  250.  
  251. if (!($inst instanceof FrontEndEntrance)) {
  252. Json::error('Invalid class; missing interface');
  253. }
  254.  
  255. $args = $inst->_getEntranceArguments();
  256.  
  257. if (empty($args)) {
  258. $args = array('' => '- Nothing available -');
  259. }
  260.  
  261. Json::out($args);
  262. }
  263.  
  264.  
  265. /**
  266.   * Adds a category
  267.   **/
  268. public function addCategory()
  269. {
  270. AdminAuth::checkLogin();
  271. Csrf::checkOrDie();
  272.  
  273. $_POST['name'] = trim(@$_POST['name']);
  274. $_POST['table'] = trim(@$_POST['table']);
  275.  
  276. if ($_POST['name'] == '') Json::error('Please enter a category name to add');
  277. if ($_POST['table'] == '') Json::error('Invalid arguments');
  278.  
  279. $data = [
  280. 'name' => $_POST['name'],
  281. 'date_added' => Pdb::now(),
  282. ];
  283.  
  284. // Incorporate slug where possible
  285. try {
  286. $data['slug'] = Slug::create($_POST['table'], $_POST['name']);
  287. $insert_id = Pdb::insert($_POST['table'], $data);
  288. } catch (Exception $ex) {
  289. unset($data['slug']);
  290. try {
  291. $insert_id = Pdb::insert($_POST['table'], $data);
  292. } catch (Exception $ex) {
  293. Json::error('Database error');
  294. }
  295. }
  296.  
  297. $out = array(
  298. 'success' => 1,
  299. 'id' => $insert_id,
  300. 'name' => $_POST['name'],
  301. );
  302.  
  303. Json::out($out);
  304. }
  305.  
  306.  
  307. /**
  308.   * Load an attribute editor for a given field
  309.   **/
  310. public function attrEditor()
  311. {
  312. AdminAuth::checkLogin();
  313.  
  314. $_POST['val'] = trim(@$_POST['val']);
  315. $_POST['attr_name'] = trim(@$_POST['attr_name']);
  316.  
  317. // Find the attr
  318. $attrs = Register::getPageattrs();
  319.  
  320. if (empty($attrs[$_POST['attr_name']])) {
  321. throw new Exception('Invalid field');
  322. }
  323.  
  324. $attr = $attrs[$_POST['attr_name']];
  325.  
  326. // Check the class is valid
  327. $class_name = $attr[1];
  328. if (! class_exists($class_name)) {
  329. throw new Exception('Invalid class for field');
  330. }
  331.  
  332. // Create instance
  333. $inst = new $class_name();
  334. if (!($inst instanceof AttrEditor)) {
  335. throw new Exception('Invalid class for field');
  336. }
  337.  
  338. // And render
  339. $html = $inst->render($_POST['val'], $_POST['attr_name']);
  340. $html = Needs::replacePathsString($html);
  341. $js = $inst->javascript($_POST['val'], $_POST['attr_name']);
  342.  
  343. Json::confirm(array(
  344. 'html' => $html,
  345. 'js' => $js,
  346. ));
  347. }
  348.  
  349.  
  350. /**
  351.   * Load an attribute editor for a given field
  352.   **/
  353. public function lnkEditor()
  354. {
  355. AdminAuth::checkLogin();
  356.  
  357. $_POST['field'] = trim(@$_POST['field']);
  358. $_POST['val'] = trim(@$_POST['val']);
  359. $_POST['type'] = trim(@$_POST['type']);
  360.  
  361. if ($_POST['type'] == '') {
  362. Json::confirm(array(
  363. 'html' => '',
  364. ));
  365. }
  366.  
  367. // Helps out attributes which use the page tree
  368. Navigation::loadPageTree($_SESSION['admin']['active_subsite'], true);
  369.  
  370. // Ensures fields using the 'Form' class will have usable id attributes
  371. Form::setFieldIdPrefix('lnkspec-' . time() . rand(0, 999) . '-');
  372.  
  373. // Find the attr
  374. $specs = Register::getLinkspecs();
  375. if (empty($specs[$_POST['type']])) {
  376. throw new Exception('Invalid LinkSpec class');
  377. }
  378.  
  379. // Create instance
  380. $class_name = $_POST['type'];
  381. $inst = new $class_name();
  382. if (!($inst instanceof LinkSpec)) {
  383. throw new Exception('Invalid class for field');
  384. }
  385.  
  386. // And render
  387. $html = $inst->getEditForm($_POST['field'], $_POST['val']);
  388. $html = Needs::replacePathsString($html);
  389.  
  390. Json::confirm(array(
  391. 'html' => $html,
  392. ));
  393. }
  394.  
  395.  
  396. /**
  397.   * Set a given JavaScript tour as complete, preventing it from showing again.
  398.   */
  399. public function setTourCompleted($tour_name)
  400. {
  401. AdminAuth::checkLogin();
  402. Admin::setTourCompleted($tour_name);
  403. echo '.';
  404. }
  405.  
  406.  
  407. /**
  408.   * Called by button handlers on the two RTEs and loaded in a facebox window
  409.   * Shows an interface to load stuff into the editor
  410.   * The editor id will br provided in $elemid
  411.   **/
  412. public function richtextImport($elemid)
  413. {
  414. AdminAuth::checkLogin();
  415.  
  416. $view = new View('sprout/ajax/document_import');
  417. $view->elemid = $elemid;
  418. echo $view->render();
  419. }
  420.  
  421.  
  422. /**
  423.   * Handle an upload of a document. Echos DIV-warapped JSON with the result of the import.
  424.   *
  425.   * Keys include:
  426.   * 'html' The imported HTML of the document
  427.   **/
  428. public function richtextImportIframe()
  429. {
  430. AdminAuth::checkLogin();
  431.  
  432. try {
  433. $inst = DocImport::instance($_FILES['import']['name']);
  434.  
  435. $result = $inst->load($_FILES['import']['tmp_name']);
  436.  
  437. // PHP-8+ deprecated this because it's disabled by default.
  438. if (PHP_VERSION_ID < 80000) {
  439. libxml_disable_entity_loader();
  440. }
  441.  
  442. $dom = new DOMDocument();
  443. $dom->loadXML($result);
  444.  
  445. // TODO: import images
  446.  
  447. $images = array();
  448. $headings = array(1 => 2, 2 => 3, 3 => 4, 4 => 5, 5 => 6);
  449. $html = DocImport::getHtml($dom, $images, $headings);
  450.  
  451. $out = array(
  452. 'html' => $html
  453. );
  454.  
  455. } catch (Exception $ex) {
  456. $out = array('error' => $ex->getMessage());
  457. }
  458.  
  459. echo '<div>', Enc::html(json_encode($out)), '</div>';
  460. }
  461.  
  462.  
  463. /**
  464.   * Demo AJAX JSON callback url for {@see Fb::conditionsList}
  465.   *
  466.   * Input is GET params 'field', 'op', 'val'
  467.   *
  468.   * Output is JSON with two keys, 'op' and 'val'. They are both
  469.   * HTML strings containing {@see Form} fields for the operator
  470.   * dropdown and the values dropdown/textbox
  471.   *
  472.   * @return void Outputs JSON and then terminates
  473.   */
  474. public function styleGuideDemoConditions()
  475. {
  476. AdminAuth::checkLogin();
  477.  
  478. Form::setData($_GET);
  479.  
  480. switch ($_GET['field']) {
  481. case 'name':
  482. $op = Form::dropdown('op', ['-dropdown-top' => ' '], [
  483. '=' => 'Equals',
  484. '!=' => 'Not equals',
  485. 'begin' => 'Begins with',
  486. 'end' => 'Ends with',
  487. ]);
  488. $val = Form::text('val');
  489. break;
  490.  
  491. case 'age':
  492. $op = Form::dropdown('op', ['-dropdown-top' => ' '], [
  493. '=' => 'Equals',
  494. '!=' => 'Not equals',
  495. '>' => 'Greater than',
  496. '>=' => 'Greater than or equal',
  497. '<' => 'Less than',
  498. '<=' => 'Less than or equal',
  499. ]);
  500. $val = Form::text('val');
  501. break;
  502.  
  503. case 'gender':
  504. $op = Form::dropdown('op', ['-dropdown-top' => ' '], [
  505. '=' => 'Is',
  506. '!=' => 'Is not',
  507. ]);
  508. $val = Form::dropdown('val', ['-dropdown-top' => ' '], [
  509. 'f' => 'Female',
  510. 'm' => 'Male',
  511. 'o' => 'Other',
  512. ]);
  513. break;
  514. }
  515.  
  516. Json::out([
  517. 'op' => $op,
  518. 'val' => $val,
  519. ]);
  520. }
  521.  
  522. }
  523.  
  524.  
  525.