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