Completed
Pull Request — master (#113)
by
unknown
03:30
created

TagField::getIsMultiple()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\TagField;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\HTTPResponse;
8
use SilverStripe\Core\Convert;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\Forms\DropdownField;
11
use SilverStripe\ORM\ArrayList;
12
use SilverStripe\ORM\DataList;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\ORM\DataObjectInterface;
15
use SilverStripe\ORM\SS_List;
16
use SilverStripe\View\ArrayData;
17
use SilverStripe\View\Requirements;
18
19
/**
20
 * Provides a tagging interface, storing links between tag DataObjects and a parent DataObject.
21
 *
22
 * @package forms
23
 * @subpackage fields
24
 */
25
class TagField extends DropdownField
26
{
27
    /**
28
     * @var array
29
     */
30
    private static $allowed_actions = array(
31
        'suggest'
32
    );
33
34
    /**
35
     * @var bool
36
     */
37
    protected $shouldLazyLoad = false;
38
39
    /**
40
     * @var int
41
     */
42
    protected $lazyLoadItemLimit = 10;
43
44
    /**
45
     * @var bool
46
     */
47
    protected $canCreate = true;
48
49
    /**
50
     * @var string
51
     */
52
    protected $titleField = 'Title';
53
54
    /**
55
     * @var DataList
56
     */
57
    protected $sourceList;
58
59
    /**
60
     * @var bool
61
     */
62
    protected $isMultiple = true;
63
64
    /**
65
     * @param string $name
66
     * @param string $title
67
     * @param null|DataList $source
68
     * @param null|DataList $value
69
     * @param string $titleField
70
     */
71
    public function __construct($name, $title = '', $source = [], $value = null, $titleField = 'Title')
72
    {
73
        $this->setSourceList($source);
74
        $this->setTitleField($titleField);
75
        parent::__construct($name, $title, $source, $value);
76
    }
77
78
    /**
79
     * @return bool
80
     */
81
    public function getShouldLazyLoad()
82
    {
83
        return $this->shouldLazyLoad;
84
    }
85
86
    /**
87
     * @param bool $shouldLazyLoad
88
     *
89
     * @return static
90
     */
91
    public function setShouldLazyLoad($shouldLazyLoad)
92
    {
93
        $this->shouldLazyLoad = $shouldLazyLoad;
94
95
        return $this;
96
    }
97
98
    /**
99
     * @return int
100
     */
101
    public function getLazyLoadItemLimit()
102
    {
103
        return $this->lazyLoadItemLimit;
104
    }
105
106
    /**
107
     * @param int $lazyLoadItemLimit
108
     *
109
     * @return static
110
     */
111
    public function setLazyLoadItemLimit($lazyLoadItemLimit)
112
    {
113
        $this->lazyLoadItemLimit = $lazyLoadItemLimit;
114
115
        return $this;
116
    }
117
118
    /**
119
     * @return bool
120
     */
121
    public function getIsMultiple()
122
    {
123
        return $this->isMultiple;
124
    }
125
126
    /**
127
     * @param bool $isMultiple
128
     *
129
     * @return static
130
     */
131
    public function setIsMultiple($isMultiple)
132
    {
133
        $this->isMultiple = $isMultiple;
134
135
        return $this;
136
    }
137
138
    /**
139
     * @return bool
140
     */
141
    public function getCanCreate()
142
    {
143
        return $this->canCreate;
144
    }
145
146
    /**
147
     * @param bool $canCreate
148
     *
149
     * @return static
150
     */
151
    public function setCanCreate($canCreate)
152
    {
153
        $this->canCreate = $canCreate;
154
155
        return $this;
156
    }
157
158
    /**
159
     * @return string
160
     */
161
    public function getTitleField()
162
    {
163
        return $this->titleField;
164
    }
165
166
    /**
167
     * @param string $titleField
168
     *
169
     * @return $this
170
     */
171
    public function setTitleField($titleField)
172
    {
173
        $this->titleField = $titleField;
174
175
        return $this;
176
    }
177
178
    /**
179
     * Get the DataList source. The 4.x upgrade for SelectField::setSource starts to convert this to an array
180
     * @return DataList
181
     */
182
    public function getSourceList()
183
    {
184
        return $this->sourceList;
185
    }
186
187
    /**
188
     * Set the model class name for tags
189
     * @param  DataList $className
190
     * @return self
191
     */
192
    public function setSourceList($sourceList)
193
    {
194
        $this->sourceList = $sourceList;
195
        return $this;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function Field($properties = array())
202
    {
203
        Requirements::css('silverstripe/tagfield:client/dist/styles/bundle.css');
204
        Requirements::javascript('silverstripe/tagfield:client/dist/js/bundle.js');
205
206
        $schema = [
207
            'name' => $this->getName() . '[]',
208
            'lazyLoad' => $this->getShouldLazyLoad(),
209
            'creatable' => $this->getCanCreate(),
210
            'multiple' => $this->getIsMultiple(),
211
            'value' => $this->Value(),
212
            'disabled' => $this->isDisabled() || $this->isReadonly(),
213
        ];
214
        if (!$this->getShouldLazyLoad()) {
215
            $schema['options'] = array_values($this->getOptions()->toNestedArray());
216
        }
217
        else {
218
            if ($this->Value()) {
219
                $schema['value'] = $this->getOptions(true)->toNestedArray();
220
            }
221
            $schema['optionUrl'] = $this->getSuggestURL();
222
        }
223
        $this->setAttribute('data-schema', Convert::array2json($schema));
224
225
        $this->addExtraClass('ss-tag-field');
226
227
        return $this
228
            ->customise($properties)
229
            ->renderWith(self::class);
230
    }
231
232
    /**
233
     * @return string
234
     */
235
    protected function getSuggestURL()
236
    {
237
        return Controller::join_links($this->Link(), 'suggest');
238
    }
239
240
    /**
241
     * @param bool $onlySelected Only return options that are selected
242
     * @return ArrayList
243
     */
244
    protected function getOptions($onlySelected = false)
245
    {
246
        $source = $this->getSourceList();
247
248
        if (!$source) {
0 ignored issues
show
introduced by
$source is of type SilverStripe\ORM\DataList, thus it always evaluated to true.
Loading history...
249
            $source = ArrayList::create();
250
        }
251
252
        $dataClass = $source->dataClass();
253
        $titleField = $this->getTitleField();
254
        $values = $this->Value();
255
256
        if ($values) {
257
            if (is_array($values)) {
258
                $values = DataList::create($dataClass)->filter($titleField, $values);
259
            }
260
        }
261
        if ($onlySelected) {
262
            $source = $values;
263
        }
264
265
        return $source instanceof DataList ? $this->formatOptions($source) : ArrayList::create();
266
    }
267
268
    /**
269
     * @param DataList $source
270
     * @return ArrayList
271
     */
272
    protected function formatOptions(DataList $source)
273
    {
274
        $options = ArrayList::create();
275
        $titleField = $this->getTitleField();
276
277
        foreach ($source as $object) {
278
            $options->push(
279
                ArrayData::create(array(
280
                    'Title' => $object->$titleField,
281
                    'Value' => $object->Title,
282
                ))
283
            );
284
        }
285
286
        return $options;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292
    public function setValue($value, $source = null)
293
    {
294
        if ($source instanceof DataObject) {
295
            $name = $this->getName();
296
297
            if ($source->hasMethod($name)) {
298
                $value = $source->$name()->column($this->getTitleField());
299
            }
300
        } elseif ($value instanceof SS_List) {
301
            $value = $value->column($this->getTitleField());
302
        }
303
304
        if (!is_array($value)) {
305
            return parent::setValue($value);
306
        }
307
308
        return parent::setValue(array_filter($value));
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     */
314
    public function saveInto(DataObjectInterface $record)
315
    {
316
        parent::saveInto($record);
317
318
        $name = $this->getName();
319
        $titleField = $this->getTitleField();
320
        $source = $this->getSource();
0 ignored issues
show
Unused Code introduced by
The assignment to $source is dead and can be removed.
Loading history...
321
        $values = $this->Value();
322
        $relation = $record->$name();
323
        $ids = array();
324
325
        if (!$values) {
326
            $values = array();
327
        }
328
        if (empty($record) || empty($titleField)) {
329
            return;
330
        }
331
332
        if (!$record->hasMethod($name)) {
333
            throw new Exception(
0 ignored issues
show
Bug introduced by
The type SilverStripe\TagField\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
334
                sprintf("%s does not have a %s method", get_class($record), $name)
335
            );
336
        }
337
338
        foreach ($values as $key => $value) {
339
            // Get or create record
340
            $record = $this->getOrCreateTag($value);
341
            if ($record) {
342
                $ids[] = $record->ID;
343
                $values[$key] = $record->Title;
344
            }
345
        }
346
347
        $relation->setByIDList(array_filter($ids));
348
    }
349
350
    /**
351
     * Get or create tag with the given value
352
     *
353
     * @param  string $term
354
     * @return DataObject
355
     */
356
    protected function getOrCreateTag($term)
357
    {
358
        // Check if existing record can be found
359
        /** @var DataList $source */
360
        $source = $this->getSourceList();
361
        $titleField = $this->getTitleField();
362
        $record = $source
363
            ->filter($titleField, $term)
364
            ->first();
365
        if ($record) {
0 ignored issues
show
introduced by
$record is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
366
            return $record;
367
        }
368
369
        // Create new instance if not yet saved
370
        if ($this->getCanCreate()) {
371
            $dataClass = $source->dataClass();
372
            $record = Injector::inst()->create($dataClass);
373
            $record->{$titleField} = $term;
374
            $record->write();
375
            return $record;
376
        } else {
377
            return false;
378
        }
379
    }
380
381
    /**
382
     * Returns a JSON string of tags, for lazy loading.
383
     *
384
     * @param  HTTPRequest $request
385
     * @return HTTPResponse
386
     */
387
    public function suggest(HTTPRequest $request)
388
    {
389
        $tags = $this->getTags($request->getVar('term'));
390
391
        $response = new HTTPResponse();
392
        $response->addHeader('Content-Type', 'application/json');
393
        $response->setBody(json_encode(array('items' => $tags)));
394
395
        return $response;
396
    }
397
398
    /**
399
     * Returns array of arrays representing tags.
400
     *
401
     * @param  string $term
402
     * @return array
403
     */
404
    protected function getTags($term)
405
    {
406
        /**
407
         * @var array $source
408
         */
409
        $source = $this->getSourceList();
410
411
        $titleField = $this->getTitleField();
412
413
        $query = $source
414
            ->filter($titleField . ':PartialMatch:nocase', $term)
415
            ->sort($titleField)
416
            ->limit($this->getLazyLoadItemLimit());
417
418
        return $this->formatOptions($query)->toNestedArray();
419
    }
420
421
    /**
422
     * DropdownField assumes value will be a scalar so we must
423
     * override validate. This only applies to Silverstripe 3.2+
424
     *
425
     * @param Validator $validator
0 ignored issues
show
Bug introduced by
The type SilverStripe\TagField\Validator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
426
     * @return bool
427
     */
428
    public function validate($validator)
429
    {
430
        return true;
431
    }
432
433
    /**
434
     * Converts the field to a readonly variant.
435
     *
436
     * @return TagField_Readonly
0 ignored issues
show
Bug introduced by
The type SilverStripe\TagField\TagField_Readonly was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
437
     */
438
    public function performReadonlyTransformation()
439
    {
440
        $copy = $this->castedCopy(ReadonlyTagField::class);
441
        $copy->setSourceList($this->getSourceList());
442
        return $copy;
443
    }
444
}
445