PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/legacy/controller/form.php

http://github.com/joomla/joomla-platform
PHP | 792 lines | 440 code | 113 blank | 239 comment | 48 complexity | 7d4859d7531651836d8a3647d1c141ee MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package Joomla.Legacy
  4. * @subpackage Controller
  5. *
  6. * @copyright Copyright (C) 2005 - 2013 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. /**
  11. * Controller tailored to suit most form-based admin operations.
  12. *
  13. * @package Joomla.Legacy
  14. * @subpackage Controller
  15. * @since 12.2
  16. * @deprecated 13.3
  17. * @todo Add ability to set redirect manually to better cope with frontend usage.
  18. */
  19. class JControllerForm extends JControllerLegacy
  20. {
  21. /**
  22. * The context for storing internal data, e.g. record.
  23. *
  24. * @var string
  25. * @since 12.2
  26. */
  27. protected $context;
  28. /**
  29. * The URL option for the component.
  30. *
  31. * @var string
  32. * @since 12.2
  33. */
  34. protected $option;
  35. /**
  36. * The URL view item variable.
  37. *
  38. * @var string
  39. * @since 12.2
  40. */
  41. protected $view_item;
  42. /**
  43. * The URL view list variable.
  44. *
  45. * @var string
  46. * @since 12.2
  47. */
  48. protected $view_list;
  49. /**
  50. * The prefix to use with controller messages.
  51. *
  52. * @var string
  53. * @since 12.2
  54. */
  55. protected $text_prefix;
  56. /**
  57. * Constructor.
  58. *
  59. * @param array $config An optional associative array of configuration settings.
  60. *
  61. * @see JControllerLegacy
  62. * @since 12.2
  63. * @throws Exception
  64. */
  65. public function __construct($config = array())
  66. {
  67. parent::__construct($config);
  68. // Guess the option as com_NameOfController
  69. if (empty($this->option))
  70. {
  71. $this->option = 'com_' . strtolower($this->getName());
  72. }
  73. // Guess the JText message prefix. Defaults to the option.
  74. if (empty($this->text_prefix))
  75. {
  76. $this->text_prefix = strtoupper($this->option);
  77. }
  78. // Guess the context as the suffix, eg: OptionControllerContent.
  79. if (empty($this->context))
  80. {
  81. $r = null;
  82. if (!preg_match('/(.*)Controller(.*)/i', get_class($this), $r))
  83. {
  84. throw new Exception(JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
  85. }
  86. $this->context = strtolower($r[2]);
  87. }
  88. // Guess the item view as the context.
  89. if (empty($this->view_item))
  90. {
  91. $this->view_item = $this->context;
  92. }
  93. // Guess the list view as the plural of the item view.
  94. if (empty($this->view_list))
  95. {
  96. // @TODO Probably worth moving to an inflector class based on
  97. // http://kuwamoto.org/2007/12/17/improved-pluralizing-in-php-actionscript-and-ror/
  98. // Simple pluralisation based on public domain snippet by Paul Osman
  99. // For more complex types, just manually set the variable in your class.
  100. $plural = array(
  101. array('/(x|ch|ss|sh)$/i', "$1es"),
  102. array('/([^aeiouy]|qu)y$/i', "$1ies"),
  103. array('/([^aeiouy]|qu)ies$/i', "$1y"),
  104. array('/(bu)s$/i', "$1ses"),
  105. array('/s$/i', "s"),
  106. array('/$/', "s"));
  107. // Check for matches using regular expressions
  108. foreach ($plural as $pattern)
  109. {
  110. if (preg_match($pattern[0], $this->view_item))
  111. {
  112. $this->view_list = preg_replace($pattern[0], $pattern[1], $this->view_item);
  113. break;
  114. }
  115. }
  116. }
  117. // Apply, Save & New, and Save As copy should be standard on forms.
  118. $this->registerTask('apply', 'save');
  119. $this->registerTask('save2new', 'save');
  120. $this->registerTask('save2copy', 'save');
  121. }
  122. /**
  123. * Method to add a new record.
  124. *
  125. * @return mixed True if the record can be added, a error object if not.
  126. *
  127. * @since 12.2
  128. */
  129. public function add()
  130. {
  131. $app = JFactory::getApplication();
  132. $context = "$this->option.edit.$this->context";
  133. // Access check.
  134. if (!$this->allowAdd())
  135. {
  136. // Set the internal error and also the redirect error.
  137. $this->setError(JText::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'));
  138. $this->setMessage($this->getError(), 'error');
  139. $this->setRedirect(
  140. JRoute::_(
  141. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  142. . $this->getRedirectToListAppend(), false
  143. )
  144. );
  145. return false;
  146. }
  147. // Clear the record edit information from the session.
  148. $app->setUserState($context . '.data', null);
  149. // Redirect to the edit screen.
  150. $this->setRedirect(
  151. JRoute::_(
  152. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  153. . $this->getRedirectToItemAppend(), false
  154. )
  155. );
  156. return true;
  157. }
  158. /**
  159. * Method to check if you can add a new record.
  160. *
  161. * Extended classes can override this if necessary.
  162. *
  163. * @param array $data An array of input data.
  164. *
  165. * @return boolean
  166. *
  167. * @since 12.2
  168. */
  169. protected function allowAdd($data = array())
  170. {
  171. $user = JFactory::getUser();
  172. return ($user->authorise('core.create', $this->option) || count($user->getAuthorisedCategories($this->option, 'core.create')));
  173. }
  174. /**
  175. * Method to check if you can add a new record.
  176. *
  177. * Extended classes can override this if necessary.
  178. *
  179. * @param array $data An array of input data.
  180. * @param string $key The name of the key for the primary key; default is id.
  181. *
  182. * @return boolean
  183. *
  184. * @since 12.2
  185. */
  186. protected function allowEdit($data = array(), $key = 'id')
  187. {
  188. return JFactory::getUser()->authorise('core.edit', $this->option);
  189. }
  190. /**
  191. * Method to check if you can save a new or existing record.
  192. *
  193. * Extended classes can override this if necessary.
  194. *
  195. * @param array $data An array of input data.
  196. * @param string $key The name of the key for the primary key.
  197. *
  198. * @return boolean
  199. *
  200. * @since 12.2
  201. */
  202. protected function allowSave($data, $key = 'id')
  203. {
  204. $recordId = isset($data[$key]) ? $data[$key] : '0';
  205. if ($recordId)
  206. {
  207. return $this->allowEdit($data, $key);
  208. }
  209. else
  210. {
  211. return $this->allowAdd($data);
  212. }
  213. }
  214. /**
  215. * Method to run batch operations.
  216. *
  217. * @param JModelLegacy $model The model of the component being processed.
  218. *
  219. * @return boolean True if successful, false otherwise and internal error is set.
  220. *
  221. * @since 12.2
  222. */
  223. public function batch($model)
  224. {
  225. $vars = $this->input->post->get('batch', array(), 'array');
  226. $cid = $this->input->post->get('cid', array(), 'array');
  227. // Build an array of item contexts to check
  228. $contexts = array();
  229. foreach ($cid as $id)
  230. {
  231. // If we're coming from com_categories, we need to use extension vs. option
  232. if (isset($this->extension))
  233. {
  234. $option = $this->extension;
  235. }
  236. else
  237. {
  238. $option = $this->option;
  239. }
  240. $contexts[$id] = $option . '.' . $this->context . '.' . $id;
  241. }
  242. // Attempt to run the batch operation.
  243. if ($model->batch($vars, $cid, $contexts))
  244. {
  245. $this->setMessage(JText::_('JLIB_APPLICATION_SUCCESS_BATCH'));
  246. return true;
  247. }
  248. else
  249. {
  250. $this->setMessage(JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_FAILED', $model->getError()));
  251. return false;
  252. }
  253. }
  254. /**
  255. * Method to cancel an edit.
  256. *
  257. * @param string $key The name of the primary key of the URL variable.
  258. *
  259. * @return boolean True if access level checks pass, false otherwise.
  260. *
  261. * @since 12.2
  262. */
  263. public function cancel($key = null)
  264. {
  265. JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
  266. $app = JFactory::getApplication();
  267. $model = $this->getModel();
  268. $table = $model->getTable();
  269. $checkin = property_exists($table, 'checked_out');
  270. $context = "$this->option.edit.$this->context";
  271. if (empty($key))
  272. {
  273. $key = $table->getKeyName();
  274. }
  275. $recordId = $app->input->getInt($key);
  276. // Attempt to check-in the current record.
  277. if ($recordId)
  278. {
  279. // Check we are holding the id in the edit list.
  280. if (!$this->checkEditId($context, $recordId))
  281. {
  282. // Somehow the person just went to the form - we don't allow that.
  283. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId));
  284. $this->setMessage($this->getError(), 'error');
  285. $this->setRedirect(
  286. JRoute::_(
  287. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  288. . $this->getRedirectToListAppend(), false
  289. )
  290. );
  291. return false;
  292. }
  293. if ($checkin)
  294. {
  295. if ($model->checkin($recordId) === false)
  296. {
  297. // Check-in failed, go back to the record and display a notice.
  298. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
  299. $this->setMessage($this->getError(), 'error');
  300. $this->setRedirect(
  301. JRoute::_(
  302. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  303. . $this->getRedirectToItemAppend($recordId, $key), false
  304. )
  305. );
  306. return false;
  307. }
  308. }
  309. }
  310. // Clean the session data and redirect.
  311. $this->releaseEditId($context, $recordId);
  312. $app->setUserState($context . '.data', null);
  313. $this->setRedirect(
  314. JRoute::_(
  315. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  316. . $this->getRedirectToListAppend(), false
  317. )
  318. );
  319. return true;
  320. }
  321. /**
  322. * Method to edit an existing record.
  323. *
  324. * @param string $key The name of the primary key of the URL variable.
  325. * @param string $urlVar The name of the URL variable if different from the primary key
  326. * (sometimes required to avoid router collisions).
  327. *
  328. * @return boolean True if access level check and checkout passes, false otherwise.
  329. *
  330. * @since 12.2
  331. */
  332. public function edit($key = null, $urlVar = null)
  333. {
  334. $app = JFactory::getApplication();
  335. $model = $this->getModel();
  336. $table = $model->getTable();
  337. $cid = $this->input->post->get('cid', array(), 'array');
  338. $context = "$this->option.edit.$this->context";
  339. // Determine the name of the primary key for the data.
  340. if (empty($key))
  341. {
  342. $key = $table->getKeyName();
  343. }
  344. // To avoid data collisions the urlVar may be different from the primary key.
  345. if (empty($urlVar))
  346. {
  347. $urlVar = $key;
  348. }
  349. // Get the previous record id (if any) and the current record id.
  350. $recordId = (int) (count($cid) ? $cid[0] : $this->input->getInt($urlVar));
  351. $checkin = property_exists($table, 'checked_out');
  352. // Access check.
  353. if (!$this->allowEdit(array($key => $recordId), $key))
  354. {
  355. $this->setError(JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
  356. $this->setMessage($this->getError(), 'error');
  357. $this->setRedirect(
  358. JRoute::_(
  359. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  360. . $this->getRedirectToListAppend(), false
  361. )
  362. );
  363. return false;
  364. }
  365. // Attempt to check-out the new record for editing and redirect.
  366. if ($checkin && !$model->checkout($recordId))
  367. {
  368. // Check-out failed, display a notice but allow the user to see the record.
  369. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKOUT_FAILED', $model->getError()));
  370. $this->setMessage($this->getError(), 'error');
  371. $this->setRedirect(
  372. JRoute::_(
  373. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  374. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  375. )
  376. );
  377. return false;
  378. }
  379. else
  380. {
  381. // Check-out succeeded, push the new record id into the session.
  382. $this->holdEditId($context, $recordId);
  383. $app->setUserState($context . '.data', null);
  384. $this->setRedirect(
  385. JRoute::_(
  386. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  387. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  388. )
  389. );
  390. return true;
  391. }
  392. }
  393. /**
  394. * Method to get a model object, loading it if required.
  395. *
  396. * @param string $name The model name. Optional.
  397. * @param string $prefix The class prefix. Optional.
  398. * @param array $config Configuration array for model. Optional.
  399. *
  400. * @return object The model.
  401. *
  402. * @since 12.2
  403. */
  404. public function getModel($name = '', $prefix = '', $config = array('ignore_request' => true))
  405. {
  406. if (empty($name))
  407. {
  408. $name = $this->context;
  409. }
  410. return parent::getModel($name, $prefix, $config);
  411. }
  412. /**
  413. * Gets the URL arguments to append to an item redirect.
  414. *
  415. * @param integer $recordId The primary key id for the item.
  416. * @param string $urlVar The name of the URL variable for the id.
  417. *
  418. * @return string The arguments to append to the redirect URL.
  419. *
  420. * @since 12.2
  421. */
  422. protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
  423. {
  424. $tmpl = $this->input->get('tmpl');
  425. $layout = $this->input->get('layout', 'edit');
  426. $append = '';
  427. // Setup redirect info.
  428. if ($tmpl)
  429. {
  430. $append .= '&tmpl=' . $tmpl;
  431. }
  432. if ($layout)
  433. {
  434. $append .= '&layout=' . $layout;
  435. }
  436. if ($recordId)
  437. {
  438. $append .= '&' . $urlVar . '=' . $recordId;
  439. }
  440. return $append;
  441. }
  442. /**
  443. * Gets the URL arguments to append to a list redirect.
  444. *
  445. * @return string The arguments to append to the redirect URL.
  446. *
  447. * @since 12.2
  448. */
  449. protected function getRedirectToListAppend()
  450. {
  451. $tmpl = JFactory::getApplication()->input->get('tmpl');
  452. $append = '';
  453. // Setup redirect info.
  454. if ($tmpl)
  455. {
  456. $append .= '&tmpl=' . $tmpl;
  457. }
  458. return $append;
  459. }
  460. /**
  461. * Function that allows child controller access to model data
  462. * after the data has been saved.
  463. *
  464. * @param JModelLegacy $model The data model object.
  465. * @param array $validData The validated data.
  466. *
  467. * @return void
  468. *
  469. * @since 12.2
  470. */
  471. protected function postSaveHook(JModelLegacy $model, $validData = array())
  472. {
  473. }
  474. /**
  475. * Method to save a record.
  476. *
  477. * @param string $key The name of the primary key of the URL variable.
  478. * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
  479. *
  480. * @return boolean True if successful, false otherwise.
  481. *
  482. * @since 12.2
  483. */
  484. public function save($key = null, $urlVar = null)
  485. {
  486. // Check for request forgeries.
  487. JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
  488. $app = JFactory::getApplication();
  489. $lang = JFactory::getLanguage();
  490. $model = $this->getModel();
  491. $table = $model->getTable();
  492. $data = $this->input->post->get('jform', array(), 'array');
  493. $checkin = property_exists($table, 'checked_out');
  494. $context = "$this->option.edit.$this->context";
  495. $task = $this->getTask();
  496. // Determine the name of the primary key for the data.
  497. if (empty($key))
  498. {
  499. $key = $table->getKeyName();
  500. }
  501. // To avoid data collisions the urlVar may be different from the primary key.
  502. if (empty($urlVar))
  503. {
  504. $urlVar = $key;
  505. }
  506. $recordId = $this->input->getInt($urlVar);
  507. if (!$this->checkEditId($context, $recordId))
  508. {
  509. // Somehow the person just went to the form and tried to save it. We don't allow that.
  510. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId));
  511. $this->setMessage($this->getError(), 'error');
  512. $this->setRedirect(
  513. JRoute::_(
  514. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  515. . $this->getRedirectToListAppend(), false
  516. )
  517. );
  518. return false;
  519. }
  520. // Populate the row id from the session.
  521. $data[$key] = $recordId;
  522. // The save2copy task needs to be handled slightly differently.
  523. if ($task == 'save2copy')
  524. {
  525. // Check-in the original row.
  526. if ($checkin && $model->checkin($data[$key]) === false)
  527. {
  528. // Check-in failed. Go back to the item and display a notice.
  529. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
  530. $this->setMessage($this->getError(), 'error');
  531. $this->setRedirect(
  532. JRoute::_(
  533. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  534. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  535. )
  536. );
  537. return false;
  538. }
  539. // Reset the ID and then treat the request as for Apply.
  540. $data[$key] = 0;
  541. $task = 'apply';
  542. }
  543. // Access check.
  544. if (!$this->allowSave($data, $key))
  545. {
  546. $this->setError(JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
  547. $this->setMessage($this->getError(), 'error');
  548. $this->setRedirect(
  549. JRoute::_(
  550. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  551. . $this->getRedirectToListAppend(), false
  552. )
  553. );
  554. return false;
  555. }
  556. // Validate the posted data.
  557. // Sometimes the form needs some posted data, such as for plugins and modules.
  558. $form = $model->getForm($data, false);
  559. if (!$form)
  560. {
  561. $app->enqueueMessage($model->getError(), 'error');
  562. return false;
  563. }
  564. // Test whether the data is valid.
  565. $validData = $model->validate($form, $data);
  566. // Check for validation errors.
  567. if ($validData === false)
  568. {
  569. // Get the validation messages.
  570. $errors = $model->getErrors();
  571. // Push up to three validation messages out to the user.
  572. for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
  573. {
  574. if ($errors[$i] instanceof Exception)
  575. {
  576. $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
  577. }
  578. else
  579. {
  580. $app->enqueueMessage($errors[$i], 'warning');
  581. }
  582. }
  583. // Save the data in the session.
  584. $app->setUserState($context . '.data', $data);
  585. // Redirect back to the edit screen.
  586. $this->setRedirect(
  587. JRoute::_(
  588. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  589. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  590. )
  591. );
  592. return false;
  593. }
  594. // Attempt to save the data.
  595. if (!$model->save($validData))
  596. {
  597. // Save the data in the session.
  598. $app->setUserState($context . '.data', $validData);
  599. // Redirect back to the edit screen.
  600. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
  601. $this->setMessage($this->getError(), 'error');
  602. $this->setRedirect(
  603. JRoute::_(
  604. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  605. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  606. )
  607. );
  608. return false;
  609. }
  610. // Save succeeded, so check-in the record.
  611. if ($checkin && $model->checkin($validData[$key]) === false)
  612. {
  613. // Save the data in the session.
  614. $app->setUserState($context . '.data', $validData);
  615. // Check-in failed, so go back to the record and display a notice.
  616. $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
  617. $this->setMessage($this->getError(), 'error');
  618. $this->setRedirect(
  619. JRoute::_(
  620. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  621. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  622. )
  623. );
  624. return false;
  625. }
  626. $this->setMessage(
  627. JText::_(
  628. ($lang->hasKey($this->text_prefix . ($recordId == 0 && $app->isSite() ? '_SUBMIT' : '') . '_SAVE_SUCCESS')
  629. ? $this->text_prefix
  630. : 'JLIB_APPLICATION') . ($recordId == 0 && $app->isSite() ? '_SUBMIT' : '') . '_SAVE_SUCCESS'
  631. )
  632. );
  633. // Redirect the user and adjust session state based on the chosen task.
  634. switch ($task)
  635. {
  636. case 'apply':
  637. // Set the record data in the session.
  638. $recordId = $model->getState($this->context . '.id');
  639. $this->holdEditId($context, $recordId);
  640. $app->setUserState($context . '.data', null);
  641. $model->checkout($recordId);
  642. // Redirect back to the edit screen.
  643. $this->setRedirect(
  644. JRoute::_(
  645. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  646. . $this->getRedirectToItemAppend($recordId, $urlVar), false
  647. )
  648. );
  649. break;
  650. case 'save2new':
  651. // Clear the record id and data from the session.
  652. $this->releaseEditId($context, $recordId);
  653. $app->setUserState($context . '.data', null);
  654. // Redirect back to the edit screen.
  655. $this->setRedirect(
  656. JRoute::_(
  657. 'index.php?option=' . $this->option . '&view=' . $this->view_item
  658. . $this->getRedirectToItemAppend(null, $urlVar), false
  659. )
  660. );
  661. break;
  662. default:
  663. // Clear the record id and data from the session.
  664. $this->releaseEditId($context, $recordId);
  665. $app->setUserState($context . '.data', null);
  666. // Redirect to the list screen.
  667. $this->setRedirect(
  668. JRoute::_(
  669. 'index.php?option=' . $this->option . '&view=' . $this->view_list
  670. . $this->getRedirectToListAppend(), false
  671. )
  672. );
  673. break;
  674. }
  675. // Invoke the postSave method to allow for the child class to access the model.
  676. $this->postSaveHook($model, $validData);
  677. return true;
  678. }
  679. }