GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — integration ( a3ab80...7a98f7 )
by Brendan
06:22
created

Field::displaySettingsPanel()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 14
c 1
b 0
f 0
nc 8
nop 2
dl 0
loc 22
rs 8.9197
1
<?php
2
    /**
3
     * @package toolkit
4
     */
5
6
    /**
7
     * The Field class represents a Symphony Field object. Fields are the building
8
     * blocks for Sections. All fields instances are unique and can only be used once
9
     * in a Symphony install. Fields have their own field table which records where
10
     * instances of this field type have been used in other sections and their settings.
11
     * They also spinoff other `tbl_entry_data_{id}` tables that actually store data for
12
     * entries particular to this field.
13
     */
14
    class Field
15
    {
16
        /**
17
         * The desired result when creating a field in the section editor
18
         *
19
         * @var integer
20
         */
21
        const __OK__ = 100;
22
23
        /**
24
         * If an error occurring when saving a section because of this field,
25
         * this will be returned
26
         *
27
         * @var integer
28
         */
29
        const __ERROR__ = 150;
30
31
        /**
32
         * When saving a section, if a value that is required is missing,
33
         * this will be returned
34
         *
35
         * @var integer
36
         */
37
        const __MISSING_FIELDS__ = 200;
38
39
        /**
40
         * If a value for a setting is invalid, this will be returned
41
         *
42
         * @var integer
43
         */
44
        const __INVALID_FIELDS__ = 220;
45
46
        /**
47
         * If there already is an instance of this field in this section and
48
         * `mustBeUnique()` returns true, this will be returned
49
         *
50
         * @var integer
51
         * @see mustBeUnique()
52
         */
53
        const __DUPLICATE__ = 300;
54
55
        /**
56
         * Fields can returned this is an error occurred when saving the
57
         * field's settings that doesn't fit another `Field` constant
58
         *
59
         * @var integer
60
         */
61
        const __ERROR_CUSTOM__ = 400;
62
63
        /**
64
         * If the field name is not a valid QName, this error will be returned
65
         *
66
         * @var integer
67
         */
68
        const __INVALID_QNAME__ = 500;
69
70
        /**
71
         * Used by the `FieldManager` to return fields that can be toggled
72
         *
73
         * @var integer
74
         */
75
        const __TOGGLEABLE_ONLY__ = 600;
76
77
        /**
78
         * Used by the `FieldManager` to return fields that can't be toggled
79
         *
80
         * @var integer
81
         */
82
        const __UNTOGGLEABLE_ONLY__ = 700;
83
84
        /**
85
         * Used by the `FieldManager` to return fields that can be filtered
86
         *
87
         * @var integer
88
         */
89
        const __FILTERABLE_ONLY__ = 800;
90
91
        /**
92
         * Used by the `FieldManager` to return fields that can't be filtered
93
         *
94
         * @var integer
95
         */
96
        const __UNFILTERABLE_ONLY__ = 900;
97
98
        /**
99
         * Used by the `FieldManager` to just return all fields
100
         *
101
         * @var integer
102
         */
103
        const __FIELD_ALL__ = 1000;
104
105
        /**
106
         * Used to manage the joins when this field used in a datasource
107
         *
108
         * @var integer
109
         */
110
        protected $_key = 0;
111
112
        /**
113
         * An associative array of the settings for this `Field` instance
114
         *
115
         * @var array
116
         */
117
        protected $_settings = array();
118
119
        /**
120
         * Whether this field is required inherently, defaults to false.
121
         *
122
         * @var boolean
123
         */
124
        protected $_required = false;
125
126
        /**
127
         * Whether this field can be viewed on the entries table. Note
128
         * that this is not the same variable as the one set when saving
129
         * a field in the section editor, rather just the if the field has
130
         * the ability to be shown. Defaults to true.
131
         *
132
         * @var boolean
133
         */
134
        protected $_showcolumn = true;
135
136
        /**
137
         * Whether this field has an association that should be shown on
138
         * the Publish Index. This does not mean that it will be, but just
139
         * that this field has the ability too. Defaults to false.
140
         *
141
         * @var boolean
142
         */
143
        protected $_showassociation = false;
144
145
        /**
146
         * Construct a new instance of this field.
147
         */
148
        public function __construct()
149
        {
150
            $this->_handle = (strtolower(get_class($this)) === 'field' ? 'field' : strtolower(substr(get_class($this),
0 ignored issues
show
Bug introduced by
The property _handle does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
151
                5)));
152
        }
153
154
        /**
155
         * Test whether this field can show the table column.
156
         *
157
         * @return boolean
158
         *  true if this can, false otherwise.
159
         */
160
        public function canShowTableColumn()
161
        {
162
            return $this->_showcolumn;
163
        }
164
165
        /**
166
         * Test whether this field can show the association column in
167
         * the Publish Index.
168
         *
169
         * @since Symphony 2.6.0
170
         * @return boolean
171
         *  true if this can, false otherwise.
172
         */
173
        public function canShowAssociationColumn()
174
        {
175
            return $this->_showassociation;
176
        }
177
178
        /**
179
         * Test whether this field can be toggled using the With Selected menu
180
         * on the Publish Index.
181
         *
182
         * @return boolean
183
         *  true if it can be toggled, false otherwise.
184
         */
185
        public function canToggle()
186
        {
187
            return false;
188
        }
189
190
        /**
191
         * Accessor to the toggle states. This default implementation returns
192
         * an empty array.
193
         *
194
         * @return array
195
         *  the array of toggle states.
196
         */
197
        public function getToggleStates()
198
        {
199
            return array();
200
        }
201
202
        /**
203
         * Toggle the field data. This default implementation always returns
204
         * the input data.
205
         *
206
         * @param array $data
207
         *   the data to toggle.
208
         * @param string $newState
209
         *   the new value to set
210
         * @param integer $entry_id (optional)
211
         *   an optional entry ID for more intelligent processing. defaults to null
212
         * @return array
213
         *   the toggled data.
214
         */
215
        public function toggleFieldData(array $data, $newState, $entry_id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $entry_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
216
        {
217
            return $data;
218
        }
219
220
        /**
221
         * Test whether this field can be filtered in the publish index. This default
222
         * implementation prohibts filtering. Publish Filtering allows the index view
223
         * to filter results. Subclasses should override this if
224
         * filtering is supported.
225
         *
226
         * @return boolean
227
         *  true if this can be publish-filtered, false otherwise.
228
         */
229
        public function canPublishFilter()
230
        {
231
            return $this->canFilter();
232
        }
233
234
        /**
235
         * Test whether this field can be filtered. This default implementation
236
         * prohibits filtering. Filtering allows the XML output results to be limited
237
         * according to an input parameter. Subclasses should override this if
238
         * filtering is supported.
239
         *
240
         * @return boolean
241
         *  true if this can be filtered, false otherwise.
242
         */
243
        public function canFilter()
244
        {
245
            return false;
246
        }
247
248
        /**
249
         * Test whether this field can be prepopulated with data. This default
250
         * implementation does not support pre-population and, thus, returns false.
251
         *
252
         * @return boolean
253
         *  true if this can be pre-populated, false otherwise.
254
         */
255
        public function canPrePopulate()
256
        {
257
            return false;
258
        }
259
260
        /**
261
         * Test whether this field can be sorted. This default implementation
262
         * returns false.
263
         *
264
         * @return boolean
265
         *  true if this field is sortable, false otherwise.
266
         */
267
        public function isSortable()
268
        {
269
            return false;
270
        }
271
272
        /**
273
         * Test whether this field must be unique in a section, that is, only one of
274
         * this field's type is allowed per section. This default implementation
275
         * always returns false.
276
         *
277
         * @return boolean
278
         *  true if the content of this field must be unique, false otherwise.
279
         */
280
        public function mustBeUnique()
281
        {
282
            return false;
283
        }
284
285
        /**
286
         * Test whether this field supports data-source output grouping. This
287
         * default implementation prohibits grouping. Data-source grouping allows
288
         * clients of this field to group the XML output according to this field.
289
         * Subclasses should override this if grouping is supported.
290
         *
291
         * @return boolean
292
         *  true if this field does support data-source grouping, false otherwise.
293
         */
294
        public function allowDatasourceOutputGrouping()
295
        {
296
            return false;
297
        }
298
299
        /**
300
         * Test whether this field requires grouping. If this function returns true
301
         * SQL statements generated in the `EntryManager` will include the `DISTINCT` keyword
302
         * to only return a single row for an entry regardless of how many 'matches' it
303
         * might have. This default implementation returns false.
304
         *
305
         * @return boolean
306
         *  true if this field requires grouping, false otherwise.
307
         */
308
        public function requiresSQLGrouping()
309
        {
310
            return false;
311
        }
312
313
        /**
314
         * Test whether this field supports data-source parameter output. This
315
         * default implementation prohibits parameter output. Data-source
316
         * parameter output allows this field to be provided as a parameter
317
         * to other data-sources or XSLT. Subclasses should override this if
318
         * parameter output is supported.
319
         *
320
         * @return boolean
321
         *  true if this supports data-source parameter output, false otherwise.
322
         */
323
        public function allowDatasourceParamOutput()
324
        {
325
            return false;
326
        }
327
328
        /**
329
         * Fill the input data array with default values for known keys provided
330
         * these settings are not already set. The input array is then used to set
331
         * the values of the corresponding settings for this field. This function
332
         * is called when a section is saved.
333
         *
334
         * @param array $settings
335
         *  the data array to initialize if necessary.
336
         */
337
        public function setFromPOST(array $settings = array())
338
        {
339
            $settings['location'] = (isset($settings['location']) ? $settings['location'] : 'main');
340
            $settings['required'] = (isset($settings['required']) && $settings['required'] === 'yes' ? 'yes' : 'no');
341
            $settings['show_column'] = (isset($settings['show_column']) && $settings['show_column'] === 'yes' ? 'yes' : 'no');
342
343
            $this->setArray($settings);
344
        }
345
346
        /**
347
         * Add or overwrite the settings of this field by providing an associative array
348
         * of the settings. This will do nothing if the input array is empty. If a setting is
349
         * omitted from the input array, it will not be unset by this function
350
         *
351
         * @param array $array
352
         *  the associative array of settings for this field
353
         */
354
        public function setArray(array $array = array())
355
        {
356
            if (empty($array)) {
357
                return;
358
            }
359
360
            foreach ($array as $setting => $value) {
361
                $this->set($setting, $value);
362
            }
363
        }
364
365
        /**
366
         * Fields have settings that define how that field will act in a section, including
367
         * if it's required, any validators, if it can be shown on the entries table etc. This
368
         * function will set a setting to a value.  This function will set a setting to a value
369
         * overwriting any existing value for this setting
370
         *
371
         * @param string $setting
372
         *  the setting key.
373
         * @param mixed $value
374
         *  the value of the setting.
375
         */
376
        public function set($setting, $value)
377
        {
378
            $this->_settings[$setting] = $value;
379
        }
380
381
        /**
382
         * Unset the value of a setting by the key
383
         *
384
         * @param string $setting
385
         *  the key of the setting to unset.
386
         */
387
        public function remove($setting)
388
        {
389
            unset($this->_settings[$setting]);
390
        }
391
392
        /**
393
         * Just prior to the field being deleted, this function allows
394
         * Fields to cleanup any additional things before it is removed
395
         * from the section. This may be useful to remove data from any
396
         * custom field tables or the configuration.
397
         *
398
         * @since Symphony 2.2.1
399
         * @return boolean
400
         */
401
        public function tearDown()
402
        {
403
            return true;
404
        }
405
406
        /**
407
         * Allows a field to set default settings.
408
         *
409
         * @param array $settings
410
         *  the array of settings to populate with their defaults.
411
         */
412
        public function findDefaults(array &$settings)
413
        {
414
        }
415
416
        /**
417
         * Display the default settings panel, calls the `buildSummaryBlock`
418
         * function after basic field settings are added to the wrapper.
419
         *
420
         * @see buildSummaryBlock()
421
         * @param XMLElement $wrapper
422
         *    the input XMLElement to which the display of this will be appended.
423
         * @param mixed $errors
424
         *  the input error collection. this defaults to null.
425
         * @throws InvalidArgumentException
426
         */
427
        public function displaySettingsPanel(XMLElement &$wrapper, $errors = null)
428
        {
429
            // Create header
430
            $location = ($this->get('location') ? $this->get('location') : 'main');
431
            $header = new XMLElement('header', null,
432
                array('class' => 'frame-header ' . $location, 'data-name' => $this->name()));
433
            $label = (($this->get('label')) ? $this->get('label') : __('New Field'));
434
            $header->appendChild(new XMLElement('h4',
435
                '<strong>' . $label . '</strong> <span class="type">' . $this->name() . '</span>'));
436
            $wrapper->appendChild($header);
437
438
            // Create content
439
            $wrapper->appendChild(Widget::Input('fields[' . $this->get('sortorder') . '][type]', $this->handle(),
440
                'hidden'));
441
442
            if ($this->get('id')) {
443
                $wrapper->appendChild(Widget::Input('fields[' . $this->get('sortorder') . '][id]', $this->get('id'),
444
                    'hidden'));
445
            }
446
447
            $wrapper->appendChild($this->buildSummaryBlock($errors));
448
        }
449
450
        /**
451
         * Accessor to the a setting by name. If no setting is provided all the
452
         * settings of this `Field` instance are returned.
453
         *
454
         * @param string $setting (optional)
455
         *  the name of the setting to access the value for. This is optional and
456
         *  defaults to null in which case all settings are returned.
457
         * @return null|mixed|array
458
         *  the value of the setting if there is one, all settings if the input setting
459
         *  was omitted or null if the setting was supplied but there is no value
460
         *  for that setting.
461
         */
462 View Code Duplication
        public function get($setting = null)
463
        {
464
            if (is_null($setting)) {
465
                return $this->_settings;
466
            }
467
468
            if (!isset($this->_settings[$setting])) {
469
                return null;
470
            }
471
472
            return $this->_settings[$setting];
473
        }
474
475
        /**
476
         * Accessor to the name of this field object. The name may contain characters
477
         * that normally would be stripped in the handle while also allowing the field
478
         * name to be localized. If a name is not set, it will return the handle of the
479
         * the field
480
         *
481
         * @return string
482
         *  The field name
483
         */
484
        public function name()
485
        {
486
            return ($this->_name ? $this->_name : $this->_handle);
0 ignored issues
show
Bug introduced by
The property _name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
487
        }
488
489
        /**
490
         * Accessor to the handle of this field object. The Symphony convention is
491
         * for field subclass names to be prefixed with field. Handle removes this prefix
492
         * so that the class handle can be used as the field type.
493
         *
494
         * @return string
495
         *  The field classname minus the field prefix.
496
         */
497
        public function handle()
498
        {
499
            return $this->_handle;
500
        }
501
502
        /**
503
         * Construct the html block to display a summary of this field, which is the field
504
         * Label and it's location within the section. Any error messages generated are
505
         * appended to the optional input error array. This function calls
506
         * `buildLocationSelect` once it is completed
507
         *
508
         * @see buildLocationSelect()
509
         * @param array $errors (optional)
510
         *    an array to append html formatted error messages to. this defaults to null.
511
         * @throws InvalidArgumentException
512
         * @return XMLElement
513
         *    the root XML element of the html display of this.
514
         */
515
        public function buildSummaryBlock($errors = null)
516
        {
517
            $div = new XMLElement('div');
518
519
            // Publish label
520
            $label = Widget::Label(__('Label'));
521
            $label->appendChild(
522
                Widget::Input('fields[' . $this->get('sortorder') . '][label]', $this->get('label'))
523
            );
524 View Code Duplication
            if (isset($errors['label'])) {
525
                $div->appendChild(Widget::Error($label, $errors['label']));
526
            } else {
527
                $div->appendChild($label);
528
            }
529
530
            // Handle + placement
531
            $group = new XMLElement('div');
532
            $group->setAttribute('class', 'two columns');
533
534
            $label = Widget::Label(__('Handle'));
535
            $label->setAttribute('class', 'column');
536
537
            $label->appendChild(Widget::Input('fields[' . $this->get('sortorder') . '][element_name]',
538
                $this->get('element_name')));
539
540
            if (isset($errors['element_name'])) {
541
                $group->appendChild(Widget::Error($label, $errors['element_name']));
542
            } else {
543
                $group->appendChild($label);
544
            }
545
546
            // Location
547
            $group->appendChild($this->buildLocationSelect($this->get('location'),
548
                'fields[' . $this->get('sortorder') . '][location]'));
549
550
            $div->appendChild($group);
551
552
            return $div;
553
        }
554
555
        /**
556
         * Build the location select widget. This widget allows users to select
557
         * whether this field will appear in the main content column or in the sidebar
558
         * when creating a new entry.
559
         *
560
         * @param string|null $selected (optional)
561
         *    the currently selected location, if there is one. this defaults to null.
562
         * @param string $name (optional)
563
         *    the name of this field. this is optional and defaults to `fields[location]`.
564
         * @param string $label_value (optional)
565
         *    any predefined label for this widget. this is an optional argument that defaults
566
         *    to null.
567
         * @throws InvalidArgumentException
568
         * @return XMLElement
569
         *    An XMLElement representing a `<select>` field containing the options.
570
         */
571
        public function buildLocationSelect($selected = null, $name = 'fields[location]', $label_value = null)
572
        {
573
            if (!$label_value) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $label_value of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
574
                $label_value = __('Placement');
575
            }
576
577
            $label = Widget::Label($label_value);
578
            $label->setAttribute('class', 'column');
579
580
            $options = array(
581
                array('main', $selected === 'main', __('Main content')),
582
                array('sidebar', $selected === 'sidebar', __('Sidebar'))
583
            );
584
            $label->appendChild(Widget::Select($name, $options));
585
586
            return $label;
587
        }
588
589
        /**
590
         * Construct the html widget for selecting a text formatter for this field.
591
         *
592
         * @param string $selected (optional)
593
         *    the currently selected text formatter name if there is one. this defaults
594
         *    to null.
595
         * @param string $name (optional)
596
         *    the name of this field in the form. this is optional and defaults to
597
         *    "fields[format]".
598
         * @param string $label_value
599
         *    the default label for the widget to construct. if null is passed in then
600
         *    this defaults to the localization of "Formatting".
601
         * @throws InvalidArgumentException
602
         * @return XMLElement
603
         *    An XMLElement representing a `<select>` field containing the options.
604
         */
605
        public function buildFormatterSelect($selected = null, $name = 'fields[format]', $label_value)
606
        {
607
            $formatters = TextformatterManager::listAll();
608
609
            if (!$label_value) {
610
                $label_value = __('Formatting');
611
            }
612
613
            $label = Widget::Label($label_value);
614
            $label->setAttribute('class', 'column');
615
616
            $options = array();
617
618
            $options[] = array('none', false, __('None'));
619
620
            if (!empty($formatters) && is_array($formatters)) {
621
                foreach ($formatters as $handle => $about) {
622
                    $options[] = array($handle, ($selected === $handle), $about['name']);
623
                }
624
            }
625
626
            $label->appendChild(Widget::Select($name, $options));
627
628
            return $label;
629
        }
630
631
        /**
632
         * Append a validator selector to a given `XMLElement`. Note that this
633
         * function differs from the other two similarly named build functions in
634
         * that it takes an `XMLElement` to append the Validator to as a parameter,
635
         * and does not return anything.
636
         *
637
         * @param XMLElement $wrapper
638
         *    the parent element to append the XMLElement of the Validation select to,
639
         *  passed by reference.
640
         * @param string $selected (optional)
641
         *    the current validator selection if there is one. defaults to null if there
642
         *    isn't.
643
         * @param string $name (optional)
644
         *    the form element name of this field. this defaults to "fields[validator]".
645
         * @param string $type (optional)
646
         *    the type of input for the validation to apply to. this defaults to 'input'
647
         *    but also accepts 'upload'.
648
         * @param array $errors (optional)
649
         *    an associative array of errors
650
         * @throws InvalidArgumentException
651
         */
652
        public function buildValidationSelect(
653
            XMLElement &$wrapper,
654
            $selected = null,
655
            $name = 'fields[validator]',
656
            $type = 'input',
657
            array $errors = null
658
        ) {
659
            include TOOLKIT . '/util.validators.php';
660
661
            $rules = ($type === 'upload' ? $upload : $validators);
0 ignored issues
show
Bug introduced by
The variable $upload does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $validators does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
662
663
            $label = Widget::Label(__('Validation Rule'));
664
            $label->setAttribute('class', 'column');
665
            $label->appendChild(new XMLElement('i', __('Optional')));
666
            $label->appendChild(Widget::Input($name, $selected));
667
668
            $ul = new XMLElement('ul', null,
669
                array('class' => 'tags singular', 'data-interactive' => 'data-interactive'));
670
            foreach ($rules as $name => $rule) {
671
                $ul->appendChild(new XMLElement('li', $name, array('class' => $rule)));
672
            }
673
674
            if (isset($errors['validator'])) {
675
                $div = new XMLElement('div');
676
                $div->appendChild($label);
677
                $div->appendChild($ul);
678
679
                $wrapper->appendChild(Widget::Error($div, $errors['validator']));
680
            } else {
681
                $wrapper->appendChild($label);
682
                $wrapper->appendChild($ul);
683
            }
684
        }
685
686
        /**
687
         * Append the html widget for selecting an association interface and editor
688
         * for this field.
689
         *
690
         * @param XMLElement $wrapper
691
         *    the parent XML element to append the association interface selection to,
692
         *    if either interfaces or editors are provided to the system.
693
         * @since Symphony 2.5.0
694
         */
695
        public function appendAssociationInterfaceSelect(XMLElement &$wrapper)
696
        {
697
            $wrapper->setAttribute('data-condition', 'associative');
698
699
            $interfaces = Symphony::ExtensionManager()->getProvidersOf(iProvider::ASSOCIATION_UI);
700
            $editors = Symphony::ExtensionManager()->getProvidersOf(iProvider::ASSOCIATION_EDITOR);
701
702
            if (!empty($interfaces) || !empty($editors)) {
703
                $association_context = $this->getAssociationContext();
704
705
                $group = new XMLElement('div');
706
                if (!empty($interfaces) && !empty($editors)) {
707
                    $group->setAttribute('class', 'two columns');
708
                }
709
710
                // Create interface select
711 View Code Duplication
                if (!empty($interfaces)) {
712
                    $label = Widget::Label(__('Association Interface'), null, 'column');
713
                    $label->appendChild(new XMLElement('i', __('Optional')));
714
715
                    $options = array(
716
                        array(null, false, __('None'))
717
                    );
718
                    foreach ($interfaces as $id => $name) {
719
                        $options[] = array($id, ($association_context['interface'] === $id), $name);
720
                    }
721
722
                    $select = Widget::Select('fields[' . $this->get('sortorder') . '][association_ui]', $options);
723
                    $label->appendChild($select);
724
                    $group->appendChild($label);
725
                }
726
727
                // Create editor select
728 View Code Duplication
                if (!empty($editors)) {
729
                    $label = Widget::Label(__('Association Editor'), null, 'column');
730
                    $label->appendChild(new XMLElement('i', __('Optional')));
731
732
                    $options = array(
733
                        array(null, false, __('None'))
734
                    );
735
                    foreach ($editors as $id => $name) {
736
                        $options[] = array($id, ($association_context['editor'] === $id), $name);
737
                    }
738
739
                    $select = Widget::Select('fields[' . $this->get('sortorder') . '][association_editor]', $options);
740
                    $label->appendChild($select);
741
                    $group->appendChild($label);
742
                }
743
744
                $wrapper->appendChild($group);
745
            }
746
        }
747
748
        /**
749
         * Get association data of the current field from the page context.
750
         *
751
         * @since Symphony 2.5.0
752
         * @return array
753
         */
754
        public function getAssociationContext()
755
        {
756
            $context = Symphony::Engine()->Page->getContext();
757
            $associations = $context['associations']['parent'];
758
            $field_association = array();
759
            $count = 0;
760
761
            if (!empty($associations)) {
762
                $associationsCount = count($associations);
763
                for ($i = 0; $i < $associationsCount; $i++) {
764
                    if ($associations[$i]['child_section_field_id'] === $this->get('id')) {
765
                        if ($count === 0) {
766
                            $field_association = $associations[$i];
767
                            $count++;
768
                        } else {
769
                            $field_association['parent_section_id'] .= '|' . $associations[$i]['parent_section_id'];
770
                            $field_association['parent_section_field_id'] .= '|' . $associations[$i]['parent_section_field_id'];
771
                        }
772
                    }
773
                }
774
            }
775
776
            return $field_association;
777
        }
778
779
        /**
780
         * Set association data for the current field.
781
         *
782
         * @since Symphony 2.5.0
783
         * @param XMLElement $wrapper
784
         */
785
        public function setAssociationContext(XMLElement &$wrapper)
786
        {
787
            $association_context = $this->getAssociationContext();
788
789
            if (!empty($association_context)) {
790
                $wrapper->setAttributeArray(array(
791
                    'data-parent-section-id' => $association_context['parent_section_id'],
792
                    'data-parent-section-field-id' => $association_context['parent_section_field_id'],
793
                    'data-child-section-id' => $association_context['child_section_id'],
794
                    'data-child-section-field-id' => $association_context['child_section_field_id'],
795
                    'data-interface' => $association_context['interface'],
796
                    'data-editor' => $association_context['editor']
797
                ));
798
            }
799
        }
800
801
        /**
802
         * Append the show association html widget to the input parent XML element. This
803
         * widget allows fields that provide linking to hide or show the column in the linked
804
         * section, similar to how the Show Column functionality works, but for the linked
805
         * section.
806
         *
807
         * @param XMLElement $wrapper
808
         *    the parent XML element to append the checkbox to.
809
         * @param string $help (optional)
810
         *    a help message to show below the checkbox.
811
         * @throws InvalidArgumentException
812
         */
813
        public function appendShowAssociationCheckbox(XMLElement &$wrapper, $help = null)
814
        {
815
            if (!$this->_showassociation) {
816
                return;
817
            }
818
819
            $label = $this->createCheckboxSetting($wrapper, 'show_association',
820
                __('Display associations in entries table'), $help);
821
            $label->setAttribute('data-condition', 'associative');
822
        }
823
824
        /**
825
         * Given the setting name and the label, this helper method will add
826
         * the required markup for a checkbox to the given `$wrapper`.
827
         *
828
         * @since Symphony 2.5.2
829
         * @param XMLElement $wrapper
830
         *  Passed by reference, this will have the resulting markup appended to it
831
         * @param string $setting
832
         *  This will be used with $this->get() to get the existing value
833
         * @param string $label_description
834
         *  This will be localisable and displayed after the checkbox when
835
         *  generated.
836
         * @param string $help (optional)
837
         *    A help message to show below the checkbox.
838
         * @return XMLElement
839
         *  The Label and Checkbox that was just added to the `$wrapper`.
840
         */
841
        public function createCheckboxSetting(XMLElement &$wrapper, $setting, $label_description, $help = null)
842
        {
843
            $order = $this->get('sortorder');
844
            $name = "fields[$order][$setting]";
845
846
            $label = Widget::Checkbox($name, $this->get($setting), $label_description, $wrapper, $help);
847
            $label->addClass('column');
848
849
            return $label;
850
        }
851
852
        /**
853
         * Append the default status footer to the field settings panel.
854
         * Displays the required and show column checkboxes.
855
         *
856
         * @param XMLElement $wrapper
857
         *    the parent XML element to append the checkbox to.
858
         * @throws InvalidArgumentException
859
         */
860
        public function appendStatusFooter(XMLElement &$wrapper)
861
        {
862
            $fieldset = new XMLElement('fieldset');
863
            $div = new XMLElement('div', null, array('class' => 'two columns'));
864
865
            $this->appendRequiredCheckbox($div);
866
            $this->appendShowColumnCheckbox($div);
867
868
            $fieldset->appendChild($div);
869
            $wrapper->appendChild($fieldset);
870
        }
871
872
        /**
873
         * Append and set a labeled html checkbox to the input XML element if this
874
         * field is set as a required field.
875
         *
876
         * @param XMLElement $wrapper
877
         *    the parent XML element to append the constructed html checkbox to if
878
         *    necessary.
879
         * @throws InvalidArgumentException
880
         */
881
        public function appendRequiredCheckbox(XMLElement &$wrapper)
882
        {
883
            if (!$this->_required) {
884
                return;
885
            }
886
887
            $this->createCheckboxSetting($wrapper, 'required', __('Make this a required field'));
888
        }
889
890
        /**
891
         * Append the show column html widget to the input parent XML element. This
892
         * displays a column in the entries table or not.
893
         *
894
         * @param XMLElement $wrapper
895
         *    the parent XML element to append the checkbox to.
896
         * @throws InvalidArgumentException
897
         */
898
        public function appendShowColumnCheckbox(XMLElement &$wrapper)
899
        {
900
            if (!$this->_showcolumn) {
901
                return;
902
            }
903
904
            $this->createCheckboxSetting($wrapper, 'show_column', __('Display in entries table'));
905
        }
906
907
        /**
908
         * Check the field's settings to ensure they are valid on the section
909
         * editor
910
         *
911
         * @param array $errors
912
         *  the array to populate with the errors found.
913
         * @param boolean $checkForDuplicates (optional)
914
         *  if set to true, duplicate Field name's in the same section will be flagged
915
         *  as errors. Defaults to true.
916
         * @return integer
917
         *  returns the status of the checking. if errors has been populated with
918
         *  any errors `self::__ERROR__`, `self::__OK__` otherwise.
919
         */
920
        public function checkFields(array &$errors, $checkForDuplicates = true)
921
        {
922
            $parent_section = $this->get('parent_section');
923
            $label = $this->get('label');
924
            $element_name = $this->get('element_name');
925
926 View Code Duplication
            if (Lang::isUnicodeCompiled()) {
927
                $valid_name = preg_match('/^[\p{L}]([0-9\p{L}\.\-\_]+)?$/u', $element_name);
928
            } else {
929
                $valid_name = preg_match('/^[A-z]([\w\d-_\.]+)?$/i', $element_name);
930
            }
931
932
            if ($label === '') {
933
                $errors['label'] = __('This is a required field.');
934
            } elseif (strtolower($label) === 'id') {
935
                $errors['label'] = __('%s is a reserved name used by the system and is not allowed for a field handle. Try using %s instead.',
936
                    array('<code>ID</code>', '<code>UID</code>'));
937
            }
938
939
            if ($element_name === '') {
940
                $errors['element_name'] = __('This is a required field.');
941
            } elseif ($element_name === 'id') {
942
                $errors['element_name'] = __('%s is a reserved name used by the system and is not allowed for a field handle. Try using %s instead.',
943
                    array('<code>id</code>', '<code>uid</code>'));
944
            } elseif (!$valid_name) {
945
                $errors['element_name'] = __('Invalid element name. Must be valid %s.', array('<code>QName</code>'));
946
            } elseif ($checkForDuplicates) {
947
                if (FieldManager::fetchFieldIDFromElementName($element_name, $parent_section) !== $this->get('id')) {
948
                    $errors['element_name'] = __('A field with that element name already exists. Please choose another.');
949
                }
950
            }
951
952
            // Check that if the validator is provided that it's a valid regular expression
953
            if (!is_null($this->get('validator')) && $this->get('validator') !== '') {
954
                if (@preg_match($this->get('validator'), 'teststring') === false) {
955
                    $errors['validator'] = __('Validation rule is not a valid regular expression');
956
                }
957
            }
958
959
            return (!empty($errors) ? self::__ERROR__ : self::__OK__);
960
        }
961
962
        /**
963
         * Format this field value for display in the Associations Drawer publish index.
964
         * By default, Symphony will use the return value of the `prepareReadableValue` function.
965
         *
966
         * @since Symphony 2.4
967
         * @since Symphony 2.5.0 The prepopulate parameter was added.
968
         *
969
         * @param Entry $e
970
         *   The associated entry
971
         * @param array $parent_association
972
         *   An array containing information about the association
973
         * @param string $prepopulate
974
         *   A string containing prepopulate parameter to append to the association url
975
         *
976
         * @return XMLElement
977
         *   The XMLElement must be a li node, since it will be added an ul node.
978
         */
979
        public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepopulate = '')
980
        {
981
            $value = $this->prepareReadableValue($e->getData($this->get('id')), $e->get('id'));
982
983
            // fallback for compatibility since the default
984
            // `preparePlainTextValue` is not compatible with all fields
985
            // this should be removed in Symphony 3.0.0
986
            if (empty($value)) {
987
                $value = strip_tags($this->prepareTableValue($e->getData($this->get('id')), null, $e->get('id')));
988
            }
989
990
            // use our factory method to create the html
991
            $li = self::createAssociationsDrawerXMLElement($value, $e, $parent_association, $prepopulate);
992
993
            $li->setAttribute('class', 'field-' . $this->get('type'));
994
995
            return $li;
996
        }
997
998
        /**
999
         * Format this field value for display as readable  text value. By default, it
1000
         * will call `Field::prepareTextValue` to get the raw text value of this field.
1001
         *
1002
         * If $truncate is set to true, Symphony will truncate the value to the
1003
         * configuration setting `cell_truncation_length`.
1004
         *
1005
         * @since Symphony 2.5.0
1006
         * @param array $data
1007
         *  an associative array of data for this string. At minimum this requires a
1008
         *  key of 'value'.
1009
         * @param integer $entry_id (optional)
1010
         *  An option entry ID for more intelligent processing. Defaults to null.
1011
         * @param bool $truncate
1012
         *  Defaults to false
1013
         * @param string $defaultValue (optional)
1014
         *  The value to use when no plain text representation of the field's data
1015
         *  can be made. Defaults to null.
1016
         * @return string
1017
         *  The readable text summary of the values of this field instance.
1018
         */
1019
        public function prepareReadableValue($data, $entry_id = null, $truncate = false, $defaultValue = null)
1020
        {
1021
            $value = $this->prepareTextValue($data, $entry_id);
1022
1023
            if ($truncate) {
1024
                $max_length = Symphony::Configuration()->get('cell_truncation_length', 'symphony');
1025
                $max_length = ($max_length ? $max_length : 75);
1026
1027
                $value = (General::strlen($value) <= $max_length ? $value : General::substr($value, 0,
1028
                        $max_length) . '…');
1029
            }
1030
1031
            if (General::strlen($value) === 0 && $defaultValue !== null) {
1032
                $value = $defaultValue;
1033
            }
1034
1035
            return $value;
1036
        }
1037
1038
        /**
1039
         * Format this field value for complete display as text (string). By default,
1040
         * it looks for the 'value' key in the $data array and strip tags from it.
1041
         *
1042
         * @since Symphony 2.5.0
1043
         * @param array $data
1044
         *  an associative array of data for this string. At minimum this requires a
1045
         *  key of 'value'.
1046
         * @param integer $entry_id (optional)
1047
         *  An option entry ID for more intelligent processing. defaults to null
1048
         * @return string
1049
         *  the complete text representation of the values of this field instance.
1050
         */
1051
        public function prepareTextValue($data, $entry_id = null)
1052
        {
1053
            return strip_tags($data['value']);
1054
        }
1055
1056
        /**
1057
         * Format this field value for display in the publish index tables.
1058
         *
1059
         * Since Symphony 2.5.0, this function will call `Field::prepareReadableValue`
1060
         * in order to get the field's human readable value.
1061
         *
1062
         * @param array $data
1063
         *  an associative array of data for this string. At minimum this requires a
1064
         *  key of 'value'.
1065
         * @param XMLElement $link (optional)
1066
         *  an XML link structure to append the content of this to provided it is not
1067
         *  null. it defaults to null.
1068
         * @param integer $entry_id (optional)
1069
         *  An option entry ID for more intelligent processing. defaults to null
1070
         * @return string
1071
         *  the formatted string summary of the values of this field instance.
1072
         */
1073
        public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
1074
        {
1075
            $value = $this->prepareReadableValue($data, $entry_id, true, __('None'));
1076
1077
            if ($link) {
1078
                $link->setValue($value);
1079
1080
                return $link->generate();
1081
            }
1082
1083
            return $value;
1084
        }
1085
1086
        /**
1087
         * This is general purpose factory method that makes it easier to create the
1088
         * markup needed in order to create an Associations Drawer XMLElement.
1089
         *
1090
         * @since Symphony 2.5.0
1091
         *
1092
         * @param string $value
1093
         *   The value to display in the link
1094
         * @param Entry $e
1095
         *   The associated entry
1096
         * @param array $parent_association
1097
         *   An array containing information about the association
1098
         * @param string $prepopulate
1099
         *   A string containing prepopulate parameter to append to the association url
1100
         *
1101
         * @return XMLElement
1102
         *   The XMLElement must be a li node, since it will be added an ul node.
1103
         */
1104
        public static function createAssociationsDrawerXMLElement(
1105
            $value,
1106
            Entry $e,
1107
            array $parent_association,
1108
            $prepopulate = ''
1109
        ) {
1110
            $li = new XMLElement('li');
1111
            $a = new XMLElement('a', $value);
1112
            $a->setAttribute('href',
1113
                SYMPHONY_URL . '/publish/' . $parent_association['handle'] . '/edit/' . $e->get('id') . '/' . $prepopulate);
1114
            $li->appendChild($a);
1115
1116
            return $li;
1117
        }
1118
1119
        /**
1120
         * Display the publish panel for this field. The display panel is the
1121
         * interface shown to Authors that allow them to input data into this
1122
         * field for an `Entry`.
1123
         *
1124
         * @param XMLElement $wrapper
1125
         *  the XML element to append the html defined user interface to this
1126
         *  field.
1127
         * @param array $data (optional)
1128
         *  any existing data that has been supplied for this field instance.
1129
         *  this is encoded as an array of columns, each column maps to an
1130
         *  array of row indexes to the contents of that column. this defaults
1131
         *  to null.
1132
         * @param mixed $flagWithError (optional)
1133
         *  flag with error defaults to null.
1134
         * @param string $fieldnamePrefix (optional)
1135
         *  the string to be prepended to the display of the name of this field.
1136
         *  this defaults to null.
1137
         * @param string $fieldnamePostfix (optional)
1138
         *  the string to be appended to the display of the name of this field.
1139
         *  this defaults to null.
1140
         * @param integer $entry_id (optional)
1141
         *  the entry id of this field. this defaults to null.
1142
         */
1143
        public function displayPublishPanel(
1144
            XMLElement &$wrapper,
1145
            $data = null,
1146
            $flagWithError = null,
1147
            $fieldnamePrefix = null,
1148
            $fieldnamePostfix = null,
1149
            $entry_id = null
0 ignored issues
show
Unused Code introduced by
The parameter $entry_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1150
        ) {
1151
        }
1152
1153
        /**
1154
         * Check the field data that has been posted from a form. This will set the
1155
         * input message to the error message or to null if there is none. Any existing
1156
         * message value will be overwritten.
1157
         *
1158
         * @param array $data
1159
         *  the input data to check.
1160
         * @param string $message
1161
         *  the place to set any generated error message. any previous value for
1162
         *  this variable will be overwritten.
1163
         * @param integer $entry_id (optional)
1164
         *  the optional id of this field entry instance. this defaults to null.
1165
         * @return integer
1166
         *  `self::__MISSING_FIELDS__` if there are any missing required fields,
1167
         *  `self::__OK__` otherwise.
1168
         */
1169
        public function checkPostFieldData($data, &$message, $entry_id = null)
1170
        {
1171
            $message = null;
1172
1173
            $has_no_value = is_array($data) ? empty($data) : strlen(trim($data)) === 0;
1174
1175
            if ($this->get('required') === 'yes' && $has_no_value) {
1176
                $message = __('‘%s’ is a required field.', array($this->get('label')));
1177
1178
                return self::__MISSING_FIELDS__;
1179
            }
1180
1181
            return self::__OK__;
1182
        }
1183
1184
        /**
1185
         * Process the raw field data.
1186
         *
1187
         * @param mixed $data
1188
         *  post data from the entry form
1189
         * @param integer $status
1190
         *  the status code resultant from processing the data.
1191
         * @param string $message
1192
         *  the place to set any generated error message. any previous value for
1193
         *  this variable will be overwritten.
1194
         * @param boolean $simulate (optional)
1195
         *  true if this will tell the CF's to simulate data creation, false
1196
         *  otherwise. this defaults to false. this is important if clients
1197
         *  will be deleting or adding data outside of the main entry object
1198
         *  commit function.
1199
         * @param mixed $entry_id (optional)
1200
         *  the current entry. defaults to null.
1201
         * @return array
1202
         *  the processed field data.
1203
         */
1204
        public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)
1205
        {
1206
            $status = self::__OK__;
1207
1208
            return array(
1209
                'value' => $data,
1210
            );
1211
        }
1212
1213
        /**
1214
         * Returns the types of filter suggestion this field supports.
1215
         * The array may contain the following values:
1216
         *
1217
         * - `entry` for searching entries in the current section
1218
         * - `association` for searching entries in associated sections
1219
         * - `static` for searching static values
1220
         * - `date` for searching in a calendar
1221
         * - `parameters` for searching in parameters
1222
         *
1223
         * If the date type is set, only the calendar will be shown in the suggestion dropdown.
1224
         *
1225
         * @since Symphony 2.6.0
1226
         * @return array
1227
         */
1228
        public function fetchSuggestionTypes()
1229
        {
1230
            return array('entry');
1231
        }
1232
1233
        /**
1234
         * Display the default data-source filter panel.
1235
         *
1236
         * @param XMLElement $wrapper
1237
         *    the input XMLElement to which the display of this will be appended.
1238
         * @param mixed $data (optional)
1239
         *    the input data. this defaults to null.
1240
         * @param null $errors
1241
         *  the input error collection. this defaults to null.
1242
         * @param string $fieldnamePrefix
1243
         *  the prefix to apply to the display of this.
1244
         * @param string $fieldnamePostfix
1245
         *  the suffix to apply to the display of this.
1246
         * @throws InvalidArgumentException
1247
         */
1248
        public function displayDatasourceFilterPanel(
1249
            XMLElement &$wrapper,
1250
            $data = null,
1251
            $errors = null,
0 ignored issues
show
Unused Code introduced by
The parameter $errors is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1252
            $fieldnamePrefix = null,
1253
            $fieldnamePostfix = null
1254
        ) {
1255
            $wrapper->appendChild(new XMLElement('header',
1256
                '<h4>' . $this->get('label') . '</h4> <span>' . $this->name() . '</span>', array(
1257
                    'data-name' => $this->get('label') . ' (' . $this->name() . ')'
1258
                )));
1259
1260
            $label = Widget::Label(__('Value'));
1261
            $input = Widget::Input('fields[filter]' . ($fieldnamePrefix ? '[' . $fieldnamePrefix . ']' : '') . '[' . $this->get('id') . ']' . ($fieldnamePostfix ? '[' . $fieldnamePostfix . ']' : ''),
1262
                ($data ? General::sanitize($data) : null));
1263
            $input->setAttribute('autocomplete', 'off');
1264
            $input->setAttribute('data-search-types', 'parameters');
1265
            $input->setAttribute('data-trigger', '{$');
1266
            $label->appendChild($input);
1267
            $wrapper->appendChild($label);
1268
1269
            $this->displayFilteringOptions($wrapper);
1270
        }
1271
1272
        /**
1273
         * Inserts tags at the bottom of the filter panel
1274
         *
1275
         * @since Symphony 2.6.0
1276
         * @param XMLElement $wrapper
1277
         */
1278
        public function displayFilteringOptions(XMLElement &$wrapper)
1279
        {
1280
            // Add filter tags
1281
            $filterTags = new XMLElement('ul');
1282
            $filterTags->setAttribute('class', 'tags singular');
1283
            $filterTags->setAttribute('data-interactive', 'data-interactive');
1284
1285
            $filters = $this->fetchFilterableOperators();
1286
            foreach ($filters as $value) {
1287
                $item = new XMLElement('li', $value['title']);
1288
                $item->setAttribute('data-value', $value['filter']);
1289
1290
                if (isset($value['help'])) {
1291
                    $item->setAttribute('data-help', General::sanitize($value['help']));
1292
                }
1293
1294
                $filterTags->appendChild($item);
1295
            }
1296
            $wrapper->appendChild($filterTags);
1297
1298
            $help = new XMLElement('p');
1299
            $help->setAttribute('class', 'help');
1300
            $first = array_shift($filters);
1301
            $help->setValue($first['help']);
1302
            $wrapper->appendChild($help);
1303
        }
1304
1305
        /**
1306
         * Returns the keywords that this field supports for filtering. Note
1307
         * that no filter will do a simple 'straight' match on the value.
1308
         *
1309
         * @since Symphony 2.6.0
1310
         * @return array
1311
         */
1312
        public function fetchFilterableOperators()
1313
        {
1314
            return array(
1315
                array(
1316
                    'title' => 'is',
1317
                    'filter' => ' ',
1318
                    'help' => __('Find values that are an exact match for the given string.')
1319
                ),
1320
                array(
1321
                    'title' => 'contains',
1322
                    'filter' => 'regexp: ',
1323
                    'help' => __('Find values that match the given <a href="%s">MySQL regular expressions</a>.', array(
1324
                        'http://dev.mysql.com/doc/mysql/en/Regexp.html'
1325
                    ))
1326
                ),
1327
                array(
1328
                    'title' => 'does not contain',
1329
                    'filter' => 'not-regexp: ',
1330
                    'help' => __('Find values that do not match the given <a href="%s">MySQL regular expressions</a>.',
1331
                        array(
1332
                            'http://dev.mysql.com/doc/mysql/en/Regexp.html'
1333
                        ))
1334
                ),
1335
            );
1336
        }
1337
1338
        /**
1339
         * Default accessor for the includable elements of this field. This array
1340
         * will populate the `Datasource` included elements. Fields that have
1341
         * different modes will override this and add new items to the array.
1342
         * The Symphony convention is element_name : mode. Modes allow Fields to
1343
         * output different XML in datasources.
1344
         *
1345
         * @return array
1346
         *  the array of includable elements from this field.
1347
         */
1348
        public function fetchIncludableElements()
1349
        {
1350
            return array($this->get('element_name'));
1351
        }
1352
1353
        /**
1354
         * Construct the SQL statement fragments to use to retrieve the data of this
1355
         * field when utilized as a data source.
1356
         *
1357
         * @see toolkit.Datasource#__determineFilterType
1358
         * @param array $data
1359
         *  An array of the data that contains the values for the filter as specified
1360
         *  in the datasource editor. The value that is entered in the datasource editor
1361
         *  is made into an array by using + or , to separate the filter.
1362
         * @param string $joins
1363
         *  A string containing any table joins for the current SQL fragment. By default
1364
         *  Datasources will always join to the `tbl_entries` table, which has an alias of
1365
         *  `e`. This parameter is passed by reference.
1366
         * @param string $where
1367
         *  A string containing the WHERE conditions for the current SQL fragment. This
1368
         *  is passed by reference and is expected to be used to add additional conditions
1369
         *  specific to this field
1370
         * @param boolean $andOperation (optional)
1371
         *  This parameter defines whether the `$data` provided should be treated as
1372
         *  AND or OR conditions. This parameter will be set to true if $data used a
1373
         *  + to separate the values, otherwise it will be false. It is false by default.
1374
         * @return boolean
1375
         *  True if the construction of the SQL was successful, false otherwise.
1376
         */
1377
        public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false)
1378
        {
1379
            $field_id = $this->get('id');
1380
1381
            // REGEX filtering is a special case, and will only work on the first item
1382
            // in the array. You cannot specify multiple filters when REGEX is involved.
1383
            if (self::isFilterRegex($data[0])) {
1384
                $this->buildRegexSQL($data[0], array('value'), $joins, $where);
1385
1386
                // AND operation, iterates over `$data` and uses a new JOIN for
1387
                // every item.
1388
            } elseif ($andOperation) {
1389
                foreach ($data as $value) {
1390
                    $this->_key++;
1391
                    $value = $this->cleanValue($value);
1392
                    $joins .= "
1393
                    LEFT JOIN
1394
                        `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
1395
                        ON (e.id = t{$field_id}_{$this->_key}.entry_id)
1396
                ";
1397
                    $where .= "
1398
                    AND t{$field_id}_{$this->_key}.value = '{$value}'
1399
                ";
1400
                }
1401
1402
                // Default logic, this will use a single JOIN statement and collapse
1403
                // `$data` into a string to be used inconjuction with IN
1404
            } else {
1405
                foreach ($data as &$value) {
1406
                    $value = $this->cleanValue($value);
1407
                }
1408
1409
                $this->_key++;
1410
                $data = implode("', '", $data);
1411
                $joins .= "
1412
                LEFT JOIN
1413
                    `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
1414
                    ON (e.id = t{$field_id}_{$this->_key}.entry_id)
1415
            ";
1416
                $where .= "
1417
                AND t{$field_id}_{$this->_key}.value IN ('{$data}')
1418
            ";
1419
            }
1420
1421
            return true;
1422
        }
1423
1424
        /**
1425
         * Test whether the input string is a regular expression, by searching
1426
         * for the prefix of `regexp:` or `not-regexp:` in the given `$string`.
1427
         *
1428
         * @param string $string
1429
         *  The string to test.
1430
         * @return boolean
1431
         *  True if the string is prefixed with `regexp:` or `not-regexp:`, false otherwise.
1432
         */
1433
        protected static function isFilterRegex($string)
1434
        {
1435
            if (preg_match('/^regexp:/i', $string) || preg_match('/^not-?regexp:/i', $string)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return preg_match('/^reg...-?regexp:/i', $string);.
Loading history...
1436
                return true;
1437
            }
1438
1439
            return false;
1440
        }
1441
1442
        /**
1443
         * Builds a basic REGEXP statement given a `$filter`. This function supports
1444
         * `regexp:` or `not-regexp:`. Users should keep in mind this function
1445
         * uses MySQL patterns, not the usual PHP patterns, the syntax between these
1446
         * flavours differs at times.
1447
         *
1448
         * @since Symphony 2.3
1449
         * @link http://dev.mysql.com/doc/refman/5.5/en/regexp.html
1450
         * @param string $filter
1451
         *  The full filter, eg. `regexp: ^[a-d]`
1452
         * @param array $columns
1453
         *  The array of columns that need the given `$filter` applied to. The conditions
1454
         *  will be added using `OR`.
1455
         * @param string $joins
1456
         *  A string containing any table joins for the current SQL fragment. By default
1457
         *  Datasources will always join to the `tbl_entries` table, which has an alias of
1458
         *  `e`. This parameter is passed by reference.
1459
         * @param string $where
1460
         *  A string containing the WHERE conditions for the current SQL fragment. This
1461
         *  is passed by reference and is expected to be used to add additional conditions
1462
         *  specific to this field
1463
         */
1464
        public function buildRegexSQL($filter, array $columns, &$joins, &$where)
1465
        {
1466
            $this->_key++;
1467
            $field_id = $this->get('id');
1468
            $filter = $this->cleanValue($filter);
1469
1470
            if (preg_match('/^regexp:/i', $filter)) {
1471
                $pattern = preg_replace('/^regexp:\s*/i', null, $filter);
1472
                $regex = 'REGEXP';
1473
            } else {
1474
                $pattern = preg_replace('/^not-?regexp:\s*/i', null, $filter);
1475
                $regex = 'NOT REGEXP';
1476
            }
1477
1478
            if (strlen($pattern) === 0) {
1479
                return;
1480
            }
1481
1482
            $joins .= "
1483
            LEFT JOIN
1484
                `tbl_entries_data_{$field_id}` AS t{$field_id}_{$this->_key}
1485
                ON (e.id = t{$field_id}_{$this->_key}.entry_id)
1486
        ";
1487
1488
            $where .= "AND ( ";
1489
1490
            foreach ($columns as $key => $col) {
1491
                $modifier = ($key === 0) ? '' : 'OR';
1492
1493
                $where .= "
1494
                {$modifier} t{$field_id}_{$this->_key}.{$col} {$regex} '{$pattern}'
1495
            ";
1496
            }
1497
            $where .= ")";
1498
        }
1499
1500
        /**
1501
         * Clean the input value using html entity encode and the database specific
1502
         * clean methods.
1503
         *
1504
         * @param mixed $value
1505
         *  the value to clean.
1506
         * @return string
1507
         *  the cleaned value.
1508
         */
1509
        public function cleanValue($value)
1510
        {
1511
            return html_entity_decode(Symphony::Database()->cleanValue($value));
1512
        }
1513
1514
        /**
1515
         * Build the SQL command to append to the default query to enable
1516
         * sorting of this field. By default this will sort the results by
1517
         * the entry id in ascending order.
1518
         *
1519
         * @param string $joins
1520
         *  the join element of the query to append the custom join sql to.
1521
         * @param string $where
1522
         *  the where condition of the query to append to the existing where clause.
1523
         * @param string $sort
1524
         *  the existing sort component of the sql query to append the custom
1525
         *  sort sql code to.
1526
         * @param string $order (optional)
1527
         *  an optional sorting direction. this defaults to ascending. if this
1528
         *  is declared either 'random' or 'rand' then a random sort is applied.
1529
         */
1530 View Code Duplication
        public function buildSortingSQL(&$joins, &$where, &$sort, $order = 'ASC')
0 ignored issues
show
Unused Code introduced by
The parameter $where is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1531
        {
1532
            if (in_array(strtolower($order), array('random', 'rand'))) {
1533
                $sort = 'ORDER BY RAND()';
1534
            } else {
1535
                $joins .= "LEFT OUTER JOIN `tbl_entries_data_" . $this->get('id') . "` AS `ed` ON (`e`.`id` = `ed`.`entry_id`) ";
1536
                $sort = sprintf('ORDER BY `ed`.`value` %s', $order);
1537
            }
1538
        }
1539
1540
        /**
1541
         * Default implementation of record grouping. This default implementation
1542
         * will throw an `Exception`. Thus, clients must overload this method
1543
         * for grouping to be successful.
1544
         *
1545
         * @throws Exception
1546
         * @param array $records
1547
         *  the records to group.
1548
         */
1549
        public function groupRecords($records)
1550
        {
1551
            throw new Exception(
1552
                __('Data source output grouping is not supported by the %s field',
1553
                    array('<code>' . $this->get('label') . '</code>'))
1554
            );
1555
        }
1556
1557
        /**
1558
         * Function to format this field if it chosen in a data-source to be
1559
         * output as a parameter in the XML.
1560
         *
1561
         * Since Symphony 2.5.0, it will defaults to `prepareReadableValue` return value.
1562
         *
1563
         * @param array $data
1564
         *  The data for this field from it's `tbl_entry_data_{id}` table
1565
         * @param integer $entry_id
1566
         *  The optional id of this field entry instance
1567
         * @return string|array
1568
         *  The formatted value to be used as the parameter. Note that this can be
1569
         *  an array or a string. When returning multiple values use array, otherwise
1570
         *  use string.
1571
         */
1572
        public function getParameterPoolValue(array $data, $entry_id = null)
1573
        {
1574
            return $this->prepareReadableValue($data, $entry_id);
1575
        }
1576
1577
        /**
1578
         * Append the formatted XML output of this field as utilized as a data source.
1579
         *
1580
         * Since Symphony 2.5.0, it will defaults to `prepareReadableValue` return value.
1581
         *
1582
         * @param XMLElement $wrapper
1583
         *  the XML element to append the XML representation of this to.
1584
         * @param array $data
1585
         *  the current set of values for this field. the values are structured as
1586
         *  for displayPublishPanel.
1587
         * @param boolean $encode (optional)
1588
         *  flag as to whether this should be html encoded prior to output. this
1589
         *  defaults to false.
1590
         * @param string $mode
1591
         *   A field can provide ways to output this field's data. For instance a mode
1592
         *  could be 'items' or 'full' and then the function would display the data
1593
         *  in a different way depending on what was selected in the datasource
1594
         *  included elements.
1595
         * @param integer $entry_id (optional)
1596
         *  the identifier of this field entry instance. defaults to null.
1597
         */
1598
        public function appendFormattedElement(
1599
            XMLElement &$wrapper,
1600
            $data,
1601
            $encode = false,
1602
            $mode = null,
1603
            $entry_id = null
1604
        ) {
1605
            $wrapper->appendChild(new XMLElement($this->get('element_name'), ($encode ?
1606
                General::sanitize($this->prepareReadableValue($data, $entry_id)) :
1607
                $this->prepareReadableValue($data, $entry_id))));
1608
        }
1609
1610
        /**
1611
         * The default method for constructing the example form markup containing this
1612
         * field when utilized as part of an event. This displays in the event documentation
1613
         * and serves as a basic guide for how markup should be constructed on the
1614
         * `Frontend` to save this field
1615
         *
1616
         * @throws InvalidArgumentException
1617
         * @return XMLElement
1618
         *  a label widget containing the formatted field element name of this.
1619
         */
1620 View Code Duplication
        public function getExampleFormMarkup()
1621
        {
1622
            $label = Widget::Label($this->get('label'));
1623
            $label->appendChild(Widget::Input('fields[' . $this->get('element_name') . ']'));
1624
1625
            return $label;
1626
        }
1627
1628
        /**
1629
         * Commit the settings of this field from the section editor to
1630
         * create an instance of this field in a section.
1631
         *
1632
         * @return boolean
1633
         *  true if the commit was successful, false otherwise.
1634
         */
1635
        public function commit()
1636
        {
1637
            $fields = array();
1638
1639
            $fields['label'] = General::sanitize($this->get('label'));
1640
            $fields['element_name'] = ($this->get('element_name') ? $this->get('element_name') : Lang::createHandle($this->get('label')));
1641
            $fields['parent_section'] = $this->get('parent_section');
1642
            $fields['location'] = $this->get('location');
1643
            $fields['required'] = $this->get('required');
1644
            $fields['type'] = $this->_handle;
1645
            $fields['show_column'] = $this->get('show_column');
1646
            $fields['sortorder'] = (string)$this->get('sortorder');
1647
1648
            if ($id = $this->get('id')) {
1649
                return FieldManager::edit($id, $fields);
1650
            } elseif ($id = FieldManager::add($fields)) {
1651
                $this->set('id', $id);
1652
                $this->createTable();
1653
1654
                return true;
1655
            }
1656
1657
            return false;
1658
        }
1659
1660
        /**
1661
         * The default field table construction method. This constructs the bare
1662
         * minimum set of columns for a valid field table. Subclasses are expected
1663
         * to overload this method to create a table structure that contains
1664
         * additional columns to store the specific data created by the field.
1665
         *
1666
         * @throws DatabaseException
1667
         * @return boolean
1668
         */
1669
        public function createTable()
1670
        {
1671
            return Symphony::Database()->query(
1672
                "CREATE TABLE IF NOT EXISTS `tbl_entries_data_" . $this->get('id') . "` (
1673
              `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
1674
              `entry_id` INT(11) UNSIGNED NOT NULL,
1675
              `value` VARCHAR(255) DEFAULT NULL,
1676
              PRIMARY KEY  (`id`),
1677
              KEY `entry_id` (`entry_id`),
1678
              KEY `value` (`value`)
1679
            );"
1680
            );
1681
        }
1682
1683
        /**
1684
         * Remove the entry data of this field from the database.
1685
         *
1686
         * @param integer|array $entry_id
1687
         *    the ID of the entry, or an array of entry ID's to delete.
1688
         * @param array $data (optional)
1689
         *    The entry data provided for fields to do additional cleanup
1690
         *  This is an optional argument and defaults to null.
1691
         * @throws DatabaseException
1692
         * @return boolean
1693
         *    Returns true after the cleanup has been completed
1694
         */
1695
        public function entryDataCleanup($entry_id, $data = null)
1696
        {
1697
            Symphony::Database()->delete('tbl_entries_data_' . $this->get('id'), "`entry_id` IN (?)", array($entry_id));
1698
1699
            return true;
1700
        }
1701
1702
        /**
1703
         * Accessor to the associated entry search value for this field
1704
         * instance. This default implementation simply returns `$data`
1705
         *
1706
         * @param array $data
1707
         *  the data from which to construct the associated search entry value, this is usually
1708
         *  Entry with the `$parent_entry_id` value's data.
1709
         * @param integer $field_id (optional)
1710
         *  the ID of the field that is the parent in the relationship
1711
         * @param integer $parent_entry_id (optional)
1712
         *  the ID of the entry from the parent section in the relationship
1713
         * @return array|string
1714
         *  Defaults to returning `$data`, but overriding implementation should return
1715
         *  a string
1716
         */
1717
        public function fetchAssociatedEntrySearchValue($data, $field_id = null, $parent_entry_id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $field_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $parent_entry_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1718
        {
1719
            return $data;
1720
        }
1721
1722
        /**
1723
         * Fetch the count of the associated entries given a `$value`.
1724
         *
1725
         * @see toolkit.Field#fetchAssociatedEntrySearchValue()
1726
         * @param mixed $value
1727
         *  the value to find the associated entry count for, this usually comes from
1728
         *  the `fetchAssociatedEntrySearchValue` function.
1729
         * @return void|integer
1730
         *  this default implementation returns void. overriding implementations should
1731
         *  return an integer.
1732
         */
1733
        public function fetchAssociatedEntryCount($value)
1734
        {
1735
        }
1736
1737
        /**
1738
         * Find related entries from a linking field's data table. Default implementation uses
1739
         * column names `entry_id` and `relation_id` as with the Select Box Link
1740
         *
1741
         * @since Symphony 2.5.0
1742
         *
1743
         * @param  integer $entry_id
1744
         * @param  integer $parent_field_id
1745
         * @return array
1746
         */
1747 View Code Duplication
        public function findRelatedEntries($entry_id, $parent_field_id)
1748
        {
1749
            try {
1750
                $ids = Symphony::Database()->fetchCol('entry_id', sprintf("
1751
                SELECT `entry_id`
1752
                FROM `tbl_entries_data_%d`
1753
                WHERE `relation_id` = %d
1754
                AND `entry_id` IS NOT NULL
1755
            ", $this->get('id'), $entry_id));
1756
            } catch (Exception $e) {
1757
                return array();
1758
            }
1759
1760
            return $ids;
1761
        }
1762
1763
        /**
1764
         * Find related entries for the current field. Default implementation uses
1765
         * column names `entry_id` and `relation_id` as with the Select Box Link
1766
         *
1767
         * @since Symphony 2.5.0
1768
         *
1769
         * @param  integer $field_id
1770
         * @param  integer $entry_id
1771
         * @return array
1772
         */
1773 View Code Duplication
        public function findParentRelatedEntries($field_id, $entry_id)
1774
        {
1775
            try {
1776
                $ids = Symphony::Database()->fetchCol('relation_id', sprintf("
1777
                SELECT `relation_id`
1778
                FROM `tbl_entries_data_%d`
1779
                WHERE `entry_id` = %d
1780
                AND `relation_id` IS NOT NULL
1781
            ", $field_id, $entry_id));
1782
            } catch (Exception $e) {
1783
                return array();
1784
            }
1785
1786
            return $ids;
1787
        }
1788
    }
1789