Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 637 lines (562 sloc) 22.663 kb
7f90fa0 Initial commit of Revision 5 code
Alistair Kearney authored
1 <?php
2
854f78a Last lot of code formatting to PSR-2
John Porter authored
3 /**
4 * @package toolkit
5 */
6 /**
7 * The `FieldManager` class is responsible for managing all fields types in Symphony.
8 * Fields are stored on the file system either in the `/fields` folder of `TOOLKIT` or
9 * in a `fields` folder in an extension directory.
10 */
11
12 require_once TOOLKIT . '/class.field.php';
13
14 class FieldManager implements FileResource
15 {
16 /**
17 * An array of all the objects that the Manager is responsible for.
18 * Defaults to an empty array.
19 * @var array
20 */
21 protected static $_pool = array();
22
23 /**
24 * An array of all fields whose have been created by ID
25 * @var array
26 */
27 private static $_initialiased_fields = array();
28
29 /**
30 * Given the filename of a Field, return it's handle. This will remove
31 * the Symphony conventions of `field.*.php`
32 *
33 * @param string $filename
34 * The filename of the Field
35 * @return string
36 */
37 public static function __getHandleFromFilename($filename)
38 {
39 return preg_replace(array('/^field./i', '/.php$/i'), '', $filename);
40 }
41
42 /**
43 * Given a type, returns the full class name of a Field. Fields use a
44 * 'field' prefix
45 *
46 * @param string $type
47 * A field handle
48 * @return string
49 */
50 public static function __getClassName($type)
51 {
52 return 'field' . $type;
53 }
54
55 /**
56 * Finds a Field by type by searching the `TOOLKIT . /fields` folder and then
57 * any fields folders in the installed extensions. The function returns
58 * the path to the folder where the field class resides.
59 *
60 * @param string $type
61 * The field handle, that is, `field.{$handle}.php`
64b19cc Brendan Abbott Additional scrutinizer fixes
brendo authored
62 * @return string|boolean
854f78a Last lot of code formatting to PSR-2
John Porter authored
63 */
64 public static function __getClassPath($type)
65 {
66 if (is_file(TOOLKIT . "/fields/field.{$type}.php")) {
67 return TOOLKIT . '/fields';
68 } else {
69 $extensions = Symphony::ExtensionManager()->listInstalledHandles();
70
71 if (is_array($extensions) && !empty($extensions)) {
72 foreach ($extensions as $e) {
73 if (is_file(EXTENSIONS . "/{$e}/fields/field.{$type}.php")) {
74 return EXTENSIONS . "/{$e}/fields";
75 }
76 }
77 }
78 }
79
80 return false;
81 }
82
83 /**
84 * Given a field type, return the path to it's class
85 *
86 * @see __getClassPath()
87 * @param string $type
88 * The handle of the field to load (it's type)
89 * @return string
90 */
91 public static function __getDriverPath($type)
92 {
93 return self::__getClassPath($type) . "/field.{$type}.php";
94 }
95
96 /**
97 * This function is not implemented by the `FieldManager` class
98 */
99 public static function about($name)
100 {
101 return false;
102 }
103
104 /**
105 * Given an associative array of fields, insert them into the database
106 * returning the resulting Field ID if successful, or false if there
107 * was an error. As fields are saved in order on a section, a query is
108 * made to determine the sort order of this field to be current sort order
109 * +1.
110 *
111 * @param array $fields
112 * Associative array of field names => values for the Field object
113 * @throws DatabaseException
114 * @return integer|boolean
115 * Returns a Field ID of the created Field on success, false otherwise.
116 */
117 public static function add(array $fields)
118 {
119 if (!isset($fields['sortorder'])) {
120 $fields['sortorder'] = self::fetchNextSortOrder();
121 }
122
123 if (!Symphony::Database()->insert($fields, 'tbl_fields')) {
124 return false;
125 }
126
127 $field_id = Symphony::Database()->getInsertID();
128
129 return $field_id;
130 }
131
132 /**
133 * Save the settings for a Field given it's `$field_id` and an associative
134 * array of settings.
135 *
136 * @since Symphony 2.3
137 * @param integer $field_id
138 * The ID of the field
139 * @param array $settings
140 * An associative array of settings, where the key is the column name
141 * and the value is the value.
142 * @throws DatabaseException
143 * @return boolean
144 * True on success, false on failure
145 */
146 public static function saveSettings($field_id, $settings)
147 {
148 // Get the type of this field:
149 $type = self::fetchFieldTypeFromID($field_id);
150
151 // Delete the original settings:
152 Symphony::Database()->delete("`tbl_fields_".$type."`", sprintf("`field_id` = %d LIMIT 1", $field_id));
153
154 // Insert the new settings into the type table:
155 if (!isset($settings['field_id'])) {
156 $settings['field_id'] = $field_id;
157 }
158
159 return Symphony::Database()->insert($settings, 'tbl_fields_'.$type);
160 }
161
162 /**
163 * Given a Field ID and associative array of fields, update an existing Field
164 * row in the `tbl_fields`table. Returns boolean for success/failure
165 *
166 * @param integer $id
167 * The ID of the Field that should be updated
168 * @param array $fields
169 * Associative array of field names => values for the Field object
170 * This array does need to contain every value for the field object, it
171 * can just be the changed values.
172 * @throws DatabaseException
173 * @return boolean
174 */
175 public static function edit($id, array $fields)
176 {
7b74a6e Brendan Abbott Be stricter when constructing queries throughout the core. RE: #2190
brendo authored
177 if (!Symphony::Database()->update($fields, "tbl_fields", sprintf(" `id` = %d", $id))) {
854f78a Last lot of code formatting to PSR-2
John Porter authored
178 return false;
179 }
180
181 return true;
182 }
183
184 /**
185 * Given a Field ID, delete a Field from Symphony. This will remove the field from
186 * the fields table, all of the data stored in this field's `tbl_entries_data_$id` any
187 * existing section associations. This function additionally call the Field's `tearDown`
188 * method so that it can cleanup any additional settings or entry tables it may of created.
189 *
190 * @param integer $id
191 * The ID of the Field that should be deleted
192 * @throws DatabaseException
193 * @throws Exception
194 * @return boolean
195 */
196 public static function delete($id)
197 {
198 $existing = self::fetch($id);
199 $existing->tearDown();
200
7b74a6e Brendan Abbott Be stricter when constructing queries throughout the core. RE: #2190
brendo authored
201 Symphony::Database()->delete('tbl_fields', sprintf(" `id` = %d", $id));
202 Symphony::Database()->delete('tbl_fields_'.$existing->handle(), sprintf(" `field_id` = %d", $id));
854f78a Last lot of code formatting to PSR-2
John Porter authored
203 SectionManager::removeSectionAssociation($id);
204
205 Symphony::Database()->query('DROP TABLE IF EXISTS `tbl_entries_data_'.$id.'`');
206
207 return true;
208 }
209
210 /**
211 * The fetch method returns a instance of a Field from tbl_fields. The most common
212 * use of this function is to retrieve a Field by ID, but it can be used to retrieve
213 * Fields from a Section also. There are several parameters that can be used to fetch
214 * fields by their Type, Location, by a Field Constant or with a custom WHERE query.
215 *
216 * @param integer|array $id
217 * The ID of the field to retrieve. Defaults to null which will return multiple field
218 * objects. Since Symphony 2.3, `$id` will accept an array of Field ID's
219 * @param integer $section_id
220 * The ID of the section to look for the fields in. Defaults to null which will allow
221 * all fields in the Symphony installation to be searched on.
222 * @param string $order
223 * Available values of ASC (Ascending) or DESC (Descending), which refer to the
224 * sort order for the query. Defaults to ASC (Ascending)
225 * @param string $sortfield
226 * The field to sort the query by. Can be any from the tbl_fields schema. Defaults to
227 * 'sortorder'
228 * @param string $type
229 * Filter fields by their type, ie. input, select. Defaults to null
230 * @param string $location
231 * Filter fields by their location in the entry form. There are two possible values,
232 * 'main' or 'sidebar'. Defaults to null
233 * @param string $where
234 * Allows a custom where query to be included. Must be valid SQL. The tbl_fields alias
235 * is t1
236 * @param integer|string $restrict
237 * Only return fields if they match one of the Field Constants. Available values are
238 * `__TOGGLEABLE_ONLY__`, `__UNTOGGLEABLE_ONLY__`, `__FILTERABLE_ONLY__`,
239 * `__UNFILTERABLE_ONLY__` or `__FIELD_ALL__`. Defaults to `__FIELD_ALL__`
240 * @throws DatabaseException
241 * @throws Exception
242 * @return array
243 * An array of Field objects. If no Field are found, null is returned.
244 */
245 public static function fetch($id = null, $section_id = null, $order = 'ASC', $sortfield = 'sortorder', $type = null, $location = null, $where = null, $restrict = Field::__FIELD_ALL__)
246 {
247 $fields = array();
248 $returnSingle = false;
249 $ids = array();
250 $field_contexts = array();
251
252 if (!is_null($id)) {
253 if (is_numeric($id)) {
254 $returnSingle = true;
255 }
256
257 if (!is_array($id)) {
258 $field_ids = array((int)$id);
259 } else {
260 $field_ids = $id;
261 }
262
263 // Loop over the `$field_ids` and check to see we have
264 // instances of the request fields
265 foreach ($field_ids as $key => $field_id) {
266 if (
267 isset(self::$_initialiased_fields[$field_id])
268 && self::$_initialiased_fields[$field_id] instanceof Field
269 ) {
270 $fields[$field_id] = self::$_initialiased_fields[$field_id];
271 unset($field_ids[$key]);
272 }
273 }
274 }
275
276 // If there is any `$field_ids` left to be resolved lets do that, otherwise
277 // if `$id` wasn't provided in the first place, we'll also continue
278 if (!empty($field_ids) || is_null($id)) {
279 $sql = sprintf(
280 "SELECT t1.*
281 FROM tbl_fields AS `t1`
282 WHERE 1
283 %s %s %s %s
284 %s",
285 (isset($type) ? " AND t1.`type` = '{$type}' " : null),
286 (isset($location) ? " AND t1.`location` = '{$location}' " : null),
287 (isset($section_id) ? " AND t1.`parent_section` = '{$section_id}' " : null),
288 $where,
289 isset($field_ids) ? " AND t1.`id` IN(" . implode(',', $field_ids) . ") " : " ORDER BY t1.`{$sortfield}` {$order}"
290 );
291
292 if (!$result = Symphony::Database()->fetch($sql)) {
293 return ($returnSingle ? null : array());
294 }
295
296 // Loop over the resultset building an array of type, field_id
297 foreach ($result as $f) {
298 $ids[$f['type']][] = $f['id'];
299 }
300
301 // Loop over the `ids` array, which is grouped by field type
302 // and get the field context.
303 foreach ($ids as $type => $field_id) {
304 $field_contexts[$type] = Symphony::Database()->fetch(sprintf(
305 "SELECT * FROM `tbl_fields_%s` WHERE `field_id` IN (%s)",
306 $type,
307 implode(',', $field_id)
308 ), 'field_id');
309 }
310
311 foreach ($result as $f) {
312 // We already have this field in our static store
313 if (
314 isset(self::$_initialiased_fields[$f['id']])
315 && self::$_initialiased_fields[$f['id']] instanceof Field
316 ) {
317 $field = self::$_initialiased_fields[$f['id']];
318
319 // We don't have an instance of this field, so let's set one up
320 } else {
321 $field = self::create($f['type']);
322 $field->setArray($f);
323
324 // Get the context for this field from our previous queries.
325 $context = $field_contexts[$f['type']][$f['id']];
326
327 if (is_array($context) && !empty($context)) {
328 try {
329 unset($context['id']);
330 $field->setArray($context);
331 } catch (Exception $e) {
332 throw new Exception(__(
333 'Settings for field %s could not be found in table tbl_fields_%s.',
334 array($f['id'], $f['type'])
335 ));
336 }
337 }
338
339 self::$_initialiased_fields[$f['id']] = $field;
340 }
341
342 // Check to see if there was any restricts imposed on the fields
343 if (
344 $restrict == Field::__FIELD_ALL__
345 || ($restrict == Field::__TOGGLEABLE_ONLY__ && $field->canToggle())
346 || ($restrict == Field::__UNTOGGLEABLE_ONLY__ && !$field->canToggle())
347 || ($restrict == Field::__FILTERABLE_ONLY__ && $field->canFilter())
348 || ($restrict == Field::__UNFILTERABLE_ONLY__ && !$field->canFilter())
349 ) {
350 $fields[$f['id']] = $field;
351 }
352 }
353 }
354
355 return count($fields) <= 1 && $returnSingle ? current($fields) : $fields;
356 }
357
358 /**
359 * Given a field ID, return the type of the field by querying `tbl_fields`
360 *
361 * @param integer $id
362 * @return string
363 */
364 public static function fetchFieldTypeFromID($id)
365 {
7b74a6e Brendan Abbott Be stricter when constructing queries throughout the core. RE: #2190
brendo authored
366 return Symphony::Database()->fetchVar('type', 0, sprintf("
367 SELECT `type` FROM `tbl_fields` WHERE `id` = %d LIMIT 1",
368 $id
369 ));
854f78a Last lot of code formatting to PSR-2
John Porter authored
370 }
371
372 /**
373 * Given a field ID, return the handle of the field by querying `tbl_fields`
374 *
375 * @param integer $id
376 * @return string
377 */
378 public static function fetchHandleFromID($id)
379 {
7b74a6e Brendan Abbott Be stricter when constructing queries throughout the core. RE: #2190
brendo authored
380 return Symphony::Database()->fetchVar('element_name', 0, sprintf("
381 SELECT `element_name` FROM `tbl_fields` WHERE `id` = %d LIMIT 1",
382 $id
383 ));
854f78a Last lot of code formatting to PSR-2
John Porter authored
384 }
385
386 /**
387 * Given an `$element_name` and a `$section_id`, return the Field ID. Symphony enforces
388 * a uniqueness constraint on a section where every field must have a unique
389 * label (and therefore handle) so whilst it is impossible to have two fields
390 * from the same section, it would be possible to have two fields with the same
391 * name from different sections. Passing the `$section_id` lets you to specify
392 * which section should be searched. If `$element_name` is null, this function will
393 * return all the Field ID's from the given `$section_id`.
394 *
395 * @since Symphony 2.3 This function can now accept $element_name as an array
396 * of handles. These handles can now also include the handle's mode, eg. `title: formatted`
397 *
398 * @param string|array $element_name
399 * The handle of the Field label, or an array of handles. These handles may contain
400 * a mode as well, eg. `title: formatted`.
401 * @param integer $section_id
402 * The section that this field belongs too
403 * @throws DatabaseException
404 * @return mixed
405 * The field ID, or an array of field ID's
406 */
407 public static function fetchFieldIDFromElementName($element_name, $section_id = null)
408 {
409 if (is_null($element_name)) {
7b74a6e Brendan Abbott Be stricter when constructing queries throughout the core. RE: #2190
brendo authored
410 $schema_sql = sprintf("
411 SELECT `id`
854f78a Last lot of code formatting to PSR-2
John Porter authored
412 FROM `tbl_fields`
413 WHERE `parent_section` = %d
414 ORDER BY `sortorder` ASC",
415 $section_id
416 );
417 } else {
418 $element_names = !is_array($element_name) ? array($element_name) : $element_name;
419
420 // allow for pseudo-fields containing colons (e.g. Textarea formatted/unformatted)
421 foreach ($element_names as $index => $name) {
422 $parts = explode(':', $name, 2);
423
424 if (count($parts) == 1) {
425 continue;
426 }
427
428 unset($element_names[$index]);
429
430 // Prevent attempting to look up 'system', which will arise
431 // from `system:pagination`, `system:id` etc.
432 if ($parts[0] == 'system') {
433 continue;
434 }
435
436 $element_names[] = Symphony::Database()->cleanValue(trim($parts[0]));
437 }
438
7b74a6e Brendan Abbott Be stricter when constructing queries throughout the core. RE: #2190
brendo authored
439 $schema_sql = empty($element_names) ? null : sprintf("
440 SELECT `id`
854f78a Last lot of code formatting to PSR-2
John Porter authored
441 FROM `tbl_fields`
442 WHERE 1
443 %s
444 AND `element_name` IN ('%s')
445 ORDER BY `sortorder` ASC",
446 (!is_null($section_id) ? sprintf("AND `parent_section` = %d", $section_id) : ""),
447 implode("', '", array_unique($element_names))
448 );
449 }
450
451 if (is_null($schema_sql)) {
452 return false;
453 }
454
455 $result = Symphony::Database()->fetch($schema_sql);
456
457 if (count($result) == 1) {
458 return (int)$result[0]['id'];
459 } elseif (empty($result)) {
460 return false;
461 } else {
462 foreach ($result as &$r) {
463 $r = (int)$r['id'];
464 }
465
466 return $result;
467 }
468 }
469
470 /**
471 * Work out the next available sort order for a new field
472 *
473 * @return integer
474 * Returns the next sort order
475 */
476 public static function fetchNextSortOrder()
477 {
478 $next = Symphony::Database()->fetchVar(
479 "next",
480 0,
481 "SELECT
482 MAX(p.sortorder) + 1 AS `next`
483 FROM
484 `tbl_fields` AS p
485 LIMIT 1"
486 );
487 return ($next ? (int)$next : 1);
488 }
489
490 /**
491 * Given a `$section_id`, this function returns an array of the installed
492 * fields schema. This includes the `id`, `element_name`, `type`
493 * and `location`.
494 *
495 * @since Symphony 2.3
496 * @param integer $section_id
497 * @throws DatabaseException
498 * @return array
499 * An associative array that contains four keys, `id`, `element_name`,
500 * `type` and `location`
501 */
502 public static function fetchFieldsSchema($section_id)
503 {
504 return Symphony::Database()->fetch(sprintf(
505 "SELECT `id`, `element_name`, `type`, `location`
506 FROM `tbl_fields`
507 WHERE `parent_section` = %d
508 ORDER BY `sortorder` ASC",
509 $section_id
510 ));
511 }
512
513 /**
514 * Returns an array of all available field handles discovered in the
515 * `TOOLKIT . /fields` or `EXTENSIONS . /{}/fields`.
516 *
517 * @return array
518 * A single dimensional array of field handles.
519 */
520 public static function listAll()
521 {
522 $structure = General::listStructure(TOOLKIT . '/fields', '/field.[a-z0-9_-]+.php/i', false, 'asc', TOOLKIT . '/fields');
523
524 $extensions = Symphony::ExtensionManager()->listInstalledHandles();
525
526 if (is_array($extensions) && !empty($extensions)) {
527 foreach ($extensions as $handle) {
528 if (is_dir(EXTENSIONS . '/' . $handle . '/fields')) {
529 $tmp = General::listStructure(EXTENSIONS . '/' . $handle . '/fields', '/field.[a-z0-9_-]+.php/i', false, 'asc', EXTENSIONS . '/' . $handle . '/fields');
530
531 if (is_array($tmp['filelist']) && !empty($tmp['filelist'])) {
532 $structure['filelist'] = array_merge($structure['filelist'], $tmp['filelist']);
533 }
534 }
535 }
536
537 $structure['filelist'] = General::array_remove_duplicates($structure['filelist']);
538 }
539
540 $types = array();
541
542 foreach ($structure['filelist'] as $filename) {
543 $types[] = self::__getHandleFromFilename($filename);
544 }
545
546 return $types;
547 }
548
549 /**
550 * Creates an instance of a given class and returns it. Adds the instance
551 * to the `$_pool` array with the key being the handle.
552 *
553 * @param string $type
554 * The handle of the Field to create (which is it's handle)
555 * @throws Exception
556 * @return Field
557 */
558 public static function create($type)
559 {
560 if (!isset(self::$_pool[$type])) {
561 $classname = self::__getClassName($type);
562 $path = self::__getDriverPath($type);
563
564 if (!file_exists($path)) {
565 throw new Exception(
566 __('Could not find Field %1$s at %2$s.', array('<code>' . $type . '</code>', '<code>' . $path . '</code>'))
567 . ' ' . __('If it was provided by an Extension, ensure that it is installed, and enabled.')
568 );
569 }
570
571 if (!class_exists($classname)) {
572 require_once($path);
573 }
574
575 self::$_pool[$type] = new $classname;
576
577 if (self::$_pool[$type]->canShowTableColumn() && !self::$_pool[$type]->get('show_column')) {
578 self::$_pool[$type]->set('show_column', 'yes');
579 }
580 }
581
582 return clone self::$_pool[$type];
583 }
584
585 /**
586 * Return boolean if the given `$field_type` is in use anywhere in the
587 * current Symphony install.
588 *
589 * @since Symphony 2.3
590 * @param string $field_type
591 * @return boolean
592 */
593 public static function isFieldUsed($field_type)
594 {
595 return Symphony::Database()->fetchVar('count', 0, sprintf(
596 "SELECT COUNT(*) AS `count` FROM `tbl_fields` WHERE `type` = '%s'",
597 $field_type
598 )) > 0;
599 }
600
601 /**
602 * Check if a specific text formatter is used by a Field
603 *
604 * @since Symphony 2.3
605 * @param $text_formatter_handle
606 * The handle of the `TextFormatter`
607 * @return boolean
608 * true if used, false if not
609 */
610 public static function isTextFormatterUsed($text_formatter_handle)
611 {
612 $fields = Symphony::Database()->fetchCol('type', "SELECT DISTINCT `type` FROM `tbl_fields` WHERE `type` NOT IN ('author', 'checkbox', 'date', 'input', 'select', 'taglist', 'upload')");
613
614 if (!empty($fields)) {
615 foreach ($fields as $field) {
616 try {
617 $table = Symphony::Database()->fetchVar('count', 0, sprintf(
618 "SELECT COUNT(*) AS `count`
619 FROM `tbl_fields_%s`
620 WHERE `formatter` = '%s'",
621 Symphony::Database()->cleanValue($field),
622 $text_formatter_handle
623 ));
624 } catch (DatabaseException $ex) {
625 // Table probably didn't have that column
626 }
627
628 if ($table > 0) {
629 return true;
630 }
631 }
632 }
633
634 return false;
635 }
636 }
Something went wrong with that request. Please try again.