1 <?php
2 /**
3 * @package Joomla.Legacy
4 * @subpackage Model
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\Utilities\ArrayHelper;
13
14 /**
15 * Prototype form model.
16 *
17 * @see JForm
18 * @see JFormField
19 * @see JFormRule
20 * @since 1.6
21 */
22 abstract class JModelForm extends JModelLegacy
23 {
24 /**
25 * Array of form objects.
26 *
27 * @var JForm[]
28 * @since 1.6
29 */
30 protected $_forms = array();
31
32 /**
33 * Maps events to plugin groups.
34 *
35 * @var array
36 * @since 3.6
37 */
38 protected $events_map = null;
39
40 /**
41 * Constructor.
42 *
43 * @param array $config An optional associative array of configuration settings.
44 *
45 * @see JModelLegacy
46 * @since 3.6
47 */
48 public function __construct($config = array())
49 {
50 $config['events_map'] = isset($config['events_map']) ? $config['events_map'] : array();
51
52 $this->events_map = array_merge(
53 array(
54 'validate' => 'content',
55 ),
56 $config['events_map']
57 );
58
59 parent::__construct($config);
60 }
61
62 /**
63 * Method to checkin a row.
64 *
65 * @param integer $pk The numeric id of the primary key.
66 *
67 * @return boolean False on failure or error, true otherwise.
68 *
69 * @since 1.6
70 */
71 public function checkin($pk = null)
72 {
73 // Only attempt to check the row in if it exists.
74 if ($pk)
75 {
76 $user = JFactory::getUser();
77
78 // Get an instance of the row to checkin.
79 $table = $this->getTable();
80
81 if (!$table->load($pk))
82 {
83 $this->setError($table->getError());
84
85 return false;
86 }
87
88 $checkedOutField = $table->getColumnAlias('checked_out');
89 $checkedOutTimeField = $table->getColumnAlias('checked_out_time');
90
91 // If there is no checked_out or checked_out_time field, just return true.
92 if (!property_exists($table, $checkedOutField) || !property_exists($table, $checkedOutTimeField))
93 {
94 return true;
95 }
96
97 // Check if this is the user having previously checked out the row.
98 if ($table->{$checkedOutField} > 0 && $table->{$checkedOutField} != $user->get('id') && !$user->authorise('core.admin', 'com_checkin'))
99 {
100 $this->setError(JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'));
101
102 return false;
103 }
104
105 // Attempt to check the row in.
106 if (!$table->checkIn($pk))
107 {
108 $this->setError($table->getError());
109
110 return false;
111 }
112 }
113
114 return true;
115 }
116
117 /**
118 * Method to check-out a row for editing.
119 *
120 * @param integer $pk The numeric id of the primary key.
121 *
122 * @return boolean False on failure or error, true otherwise.
123 *
124 * @since 1.6
125 */
126 public function checkout($pk = null)
127 {
128 // Only attempt to check the row in if it exists.
129 if ($pk)
130 {
131 // Get an instance of the row to checkout.
132 $table = $this->getTable();
133
134 if (!$table->load($pk))
135 {
136 $this->setError($table->getError());
137
138 return false;
139 }
140
141 $checkedOutField = $table->getColumnAlias('checked_out');
142 $checkedOutTimeField = $table->getColumnAlias('checked_out_time');
143
144 // If there is no checked_out or checked_out_time field, just return true.
145 if (!property_exists($table, $checkedOutField) || !property_exists($table, $checkedOutTimeField))
146 {
147 return true;
148 }
149
150 $user = JFactory::getUser();
151
152 // Check if this is the user having previously checked out the row.
153 if ($table->{$checkedOutField} > 0 && $table->{$checkedOutField} != $user->get('id'))
154 {
155 $this->setError(JText::_('JLIB_APPLICATION_ERROR_CHECKOUT_USER_MISMATCH'));
156
157 return false;
158 }
159
160 // Attempt to check the row out.
161 if (!$table->checkOut($user->get('id'), $pk))
162 {
163 $this->setError($table->getError());
164
165 return false;
166 }
167 }
168
169 return true;
170 }
171
172 /**
173 * Abstract method for getting the form from the model.
174 *
175 * @param array $data Data for the form.
176 * @param boolean $loadData True if the form is to load its own data (default case), false if not.
177 *
178 * @return JForm|boolean A JForm object on success, false on failure
179 *
180 * @since 1.6
181 */
182 abstract public function getForm($data = array(), $loadData = true);
183
184 /**
185 * Method to get a form object.
186 *
187 * @param string $name The name of the form.
188 * @param string $source The form source. Can be XML string if file flag is set to false.
189 * @param array $options Optional array of options for the form creation.
190 * @param boolean $clear Optional argument to force load a new form.
191 * @param string $xpath An optional xpath to search for the fields.
192 *
193 * @return JForm|boolean JForm object on success, false on error.
194 *
195 * @see JForm
196 * @since 1.6
197 */
198 protected function loadForm($name, $source = null, $options = array(), $clear = false, $xpath = false)
199 {
200 // Handle the optional arguments.
201 $options['control'] = ArrayHelper::getValue((array) $options, 'control', false);
202
203 // Create a signature hash.
204 $hash = md5($source . serialize($options));
205
206 // Check if we can use a previously loaded form.
207 if (isset($this->_forms[$hash]) && !$clear)
208 {
209 return $this->_forms[$hash];
210 }
211
212 // Get the form.
213 JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
214 JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
215 JForm::addFormPath(JPATH_COMPONENT . '/model/form');
216 JForm::addFieldPath(JPATH_COMPONENT . '/model/field');
217
218 try
219 {
220 $form = JForm::getInstance($name, $source, $options, false, $xpath);
221
222 if (isset($options['load_data']) && $options['load_data'])
223 {
224 // Get the data for the form.
225 $data = $this->loadFormData();
226 }
227 else
228 {
229 $data = array();
230 }
231
232 // Allow for additional modification of the form, and events to be triggered.
233 // We pass the data because plugins may require it.
234 $this->preprocessForm($form, $data);
235
236 // Load the data into the form after the plugins have operated.
237 $form->bind($data);
238 }
239 catch (Exception $e)
240 {
241 $this->setError($e->getMessage());
242
243 return false;
244 }
245
246 // Store the form for later.
247 $this->_forms[$hash] = $form;
248
249 return $form;
250 }
251
252 /**
253 * Method to get the data that should be injected in the form.
254 *
255 * @return array The default data is an empty array.
256 *
257 * @since 1.6
258 */
259 protected function loadFormData()
260 {
261 return array();
262 }
263
264 /**
265 * Method to allow derived classes to preprocess the data.
266 *
267 * @param string $context The context identifier.
268 * @param mixed &$data The data to be processed. It gets altered directly.
269 * @param string $group The name of the plugin group to import (defaults to "content").
270 *
271 * @return void
272 *
273 * @since 3.1
274 */
275 protected function preprocessData($context, &$data, $group = 'content')
276 {
277 // Get the dispatcher and load the users plugins.
278 $dispatcher = JEventDispatcher::getInstance();
279 JPluginHelper::importPlugin($group);
280
281 // Trigger the data preparation event.
282 $results = $dispatcher->trigger('onContentPrepareData', array($context, &$data));
283
284 // Check for errors encountered while preparing the data.
285 if (count($results) > 0 && in_array(false, $results, true))
286 {
287 $this->setError($dispatcher->getError());
288 }
289 }
290
291 /**
292 * Method to allow derived classes to preprocess the form.
293 *
294 * @param JForm $form A JForm object.
295 * @param mixed $data The data expected for the form.
296 * @param string $group The name of the plugin group to import (defaults to "content").
297 *
298 * @return void
299 *
300 * @see JFormField
301 * @since 1.6
302 * @throws Exception if there is an error in the form event.
303 */
304 protected function preprocessForm(JForm $form, $data, $group = 'content')
305 {
306 // Import the appropriate plugin group.
307 JPluginHelper::importPlugin($group);
308
309 // Get the dispatcher.
310 $dispatcher = JEventDispatcher::getInstance();
311
312 // Trigger the form preparation event.
313 $results = $dispatcher->trigger('onContentPrepareForm', array($form, $data));
314
315 // Check for errors encountered while preparing the form.
316 if (count($results) && in_array(false, $results, true))
317 {
318 // Get the last error.
319 $error = $dispatcher->getError();
320
321 if (!($error instanceof Exception))
322 {
323 throw new Exception($error);
324 }
325 }
326 }
327
328 /**
329 * Method to validate the form data.
330 *
331 * @param JForm $form The form to validate against.
332 * @param array $data The data to validate.
333 * @param string $group The name of the field group to validate.
334 *
335 * @return array|boolean Array of filtered data if valid, false otherwise.
336 *
337 * @see JFormRule
338 * @see JFilterInput
339 * @since 1.6
340 */
341 public function validate($form, $data, $group = null)
342 {
343 // Include the plugins for the delete events.
344 JPluginHelper::importPlugin($this->events_map['validate']);
345
346 $dispatcher = JEventDispatcher::getInstance();
347 $dispatcher->trigger('onUserBeforeDataValidation', array($form, &$data));
348
349 // Filter and validate the form data.
350 $data = $form->filter($data);
351 $return = $form->validate($data, $group);
352
353 // Check for an error.
354 if ($return instanceof Exception)
355 {
356 $this->setError($return->getMessage());
357
358 return false;
359 }
360
361 // Check the validation results.
362 if ($return === false)
363 {
364 // Get the validation messages from the form.
365 foreach ($form->getErrors() as $message)
366 {
367 $this->setError($message);
368 }
369
370 return false;
371 }
372
373 // Tags B/C break at 3.1.2
374 if (isset($data['metadata']['tags']) && !isset($data['tags']))
375 {
376 $data['tags'] = $data['metadata']['tags'];
377 }
378
379 return $data;
380 }
381 }
382