1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Form
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
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\String\StringHelper;
13
14 jimport('joomla.filesystem.path');
15
16 /**
17 * JForm's helper class.
18 * Provides a storage for filesystem's paths where JForm's entities reside and methods for creating those entities.
19 * Also stores objects with entities' prototypes for further reusing.
20 *
21 * @since 11.1
22 */
23 class JFormHelper
24 {
25 /**
26 * Array with paths where entities(field, rule, form) can be found.
27 *
28 * Array's structure:
29 *
30 * paths:
31 * {ENTITY_NAME}:
32 * - /path/1
33 * - /path/2
34 *
35 * @var array
36 * @since 11.1
37 */
38 protected static $paths;
39
40 /**
41 * Static array of JForm's entity objects for re-use.
42 * Prototypes for all fields and rules are here.
43 *
44 * Array's structure:
45 * entities:
46 * {ENTITY_NAME}:
47 * {KEY}: {OBJECT}
48 *
49 * @var array
50 * @since 11.1
51 */
52 protected static $entities = array();
53
54 /**
55 * Method to load a form field object given a type.
56 *
57 * @param string $type The field type.
58 * @param boolean $new Flag to toggle whether we should get a new instance of the object.
59 *
60 * @return JFormField|boolean JFormField object on success, false otherwise.
61 *
62 * @since 11.1
63 */
64 public static function loadFieldType($type, $new = true)
65 {
66 return self::loadType('field', $type, $new);
67 }
68
69 /**
70 * Method to load a form rule object given a type.
71 *
72 * @param string $type The rule type.
73 * @param boolean $new Flag to toggle whether we should get a new instance of the object.
74 *
75 * @return JFormRule|boolean JFormRule object on success, false otherwise.
76 *
77 * @since 11.1
78 */
79 public static function loadRuleType($type, $new = true)
80 {
81 return self::loadType('rule', $type, $new);
82 }
83
84 /**
85 * Method to load a form entity object given a type.
86 * Each type is loaded only once and then used as a prototype for other objects of same type.
87 * Please, use this method only with those entities which support types (forms don't support them).
88 *
89 * @param string $entity The entity.
90 * @param string $type The entity type.
91 * @param boolean $new Flag to toggle whether we should get a new instance of the object.
92 *
93 * @return mixed Entity object on success, false otherwise.
94 *
95 * @since 11.1
96 */
97 protected static function loadType($entity, $type, $new = true)
98 {
99 // Reference to an array with current entity's type instances
100 $types = &self::$entities[$entity];
101
102 $key = md5($type);
103
104 // Return an entity object if it already exists and we don't need a new one.
105 if (isset($types[$key]) && $new === false)
106 {
107 return $types[$key];
108 }
109
110 $class = self::loadClass($entity, $type);
111
112 if ($class === false)
113 {
114 return false;
115 }
116
117 // Instantiate a new type object.
118 $types[$key] = new $class;
119
120 return $types[$key];
121 }
122
123 /**
124 * Attempt to import the JFormField class file if it isn't already imported.
125 * You can use this method outside of JForm for loading a field for inheritance or composition.
126 *
127 * @param string $type Type of a field whose class should be loaded.
128 *
129 * @return string|boolean Class name on success or false otherwise.
130 *
131 * @since 11.1
132 */
133 public static function loadFieldClass($type)
134 {
135 return self::loadClass('field', $type);
136 }
137
138 /**
139 * Attempt to import the JFormRule class file if it isn't already imported.
140 * You can use this method outside of JForm for loading a rule for inheritance or composition.
141 *
142 * @param string $type Type of a rule whose class should be loaded.
143 *
144 * @return string|boolean Class name on success or false otherwise.
145 *
146 * @since 11.1
147 */
148 public static function loadRuleClass($type)
149 {
150 return self::loadClass('rule', $type);
151 }
152
153 /**
154 * Load a class for one of the form's entities of a particular type.
155 * Currently, it makes sense to use this method for the "field" and "rule" entities
156 * (but you can support more entities in your subclass).
157 *
158 * @param string $entity One of the form entities (field or rule).
159 * @param string $type Type of an entity.
160 *
161 * @return string|boolean Class name on success or false otherwise.
162 *
163 * @since 11.1
164 */
165 protected static function loadClass($entity, $type)
166 {
167 $prefix = 'J';
168
169 if (strpos($type, '.'))
170 {
171 list($prefix, $type) = explode('.', $type);
172 }
173
174 $class = StringHelper::ucfirst($prefix, '_') . 'Form' . StringHelper::ucfirst($entity, '_') . StringHelper::ucfirst($type, '_');
175
176 if (class_exists($class))
177 {
178 return $class;
179 }
180
181 // Get the field search path array.
182 $paths = self::addPath($entity);
183
184 // If the type is complex, add the base type to the paths.
185 if ($pos = strpos($type, '_'))
186 {
187 // Add the complex type prefix to the paths.
188 for ($i = 0, $n = count($paths); $i < $n; $i++)
189 {
190 // Derive the new path.
191 $path = $paths[$i] . '/' . strtolower(substr($type, 0, $pos));
192
193 // If the path does not exist, add it.
194 if (!in_array($path, $paths))
195 {
196 $paths[] = $path;
197 }
198 }
199 // Break off the end of the complex type.
200 $type = substr($type, $pos + 1);
201 }
202
203 // Try to find the class file.
204 $type = strtolower($type) . '.php';
205
206 foreach ($paths as $path)
207 {
208 $file = JPath::find($path, $type);
209
210 if (!$file)
211 {
212 continue;
213 }
214
215 require_once $file;
216
217 if (class_exists($class))
218 {
219 break;
220 }
221 }
222
223 // Check for all if the class exists.
224 return class_exists($class) ? $class : false;
225 }
226
227 /**
228 * Method to add a path to the list of field include paths.
229 *
230 * @param mixed $new A path or array of paths to add.
231 *
232 * @return array The list of paths that have been added.
233 *
234 * @since 11.1
235 */
236 public static function addFieldPath($new = null)
237 {
238 return self::addPath('field', $new);
239 }
240
241 /**
242 * Method to add a path to the list of form include paths.
243 *
244 * @param mixed $new A path or array of paths to add.
245 *
246 * @return array The list of paths that have been added.
247 *
248 * @since 11.1
249 */
250 public static function addFormPath($new = null)
251 {
252 return self::addPath('form', $new);
253 }
254
255 /**
256 * Method to add a path to the list of rule include paths.
257 *
258 * @param mixed $new A path or array of paths to add.
259 *
260 * @return array The list of paths that have been added.
261 *
262 * @since 11.1
263 */
264 public static function addRulePath($new = null)
265 {
266 return self::addPath('rule', $new);
267 }
268
269 /**
270 * Method to add a path to the list of include paths for one of the form's entities.
271 * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
272 *
273 * @param string $entity Form's entity name for which paths will be added.
274 * @param mixed $new A path or array of paths to add.
275 *
276 * @return array The list of paths that have been added.
277 *
278 * @since 11.1
279 */
280 protected static function addPath($entity, $new = null)
281 {
282 // Reference to an array with paths for current entity
283 $paths = &self::$paths[$entity];
284
285 // Add the default entity's search path if not set.
286 if (empty($paths))
287 {
288 // While we support limited number of entities (form, field and rule)
289 // we can do this simple pluralisation:
290 $entity_plural = $entity . 's';
291
292 /*
293 * But when someday we would want to support more entities, then we should consider adding
294 * an inflector class to "libraries/joomla/utilities" and use it here (or somebody can use a real inflector in his subclass).
295 * See also: pluralization snippet by Paul Osman in JControllerForm's constructor.
296 */
297 $paths[] = __DIR__ . '/' . $entity_plural;
298 }
299
300 // Force the new path(s) to an array.
301 settype($new, 'array');
302
303 // Add the new paths to the stack if not already there.
304 foreach ($new as $path)
305 {
306 if (!in_array($path, $paths))
307 {
308 array_unshift($paths, trim($path));
309 }
310
311 if (!is_dir($path))
312 {
313 array_unshift($paths, trim($path));
314 }
315 }
316
317 return $paths;
318 }
319
320 /**
321 * Parse the show on conditions
322 *
323 * @param string $showOn Show on conditions.
324 * @param string $formControl Form name.
325 * @param string $group The dot-separated form group path.
326 *
327 * @return array Array with show on conditions.
328 *
329 * @since 3.7.0
330 */
331 public static function parseShowOnConditions($showOn, $formControl = null, $group = null)
332 {
333 // Process the showon data.
334 if (!$showOn)
335 {
336 return array();
337 }
338
339 $formPath = $formControl ?: '';
340
341 if ($group)
342 {
343 $groups = explode('.', $group);
344
345 // An empty formControl leads to invalid shown property
346 // Use the 1st part of the group instead to avoid.
347 if (empty($formPath) && isset($groups[0]))
348 {
349 $formPath = $groups[0];
350 array_shift($groups);
351 }
352
353 foreach ($groups as $group)
354 {
355 $formPath .= '[' . $group . ']';
356 }
357 }
358
359 $showOnData = array();
360 $showOnParts = preg_split('#(\[AND\]|\[OR\])#', $showOn, -1, PREG_SPLIT_DELIM_CAPTURE);
361 $op = '';
362
363 foreach ($showOnParts as $showOnPart)
364 {
365 if (($showOnPart === '[AND]') || $showOnPart === '[OR]')
366 {
367 $op = trim($showOnPart, '[]');
368 continue;
369 }
370
371 $compareEqual = strpos($showOnPart, '!:') === false;
372 $showOnPartBlocks = explode(($compareEqual ? ':' : '!:'), $showOnPart, 2);
373
374 $showOnData[] = array(
375 'field' => $formPath ? $formPath . '[' . $showOnPartBlocks[0] . ']' : $showOnPartBlocks[0],
376 'values' => explode(',', $showOnPartBlocks[1]),
377 'sign' => $compareEqual === true ? '=' : '!=',
378 'op' => $op,
379 );
380
381 if ($op !== '')
382 {
383 $op = '';
384 }
385 }
386
387 return $showOnData;
388 }
389 }
390