Passed
Push — master ( 444517...d24400 )
by Robbie
02:30
created

StringTagField::dataValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace SilverStripe\TagField;
4
5
use Iterator;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Forms\DropdownField;
10
use SilverStripe\Forms\Validator;
11
use SilverStripe\ORM\ArrayList;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\ORM\DataObjectInterface;
14
use SilverStripe\ORM\SS_List;
15
use SilverStripe\View\ArrayData;
16
use SilverStripe\View\Requirements;
17
18
/**
19
 * Provides a tagging interface, storing comma-delimited tags in a DataObject string field.
20
 *
21
 * This is intended bridge the gap between 1.x and 2.x, and when possible TagField should be used
22
 * instead.
23
 *
24
 * @package    tagfield
25
 * @subpackage fields
26
 */
27
class StringTagField extends DropdownField
28
{
29
    /**
30
     * @var array
31
     */
32
    private static $allowed_actions = [
33
        'suggest',
34
    ];
35
36
    /**
37
     * @var bool
38
     */
39
    protected $shouldLazyLoad = false;
40
41
    /**
42
     * @var int
43
     */
44
    protected $lazyLoadItemLimit = 10;
45
46
    /**
47
     * @var bool
48
     */
49
    protected $canCreate = true;
50
51
    /**
52
     * @var null|DataObject
53
     */
54
    protected $record;
55
56
    /**
57
     * @var bool
58
     */
59
    protected $isMultiple = true;
60
61
    /**
62
     * @return bool
63
     */
64
    public function getShouldLazyLoad()
65
    {
66
        return $this->shouldLazyLoad;
67
    }
68
69
    /**
70
     * @param bool $shouldLazyLoad
71
     * @return $this
72
     */
73
    public function setShouldLazyLoad($shouldLazyLoad)
74
    {
75
        $this->shouldLazyLoad = $shouldLazyLoad;
76
77
        return $this;
78
    }
79
80
    /**
81
     * @return int
82
     */
83
    public function getLazyLoadItemLimit()
84
    {
85
        return $this->lazyLoadItemLimit;
86
    }
87
88
    /**
89
     * @param int $lazyLoadItemLimit
90
     * @return $this
91
     */
92
    public function setLazyLoadItemLimit($lazyLoadItemLimit)
93
    {
94
        $this->lazyLoadItemLimit = $lazyLoadItemLimit;
95
96
        return $this;
97
    }
98
99
    /**
100
     * @return bool
101
     */
102
    public function getIsMultiple()
103
    {
104
        return $this->isMultiple;
105
    }
106
107
    /**
108
     * @param bool $isMultiple
109
     * @return $this
110
     */
111
    public function setIsMultiple($isMultiple)
112
    {
113
        $this->isMultiple = $isMultiple;
114
115
        return $this;
116
    }
117
118
    /**
119
     * @return null|DataObject
120
     */
121
    public function getRecord()
122
    {
123
        if ($this->record) {
124
            return $this->record;
125
        }
126
127
        if ($form = $this->getForm()) {
128
            return $form->getRecord();
129
        }
130
131
        return null;
132
    }
133
134
    /**
135
     * @param DataObject $record
136
     * @return $this
137
     */
138
    public function setRecord(DataObject $record)
139
    {
140
        $this->record = $record;
141
142
        return $this;
143
    }
144
145
    public function Field($properties = [])
146
    {
147
        $this->addExtraClass('ss-tag-field');
148
149
        return $this
150
            ->customise($properties)
151
            ->renderWith(TagField::class);
152
    }
153
154
    /**
155
     * Provide TagField data to the JSON schema for the frontend component
156
     *
157
     * @return array
158
     */
159
    public function getSchemaDataDefaults()
160
    {
161
        $schema = array_merge(
162
            parent::getSchemaDataDefaults(),
163
            [
164
                'name' => $this->getName() . '[]',
165
                'lazyLoad' => $this->getShouldLazyLoad(),
166
                'creatable' => $this->getCanCreate(),
167
                'multi' => $this->getIsMultiple(),
168
                'value' => $this->formatOptions($this->Value()),
169
                'disabled' => $this->isDisabled() || $this->isReadonly(),
170
            ]
171
        );
172
173
        if (!$this->getShouldLazyLoad()) {
174
            $schema['options'] = $this->getOptions()->toNestedArray();
175
        } else {
176
            $schema['optionUrl'] = $this->getSuggestURL();
177
        }
178
179
        return $schema;
180
    }
181
182
    protected function formatOptions($fieldValue)
183
    {
184
        if (empty($fieldValue)) {
185
            return [];
186
        }
187
188
        $formattedValue = [];
189
        foreach ($fieldValue as $value) {
190
            $formattedValue[] = [
191
                'Title' => $value,
192
                'Value' => $value,
193
            ];
194
        }
195
        return $formattedValue;
196
    }
197
198
    /**
199
     * When not used in a React form factory context, this adds the schema data to SilverStripe template
200
     * rendered attributes lists
201
     *
202
     * @return array
203
     */
204
    public function getAttributes()
205
    {
206
        $attributes = parent::getAttributes();
207
        $attributes['data-schema'] = json_encode($this->getSchemaData());
208
        return $attributes;
209
    }
210
211
    /**
212
     * @return string
213
     */
214
    protected function getSuggestURL()
215
    {
216
        return Controller::join_links($this->Link(), 'suggest');
217
    }
218
219
    /**
220
     * @return ArrayList
221
     */
222
    protected function getOptions()
223
    {
224
        $options = ArrayList::create();
225
226
        $source = $this->getSource();
227
228
        if ($source instanceof Iterator) {
229
            $source = iterator_to_array($source);
230
        }
231
232
        foreach ($source as $value) {
233
            $options->push(
234
                ArrayData::create([
235
                    'Title' => $value,
236
                    'Value' => $value,
237
                ])
238
            );
239
        }
240
241
        return $options;
242
    }
243
244
    public function setValue($value, $source = null)
245
    {
246
        if (is_string($value)) {
247
            $value = explode(',', $value);
248
        }
249
250
        if ($source instanceof DataObject) {
251
            $name = $this->getName();
252
            $value = explode(',', $source->$name);
253
        }
254
255
        if ($source instanceof SS_List) {
256
            $value = $source->column('ID');
257
        }
258
259
        if ($value === null) {
260
            $value = [];
261
        }
262
263
        return parent::setValue(array_filter($value));
264
    }
265
266
    public function saveInto(DataObjectInterface $record)
267
    {
268
        parent::saveInto($record);
269
270
        $name = $this->getName();
271
272
        $record->$name = $this->dataValue();
273
        $record->write();
274
    }
275
276
    /**
277
     * Ensure that arrays are imploded before being saved
278
     *
279
     * @return mixed|string
280
     */
281
    public function dataValue()
282
    {
283
        return implode(',', $this->value);
284
    }
285
286
    /**
287
     * Returns a JSON string of tags, for lazy loading.
288
     *
289
     * @param  HTTPRequest $request
290
     * @return HTTPResponse
291
     */
292
    public function suggest(HTTPRequest $request)
293
    {
294
        $responseBody = json_encode(
295
            ['items' => $this->getTags($request->getVar('term'))]
296
        );
297
298
        $response = HTTPResponse::create();
299
        $response->addHeader('Content-Type', 'application/json');
300
        $response->setBody($responseBody);
301
302
        return $response;
303
    }
304
305
    /**
306
     * Returns array of arrays representing tags that partially match the given search term
307
     *
308
     * @param string $term
309
     * @return array
310
     */
311
    protected function getTags($term)
312
    {
313
        $items = [];
314
        foreach ($this->getOptions() as $i => $tag) {
315
            /** @var ArrayData $tag */
316
            $tagValue = $tag->Value;
317
            // Map into a distinct list (prevent duplicates)
318
            if (stripos($tagValue, $term) !== false && !array_key_exists($tagValue, $items)) {
319
                $items[$tagValue] = [
320
                    'id' => $tag->Title,
321
                    'text' => $tag->Value,
322
                ];
323
            }
324
        }
325
        // @todo do we actually need lazy loading limits for StringTagField?
326
        return array_slice(array_values($items), 0, $this->getLazyLoadItemLimit());
327
    }
328
329
    /**
330
     * DropdownField assumes value will be a scalar so we must
331
     * override validate. This only applies to Silverstripe 3.2+
332
     *
333
     * @param Validator $validator
334
     * @return bool
335
     */
336
    public function validate($validator)
337
    {
338
        return true;
339
    }
340
341
    /**
342
     * @return bool
343
     */
344
    public function getCanCreate()
345
    {
346
        return $this->canCreate;
347
    }
348
349
    /**
350
     * @param bool $canCreate
351
     * @return $this
352
     */
353
    public function setCanCreate($canCreate)
354
    {
355
        $this->canCreate = $canCreate;
356
357
        return $this;
358
    }
359
}
360