/libraries/joomla/application/component/controllerform.php

https://bitbucket.org/pasamio/jauthentication · PHP · 591 lines · 288 code · 95 blank · 208 comment · 46 complexity · 9b7be36a5b025ab4d272a3aef0bf743a MD5 · raw file

  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Application
  5. *
  6. * @copyright Copyright (C) 2005 - 2011 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. jimport('joomla.application.component.controller');
  11. // @TODO Add ability to set redirect manually to better cope with frontend usage.
  12. /**
  13. * Controller tailored to suit most form-based admin operations.
  14. *
  15. * @package Joomla.Platform
  16. * @subpackage Application
  17. * @since 11.1
  18. */
  19. class JControllerForm extends JController
  20. {
  21. /**
  22. * @var string The context for storing internal data, e.g. record.
  23. * @since 11.1
  24. */
  25. protected $context;
  26. /**
  27. * @var string The URL option for the component.
  28. * @since 11.1
  29. */
  30. protected $option;
  31. /**
  32. * @var string The URL view item variable.
  33. * @since 11.1
  34. */
  35. protected $view_item;
  36. /**
  37. * @var string The URL view list variable.
  38. * @since 11.1
  39. */
  40. protected $view_list;
  41. /**
  42. * @var string The prefix to use with controller messages.
  43. * @since 11.1
  44. */
  45. protected $text_prefix;
  46. /**
  47. * Constructor.
  48. *
  49. * @param array An optional associative array of configuration settings.
  50. *
  51. * @return JControllerForm
  52. * @see JController
  53. * @since 11.1
  54. */
  55. public function __construct($config = array())
  56. {
  57. parent::__construct($config);
  58. // Guess the option as com_NameOfController
  59. if (empty($this->option)) {
  60. $this->option = 'com_'.strtolower($this->getName());
  61. }
  62. // Guess the JText message prefix. Defaults to the option.
  63. if (empty($this->text_prefix)) {
  64. $this->text_prefix = strtoupper($this->option);
  65. }
  66. // Guess the context as the suffix, eg: OptionControllerContent.
  67. if (empty($this->context)) {
  68. $r = null;
  69. if (!preg_match('/(.*)Controller(.*)/i', get_class($this), $r)) {
  70. JError::raiseError(500, JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'));
  71. }
  72. $this->context = strtolower($r[2]);
  73. }
  74. // Guess the item view as the context.
  75. if (empty($this->view_item)) {
  76. $this->view_item = $this->context;
  77. }
  78. // Guess the list view as the plural of the item view.
  79. if (empty($this->view_list)) {
  80. // @TODO Probably worth moving to an inflector class based on
  81. // http://kuwamoto.org/2007/12/17/improved-pluralizing-in-php-actionscript-and-ror/
  82. // Simple pluralisation based on public domain snippet by Paul Osman
  83. // For more complex types, just manually set the variable in your class.
  84. $plural = array(
  85. array( '/(x|ch|ss|sh)$/i', "$1es"),
  86. array( '/([^aeiouy]|qu)y$/i', "$1ies"),
  87. array( '/([^aeiouy]|qu)ies$/i', "$1y"),
  88. array( '/(bu)s$/i', "$1ses"),
  89. array( '/s$/i', "s"),
  90. array( '/$/', "s")
  91. );
  92. // check for matches using regular expressions
  93. foreach ($plural as $pattern)
  94. {
  95. if (preg_match($pattern[0], $this->view_item)) {
  96. $this->view_list = preg_replace( $pattern[0], $pattern[1], $this->view_item);
  97. break;
  98. }
  99. }
  100. }
  101. // Apply, Save & New, and Save As copy should be standard on forms.
  102. $this->registerTask('apply', 'save');
  103. $this->registerTask('save2new', 'save');
  104. $this->registerTask('save2copy', 'save');
  105. }
  106. /**
  107. * Method to add a new record.
  108. *
  109. * @return mixed True if the record can be added, a JError object if not.
  110. * @since 11.1
  111. */
  112. public function add()
  113. {
  114. // Initialise variables.
  115. $app = JFactory::getApplication();
  116. $context = "$this->option.edit.$this->context";
  117. // Access check.
  118. if (!$this->allowAdd()) {
  119. // Set the internal error and also the redirect error.
  120. $this->setError(JText::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'));
  121. $this->setMessage($this->getError(), 'error');
  122. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  123. return false;
  124. }
  125. // Clear the record edit information from the session.
  126. $app->setUserState($context.'.data', null);
  127. // Redirect to the edit screen.
  128. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend(), false));
  129. return true;
  130. }
  131. /**
  132. * Method to check if you can add a new record.
  133. *
  134. * Extended classes can override this if necessary.
  135. *
  136. * @param array An array of input data.
  137. *
  138. * @return boolean
  139. * @since 11.1
  140. */
  141. protected function allowAdd($data = array())
  142. {
  143. $user = JFactory::getUser();
  144. return ($user->authorise('core.create', $this->option) ||
  145. count($user->getAuthorisedCategories($this->option, 'core.create')));
  146. }
  147. /**
  148. * Method to check if you can add a new record.
  149. *
  150. * Extended classes can override this if necessary.
  151. *
  152. * @param array An array of input data.
  153. * @param string The name of the key for the primary key.
  154. *
  155. * @return boolean
  156. * @since 11.1
  157. */
  158. protected function allowEdit($data = array(), $key = 'id')
  159. {
  160. return JFactory::getUser()->authorise('core.edit', $this->option);
  161. }
  162. /**
  163. * Method to check if you can save a new or existing record.
  164. *
  165. * Extended classes can override this if necessary.
  166. *
  167. * @param array An array of input data.
  168. * @param string The name of the key for the primary key.
  169. *
  170. * @return boolean
  171. * @since 11.1
  172. */
  173. protected function allowSave($data, $key = 'id')
  174. {
  175. // Initialise variables.
  176. $recordId = isset($data[$key]) ? $data[$key] : '0';
  177. if ($recordId) {
  178. return $this->allowEdit($data, $key);
  179. } else {
  180. return $this->allowAdd($data);
  181. }
  182. }
  183. /**
  184. * Method to cancel an edit.
  185. *
  186. * @param string $key The name of the primary key of the URL variable.
  187. *
  188. * @return Boolean True if access level checks pass, false otherwise.
  189. * @since 11.1
  190. */
  191. public function cancel($key = null)
  192. {
  193. JRequest::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
  194. // Initialise variables.
  195. $app = JFactory::getApplication();
  196. $model = $this->getModel();
  197. $table = $model->getTable();
  198. $checkin = property_exists($table, 'checked_out');
  199. $context = "$this->option.edit.$this->context";
  200. if (empty($key)) {
  201. $key = $table->getKeyName();
  202. }
  203. $recordId = JRequest::getInt($key);
  204. // Attempt to check-in the current record.
  205. if ($recordId) {
  206. // Check we are holding the id in the edit list.
  207. if (!$this->checkEditId($context, $recordId)) {
  208. // Somehow the person just went to the form - we don't allow that.
  209. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId));
  210. $this->setMessage($this->getError(), 'error');
  211. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  212. return false;
  213. }
  214. if ($checkin) {
  215. if ($model->checkin($recordId) === false) {
  216. // Check-in failed, go back to the record and display a notice.
  217. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
  218. $this->setMessage($this->getError(), 'error');
  219. $this->setRedirect('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $key));
  220. return false;
  221. }
  222. }
  223. }
  224. // Clean the session data and redirect.
  225. $this->releaseEditId($context, $recordId);
  226. $app->setUserState($context.'.data', null);
  227. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  228. return true;
  229. }
  230. /**
  231. * Method to edit an existing record.
  232. *
  233. * @param string $key The name of the primary key of the URL variable.
  234. * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
  235. *
  236. * @return Boolean True if access level check and checkout passes, false otherwise.
  237. * @since 11.1
  238. */
  239. public function edit($key = null, $urlVar = null)
  240. {
  241. // Initialise variables.
  242. $app = JFactory::getApplication(); or was most recently performed
  243. $model = $this->getModel();
  244. $table = $model->getTable();
  245. $cid = JRequest::getVar('cid', array(), 'post', 'array');
  246. $context = "$this->option.edit.$this->context";
  247. $append = '';
  248. // Determine the name of the primary key for the data.
  249. if (empty($key)) {
  250. $key = $table->getKeyName();
  251. }
  252. // To avoid data collisions the urlVar may be different from the primary key.
  253. if (empty($urlVar)) {
  254. $urlVar = $key;
  255. }
  256. // Get the previous record id (if any) and the current record id.
  257. $recordId = (int) (count($cid) ? $cid[0] : JRequest::getInt($urlVar));
  258. $checkin = property_exists($table, 'checked_out');
  259. // Access check.
  260. if (!$this->allowEdit(array($key => $recordId), $key)) {
  261. $this->setError(JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
  262. $this->setMessage($this->getError(), 'error');
  263. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  264. return false;
  265. }
  266. // Attempt to check-out the new record for editing and redirect.
  267. if ($checkin && !$model->checkout($recordId)) {
  268. // Check-out failed, display a notice but allow the user to see the record.
  269. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKOUT_FAILED', $model->getError()));
  270. $this->setMessage($this->getError(), 'error');
  271. $this->setRedirect('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $urlVar));
  272. return false;
  273. }
  274. else {
  275. // Check-out succeeded, push the new record id into the session.
  276. $this->holdEditId($context, $recordId);
  277. $app->setUserState($context.'.data', null);
  278. $this->setRedirect('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $urlVar));
  279. return true;
  280. }
  281. }
  282. /**
  283. * Method to get a model object, loading it if required.
  284. *
  285. * @param string $name The model name. Optional.
  286. * @param string $prefix The class prefix. Optional.
  287. * @param array $config Configuration array for model. Optional.
  288. *
  289. * @return object The model.
  290. * @since 11.1
  291. */
  292. public function getModel($name = '', $prefix = '', $config = array('ignore_request' => true))
  293. {
  294. if (empty($name)) {
  295. $name = $this->context;
  296. }
  297. return parent::getModel($name, $prefix, $config);
  298. }
  299. /**
  300. * Gets the URL arguments to append to an item redirect.
  301. *
  302. * @param int $recordId The primary key id for the item.
  303. * @param string $urlVar The name of the URL variable for the id.
  304. *
  305. * @return string The arguments to append to the redirect URL.
  306. * @since 11.1
  307. */
  308. protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
  309. {
  310. $tmpl = JRequest::getCmd('tmpl');
  311. $layout = JRequest::getCmd('layout', 'edit');
  312. $append = '';
  313. // Setup redirect info.
  314. if ($tmpl) {
  315. $append .= '&tmpl='.$tmpl;
  316. }
  317. if ($layout) {
  318. $append .= '&layout='.$layout;
  319. }
  320. if ($recordId) {
  321. $append .= '&'.$urlVar.'='.$recordId;
  322. }
  323. return $append;
  324. }
  325. /**
  326. * Gets the URL arguments to append to a list redirect.
  327. *
  328. * @return string The arguments to append to the redirect URL.
  329. * @since 11.1
  330. */
  331. protected function getRedirectToListAppend()
  332. {
  333. $tmpl = JRequest::getCmd('tmpl');
  334. $append = '';
  335. // Setup redirect info.
  336. if ($tmpl) {
  337. $append .= '&tmpl='.$tmpl;
  338. }
  339. return $append;
  340. }
  341. /**
  342. * Function that allows child controller access to model data after the data has been saved.
  343. *
  344. * @param JModel $model The data model object.
  345. * @param array $validData The validated data.
  346. *
  347. * @return void
  348. * @since 11.1
  349. */
  350. protected function postSaveHook(JModel &$model, $validData = array())
  351. {
  352. }
  353. /**
  354. * Method to save a record.
  355. *
  356. * @param string $key The name of the primary key of the URL variable.
  357. * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
  358. *
  359. * @return Boolean True if successful, false otherwise.
  360. * @since 11.1
  361. */
  362. public function save($key = null, $urlVar = null)
  363. {
  364. // Check for request forgeries.
  365. JRequest::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
  366. // Initialise variables.
  367. $app = JFactory::getApplication();
  368. $lang = JFactory::getLanguage();
  369. $model = $this->getModel();
  370. $table = $model->getTable();
  371. $data = JRequest::getVar('jform', array(), 'post', 'array');
  372. $checkin = property_exists($table, 'checked_out');
  373. $context = "$this->option.edit.$this->context";
  374. $task = $this->getTask();
  375. // Determine the name of the primary key for the data.
  376. if (empty($key)) {
  377. $key = $table->getKeyName();
  378. }
  379. // To avoid data collisions the urlVar may be different from the primary key.
  380. if (empty($urlVar)) {
  381. $urlVar = $key;
  382. }
  383. $recordId = JRequest::getInt($urlVar);
  384. $session = JFactory::getSession();
  385. $registry = $session->get('registry');
  386. if (!$this->checkEditId($context, $recordId)) {
  387. // Somehow the person just went to the form and tried to save it. We don't allow that.
  388. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId));
  389. $this->setMessage($this->getError(), 'error');
  390. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  391. return false;
  392. }
  393. // Populate the row id from the session.
  394. $data[$key] = $recordId;
  395. // The save2copy task needs to be handled slightly differently.
  396. if ($task == 'save2copy') {
  397. // Check-in the original row.
  398. if ($checkin && $model->checkin($data[$key]) === false) {
  399. // Check-in failed. Go back to the item and display a notice.
  400. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
  401. $this->setMessage($this->getError(), 'error');
  402. $this->setRedirect('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $urlVar));
  403. return false;
  404. }
  405. // Reset the ID and then treat the request as for Apply.
  406. $data[$key] = 0;
  407. $task = 'apply';
  408. }
  409. // Access check.
  410. if (!$this->allowSave($data)) {
  411. $this->setError(JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
  412. $this->setMessage($this->getError(), 'error');
  413. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  414. return false;
  415. }
  416. // Validate the posted data.
  417. // Sometimes the form needs some posted data, such as for plugins and modules.
  418. $form = $model->getForm($data, false);
  419. if (!$form) {
  420. $app->enqueueMessage($model->getError(), 'error');
  421. return false;
  422. }
  423. // Test whether the data is valid.
  424. $validData = $model->validate($form, $data);
  425. // Check for validation errors.
  426. if ($validData === false) {
  427. // Get the validation messages.
  428. $errors = $model->getErrors();
  429. // Push up to three validation messages out to the user.
  430. for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
  431. {
  432. if (JError::isError($errors[$i])) {
  433. $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
  434. }
  435. else {
  436. $app->enqueueMessage($errors[$i], 'warning');
  437. }
  438. }
  439. // Save the data in the session.
  440. $app->setUserState($context.'.data', $data);
  441. // Redirect back to the edit screen.
  442. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $key), false));
  443. return false;
  444. }
  445. // Attempt to save the data.
  446. if (!$model->save($validData)) {
  447. // Save the data in the session.
  448. $app->setUserState($context.'.data', $validData);
  449. // Redirect back to the edit screen.
  450. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
  451. $this->setMessage($this->getError(), 'error');
  452. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $key), false));
  453. return false;
  454. }
  455. // Save succeeded, so check-in the record.
  456. if ($checkin && $model->checkin($validData[$key]) === false) {
  457. // Save the data in the session.
  458. $app->setUserState($context.'.data', $validData);
  459. // Check-in failed, so go back to the record and display a notice.
  460. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
  461. $this->setMessage($this->getError(), 'error');
  462. $this->setRedirect('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $key));
  463. return false;
  464. }
  465. $this->setMessage(JText::_(($lang->hasKey($this->text_prefix.($recordId==0 && $app->isSite() ? '_SUBMIT' : '').'_SAVE_SUCCESS') ? $this->text_prefix : 'JLIB_APPLICATION') . ($recordId==0 && $app->isSite() ? '_SUBMIT' : '') . '_SAVE_SUCCESS'));
  466. // Redirect the user and adjust session state based on the chosen task.
  467. switch ($task)
  468. {
  469. case 'apply':
  470. // Set the record data in the session.
  471. $recordId = $model->getState($this->context.'.id');
  472. $this->holdEditId($context, $recordId);
  473. $app->setUserState($context.'.data', null);
  474. // Redirect back to the edit screen.
  475. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend($recordId, $key), false));
  476. break;
  477. case 'save2new':
  478. // Clear the record id and data from the session.
  479. $this->releaseEditId($context, $recordId);
  480. $app->setUserState($context.'.data', null);
  481. // Redirect back to the edit screen.
  482. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_item.$this->getRedirectToItemAppend(null, $key), false));
  483. break;
  484. default:
  485. // Clear the record id and data from the session.
  486. $this->releaseEditId($context, $recordId);
  487. $app->setUserState($context.'.data', null);
  488. // Redirect to the list screen.
  489. $this->setRedirect(JRoute::_('index.php?option='.$this->option.'&view='.$this->view_list.$this->getRedirectToListAppend(), false));
  490. break;
  491. }
  492. // Invoke the postSave method to allow for the child class to access the model.
  493. $this->postSaveHook($model, $validData);
  494. return true;
  495. }
  496. }