Completed
Push — master ( bc7404...58edb1 )
by Robbie
14s queued 11s
created
src/TagField.php 2 patches
Indentation   +480 added lines, -480 removed lines patch added patch discarded remove patch
@@ -23,484 +23,484 @@
 block discarded – undo
23 23
  */
24 24
 class TagField extends MultiSelectField
25 25
 {
26
-    /**
27
-     * @var array
28
-     */
29
-    private static $allowed_actions = [
30
-        'suggest',
31
-    ];
32
-
33
-    /**
34
-     * @var bool
35
-     */
36
-    protected $shouldLazyLoad = false;
37
-
38
-    /**
39
-     * @var int
40
-     */
41
-    protected $lazyLoadItemLimit = 10;
42
-
43
-    /**
44
-     * @var bool
45
-     */
46
-    protected $canCreate = true;
47
-
48
-    /**
49
-     * @var string
50
-     */
51
-    protected $titleField = 'Title';
52
-
53
-    /**
54
-     * @var DataList
55
-     */
56
-    protected $sourceList;
57
-
58
-    /**
59
-     * @var bool
60
-     */
61
-    protected $isMultiple = true;
62
-
63
-    /** @skipUpgrade */
64
-    protected $schemaComponent = 'TagField';
65
-
66
-    /**
67
-     * @param string $name
68
-     * @param string $title
69
-     * @param null|DataList $source
70
-     * @param null|DataList $value
71
-     * @param string $titleField
72
-     */
73
-    public function __construct($name, $title = '', $source = [], $value = null, $titleField = 'Title')
74
-    {
75
-        $this->setSourceList($source);
76
-        $this->setTitleField($titleField);
77
-        parent::__construct($name, $title, $source, $value);
78
-
79
-        $this->addExtraClass('ss-tag-field');
80
-    }
81
-
82
-    /**
83
-     * @return bool
84
-     */
85
-    public function getShouldLazyLoad()
86
-    {
87
-        return $this->shouldLazyLoad;
88
-    }
89
-
90
-    /**
91
-     * @param bool $shouldLazyLoad
92
-     *
93
-     * @return static
94
-     */
95
-    public function setShouldLazyLoad($shouldLazyLoad)
96
-    {
97
-        $this->shouldLazyLoad = $shouldLazyLoad;
98
-
99
-        return $this;
100
-    }
101
-
102
-    /**
103
-     * @return int
104
-     */
105
-    public function getLazyLoadItemLimit()
106
-    {
107
-        return $this->lazyLoadItemLimit;
108
-    }
109
-
110
-    /**
111
-     * @param int $lazyLoadItemLimit
112
-     *
113
-     * @return static
114
-     */
115
-    public function setLazyLoadItemLimit($lazyLoadItemLimit)
116
-    {
117
-        $this->lazyLoadItemLimit = $lazyLoadItemLimit;
118
-
119
-        return $this;
120
-    }
121
-
122
-    /**
123
-     * @return bool
124
-     */
125
-    public function getIsMultiple()
126
-    {
127
-        return $this->isMultiple;
128
-    }
129
-
130
-    /**
131
-     * @param bool $isMultiple
132
-     *
133
-     * @return static
134
-     */
135
-    public function setIsMultiple($isMultiple)
136
-    {
137
-        $this->isMultiple = $isMultiple;
138
-
139
-        return $this;
140
-    }
141
-
142
-    /**
143
-     * @return bool
144
-     */
145
-    public function getCanCreate()
146
-    {
147
-        return $this->canCreate;
148
-    }
149
-
150
-    /**
151
-     * @param bool $canCreate
152
-     *
153
-     * @return static
154
-     */
155
-    public function setCanCreate($canCreate)
156
-    {
157
-        $this->canCreate = $canCreate;
158
-
159
-        return $this;
160
-    }
161
-
162
-    /**
163
-     * @return string
164
-     */
165
-    public function getTitleField()
166
-    {
167
-        return $this->titleField;
168
-    }
169
-
170
-    /**
171
-     * @param string $titleField
172
-     *
173
-     * @return $this
174
-     */
175
-    public function setTitleField($titleField)
176
-    {
177
-        $this->titleField = $titleField;
178
-
179
-        return $this;
180
-    }
181
-
182
-    /**
183
-     * Get the DataList source. The 4.x upgrade for SelectField::setSource starts to convert this to an array
184
-     * @return DataList
185
-     */
186
-    public function getSourceList()
187
-    {
188
-        return $this->sourceList;
189
-    }
190
-
191
-    /**
192
-     * Set the model class name for tags
193
-     * @param  DataList $className
194
-     * @return self
195
-     */
196
-    public function setSourceList($sourceList)
197
-    {
198
-        $this->sourceList = $sourceList;
199
-        return $this;
200
-    }
201
-
202
-    /**
203
-     * {@inheritdoc}
204
-     */
205
-    public function Field($properties = [])
206
-    {
207
-        $this->addExtraClass('entwine');
208
-
209
-        return $this->customise($properties)->renderWith(self::class);
210
-    }
211
-
212
-    /**
213
-     * Provide TagField data to the JSON schema for the frontend component
214
-     *
215
-     * @return array
216
-     */
217
-    public function getSchemaDataDefaults()
218
-    {
219
-        $options = $this->getOptions(true);
220
-        $schema = array_merge(
221
-            parent::getSchemaDataDefaults(),
222
-            [
223
-                'name' => $this->getName() . '[]',
224
-                'lazyLoad' => $this->getShouldLazyLoad(),
225
-                'creatable' => $this->getCanCreate(),
226
-                'multi' => $this->getIsMultiple(),
227
-                'value' => $options->count() ? $options->toNestedArray() : null,
228
-                'disabled' => $this->isDisabled() || $this->isReadonly(),
229
-            ]
230
-        );
231
-
232
-        if (!$this->getShouldLazyLoad()) {
233
-            $schema['options'] = array_values($this->getOptions()->toNestedArray());
234
-        } else {
235
-            $schema['optionUrl'] = $this->getSuggestURL();
236
-        }
237
-
238
-        return $schema;
239
-    }
240
-
241
-    /**
242
-     * @return string
243
-     */
244
-    protected function getSuggestURL()
245
-    {
246
-        return Controller::join_links($this->Link(), 'suggest');
247
-    }
248
-
249
-    /**
250
-     * @return ArrayList
251
-     */
252
-    protected function getOptions($onlySelected = false)
253
-    {
254
-        $options = ArrayList::create();
255
-        $source = $this->getSourceList();
256
-
257
-        // No source means we have no options
258
-        if (!$source) {
259
-            return ArrayList::create();
260
-        }
261
-
262
-        $dataClass = $source->dataClass();
263
-
264
-        $values = $this->Value();
265
-
266
-        // If we have no values and we only want selected options we can bail here
267
-        if (empty($values) && $onlySelected) {
268
-            return ArrayList::create();
269
-        }
270
-
271
-        // Convert an array of values into a datalist of options
272
-        if (is_array($values) && !empty($values)) {
273
-            $values = DataList::create($dataClass)
274
-                ->filter($this->getTitleField(), $values);
275
-        } else {
276
-            $values = ArrayList::create();
277
-        }
278
-
279
-        // Prep a function to parse a dataobject into an option
280
-        $addOption = function (DataObject $item) use ($options, $values) {
281
-            $titleField = $this->getTitleField();
282
-            $option = $item->$titleField;
283
-            $options->push(ArrayData::create([
284
-                'Title' => $option,
285
-                'Value' => $option,
286
-                'Selected' => (bool) $values->find('ID', $item->ID)
287
-            ]));
288
-        };
289
-
290
-        // Only parse the values if we only want the selected items in the values list (this is for lazy-loading)
291
-        if ($onlySelected) {
292
-            $values->each($addOption);
293
-            return $options;
294
-        }
295
-
296
-        $source->each($addOption);
297
-        return $options;
298
-    }
299
-
300
-    /**
301
-     * {@inheritdoc}
302
-     */
303
-    public function setValue($value, $source = null)
304
-    {
305
-        if ($source instanceof DataObject) {
306
-            $name = $this->getName();
307
-
308
-            if ($source->hasMethod($name)) {
309
-                $value = $source->$name()->column($this->getTitleField());
310
-            }
311
-        }
312
-
313
-        if (!is_array($value)) {
314
-            return parent::setValue($value);
315
-        }
316
-
317
-        return parent::setValue(array_filter($value));
318
-    }
319
-
320
-    /**
321
-     * {@inheritdoc}
322
-     */
323
-    public function getAttributes()
324
-    {
325
-        return array_merge(
326
-            parent::getAttributes(),
327
-            [
328
-                'name' => $this->getName() . '[]',
329
-                'style' => 'width: 100%',
330
-                'data-schema' => json_encode($this->getSchemaData()),
331
-            ]
332
-        );
333
-    }
334
-
335
-    /**
336
-     * {@inheritdoc}
337
-     */
338
-    public function saveInto(DataObjectInterface $record)
339
-    {
340
-        $name = $this->getName();
341
-        $titleField = $this->getTitleField();
342
-        $values = $this->Value();
343
-        $relation = $record->$name();
344
-        $ids = [];
345
-
346
-        if (!$values) {
347
-            $values = [];
348
-        }
349
-
350
-        if (empty($record) || empty($titleField)) {
351
-            return;
352
-        }
353
-
354
-        if (!$record->hasMethod($name)) {
355
-            throw new Exception(
356
-                sprintf("%s does not have a %s method", get_class($record), $name)
357
-            );
358
-        }
359
-
360
-        foreach ($values as $key => $value) {
361
-            // Get or create record
362
-            $record = $this->getOrCreateTag($value);
363
-            if ($record) {
364
-                $ids[] = $record->ID;
365
-                $values[$key] = $record->Title;
366
-            }
367
-        }
368
-
369
-        $relation->setByIDList(array_filter($ids));
370
-    }
371
-
372
-    /**
373
-     * Get or create tag with the given value
374
-     *
375
-     * @param  string $term
376
-     * @return DataObject|bool
377
-     */
378
-    protected function getOrCreateTag($term)
379
-    {
380
-        // Check if existing record can be found
381
-        $source = $this->getSourceList();
382
-        $titleField = $this->getTitleField();
383
-        $record = $source
384
-            ->filter($titleField, $term)
385
-            ->first();
386
-        if ($record) {
387
-            return $record;
388
-        }
389
-
390
-        // Create new instance if not yet saved
391
-        if ($this->getCanCreate()) {
392
-            $dataClass = $source->dataClass();
393
-            $record = Injector::inst()->create($dataClass);
394
-
395
-            if (is_array($term)) {
396
-                $term = $term['Value'];
397
-            }
398
-
399
-            $record->{$titleField} = $term;
400
-            $record->write();
401
-            if ($source instanceof SS_List) {
402
-                $source->add($record);
403
-            }
404
-            return $record;
405
-        }
406
-
407
-        return false;
408
-    }
409
-
410
-    /**
411
-     * Returns a JSON string of tags, for lazy loading.
412
-     *
413
-     * @param  HTTPRequest $request
414
-     * @return HTTPResponse
415
-     */
416
-    public function suggest(HTTPRequest $request)
417
-    {
418
-        $tags = $this->getTags($request->getVar('term'));
419
-
420
-        $response = HTTPResponse::create();
421
-        $response->addHeader('Content-Type', 'application/json');
422
-        $response->setBody(json_encode(['items' => $tags]));
423
-
424
-        return $response;
425
-    }
426
-
427
-    /**
428
-     * Returns array of arrays representing tags.
429
-     *
430
-     * @param  string $term
431
-     * @return array
432
-     */
433
-    protected function getTags($term)
434
-    {
435
-        $source = $this->getSourceList();
436
-
437
-        $titleField = $this->getTitleField();
438
-
439
-        $query = $source
440
-            ->filter($titleField . ':PartialMatch:nocase', $term)
441
-            ->sort($titleField)
442
-            ->limit($this->getLazyLoadItemLimit());
443
-
444
-        // Map into a distinct list
445
-        $items = [];
446
-        $titleField = $this->getTitleField();
447
-        foreach ($query->map('ID', $titleField) as $id => $title) {
448
-            $items[$title] = [
449
-                'Title' => $title,
450
-                'Value' => $title,
451
-            ];
452
-        }
453
-
454
-        return array_values($items);
455
-    }
456
-
457
-    /**
458
-     * DropdownField assumes value will be a scalar so we must
459
-     * override validate. This only applies to Silverstripe 3.2+
460
-     *
461
-     * @param Validator $validator
462
-     * @return bool
463
-     */
464
-    public function validate($validator)
465
-    {
466
-        return true;
467
-    }
468
-
469
-    /**
470
-     * Converts the field to a readonly variant.
471
-     *
472
-     * @return ReadonlyTagField
473
-     */
474
-    public function performReadonlyTransformation()
475
-    {
476
-        /** @var ReadonlyTagField $copy */
477
-        $copy = $this->castedCopy(ReadonlyTagField::class);
478
-        $copy->setSourceList($this->getSourceList());
479
-        return $copy;
480
-    }
481
-
482
-    /**
483
-     * Prevent the default, which would return "tag"
484
-     *
485
-     * @return string
486
-     */
487
-    public function Type()
488
-    {
489
-        return '';
490
-    }
491
-
492
-    public function getSchemaStateDefaults()
493
-    {
494
-        $data = parent::getSchemaStateDefaults();
495
-
496
-        // Add options to 'data'
497
-        $data['lazyLoad'] = $this->getShouldLazyLoad();
498
-        $data['multi'] = $this->getIsMultiple();
499
-        $data['optionUrl'] = $this->getSuggestURL();
500
-        $data['creatable'] = $this->getCanCreate();
501
-        $options = $this->getOptions(true);
502
-        $data['value'] = $options->count() ? $options->toNestedArray() : null;
503
-
504
-        return $data;
505
-    }
26
+	/**
27
+	 * @var array
28
+	 */
29
+	private static $allowed_actions = [
30
+		'suggest',
31
+	];
32
+
33
+	/**
34
+	 * @var bool
35
+	 */
36
+	protected $shouldLazyLoad = false;
37
+
38
+	/**
39
+	 * @var int
40
+	 */
41
+	protected $lazyLoadItemLimit = 10;
42
+
43
+	/**
44
+	 * @var bool
45
+	 */
46
+	protected $canCreate = true;
47
+
48
+	/**
49
+	 * @var string
50
+	 */
51
+	protected $titleField = 'Title';
52
+
53
+	/**
54
+	 * @var DataList
55
+	 */
56
+	protected $sourceList;
57
+
58
+	/**
59
+	 * @var bool
60
+	 */
61
+	protected $isMultiple = true;
62
+
63
+	/** @skipUpgrade */
64
+	protected $schemaComponent = 'TagField';
65
+
66
+	/**
67
+	 * @param string $name
68
+	 * @param string $title
69
+	 * @param null|DataList $source
70
+	 * @param null|DataList $value
71
+	 * @param string $titleField
72
+	 */
73
+	public function __construct($name, $title = '', $source = [], $value = null, $titleField = 'Title')
74
+	{
75
+		$this->setSourceList($source);
76
+		$this->setTitleField($titleField);
77
+		parent::__construct($name, $title, $source, $value);
78
+
79
+		$this->addExtraClass('ss-tag-field');
80
+	}
81
+
82
+	/**
83
+	 * @return bool
84
+	 */
85
+	public function getShouldLazyLoad()
86
+	{
87
+		return $this->shouldLazyLoad;
88
+	}
89
+
90
+	/**
91
+	 * @param bool $shouldLazyLoad
92
+	 *
93
+	 * @return static
94
+	 */
95
+	public function setShouldLazyLoad($shouldLazyLoad)
96
+	{
97
+		$this->shouldLazyLoad = $shouldLazyLoad;
98
+
99
+		return $this;
100
+	}
101
+
102
+	/**
103
+	 * @return int
104
+	 */
105
+	public function getLazyLoadItemLimit()
106
+	{
107
+		return $this->lazyLoadItemLimit;
108
+	}
109
+
110
+	/**
111
+	 * @param int $lazyLoadItemLimit
112
+	 *
113
+	 * @return static
114
+	 */
115
+	public function setLazyLoadItemLimit($lazyLoadItemLimit)
116
+	{
117
+		$this->lazyLoadItemLimit = $lazyLoadItemLimit;
118
+
119
+		return $this;
120
+	}
121
+
122
+	/**
123
+	 * @return bool
124
+	 */
125
+	public function getIsMultiple()
126
+	{
127
+		return $this->isMultiple;
128
+	}
129
+
130
+	/**
131
+	 * @param bool $isMultiple
132
+	 *
133
+	 * @return static
134
+	 */
135
+	public function setIsMultiple($isMultiple)
136
+	{
137
+		$this->isMultiple = $isMultiple;
138
+
139
+		return $this;
140
+	}
141
+
142
+	/**
143
+	 * @return bool
144
+	 */
145
+	public function getCanCreate()
146
+	{
147
+		return $this->canCreate;
148
+	}
149
+
150
+	/**
151
+	 * @param bool $canCreate
152
+	 *
153
+	 * @return static
154
+	 */
155
+	public function setCanCreate($canCreate)
156
+	{
157
+		$this->canCreate = $canCreate;
158
+
159
+		return $this;
160
+	}
161
+
162
+	/**
163
+	 * @return string
164
+	 */
165
+	public function getTitleField()
166
+	{
167
+		return $this->titleField;
168
+	}
169
+
170
+	/**
171
+	 * @param string $titleField
172
+	 *
173
+	 * @return $this
174
+	 */
175
+	public function setTitleField($titleField)
176
+	{
177
+		$this->titleField = $titleField;
178
+
179
+		return $this;
180
+	}
181
+
182
+	/**
183
+	 * Get the DataList source. The 4.x upgrade for SelectField::setSource starts to convert this to an array
184
+	 * @return DataList
185
+	 */
186
+	public function getSourceList()
187
+	{
188
+		return $this->sourceList;
189
+	}
190
+
191
+	/**
192
+	 * Set the model class name for tags
193
+	 * @param  DataList $className
194
+	 * @return self
195
+	 */
196
+	public function setSourceList($sourceList)
197
+	{
198
+		$this->sourceList = $sourceList;
199
+		return $this;
200
+	}
201
+
202
+	/**
203
+	 * {@inheritdoc}
204
+	 */
205
+	public function Field($properties = [])
206
+	{
207
+		$this->addExtraClass('entwine');
208
+
209
+		return $this->customise($properties)->renderWith(self::class);
210
+	}
211
+
212
+	/**
213
+	 * Provide TagField data to the JSON schema for the frontend component
214
+	 *
215
+	 * @return array
216
+	 */
217
+	public function getSchemaDataDefaults()
218
+	{
219
+		$options = $this->getOptions(true);
220
+		$schema = array_merge(
221
+			parent::getSchemaDataDefaults(),
222
+			[
223
+				'name' => $this->getName() . '[]',
224
+				'lazyLoad' => $this->getShouldLazyLoad(),
225
+				'creatable' => $this->getCanCreate(),
226
+				'multi' => $this->getIsMultiple(),
227
+				'value' => $options->count() ? $options->toNestedArray() : null,
228
+				'disabled' => $this->isDisabled() || $this->isReadonly(),
229
+			]
230
+		);
231
+
232
+		if (!$this->getShouldLazyLoad()) {
233
+			$schema['options'] = array_values($this->getOptions()->toNestedArray());
234
+		} else {
235
+			$schema['optionUrl'] = $this->getSuggestURL();
236
+		}
237
+
238
+		return $schema;
239
+	}
240
+
241
+	/**
242
+	 * @return string
243
+	 */
244
+	protected function getSuggestURL()
245
+	{
246
+		return Controller::join_links($this->Link(), 'suggest');
247
+	}
248
+
249
+	/**
250
+	 * @return ArrayList
251
+	 */
252
+	protected function getOptions($onlySelected = false)
253
+	{
254
+		$options = ArrayList::create();
255
+		$source = $this->getSourceList();
256
+
257
+		// No source means we have no options
258
+		if (!$source) {
259
+			return ArrayList::create();
260
+		}
261
+
262
+		$dataClass = $source->dataClass();
263
+
264
+		$values = $this->Value();
265
+
266
+		// If we have no values and we only want selected options we can bail here
267
+		if (empty($values) && $onlySelected) {
268
+			return ArrayList::create();
269
+		}
270
+
271
+		// Convert an array of values into a datalist of options
272
+		if (is_array($values) && !empty($values)) {
273
+			$values = DataList::create($dataClass)
274
+				->filter($this->getTitleField(), $values);
275
+		} else {
276
+			$values = ArrayList::create();
277
+		}
278
+
279
+		// Prep a function to parse a dataobject into an option
280
+		$addOption = function (DataObject $item) use ($options, $values) {
281
+			$titleField = $this->getTitleField();
282
+			$option = $item->$titleField;
283
+			$options->push(ArrayData::create([
284
+				'Title' => $option,
285
+				'Value' => $option,
286
+				'Selected' => (bool) $values->find('ID', $item->ID)
287
+			]));
288
+		};
289
+
290
+		// Only parse the values if we only want the selected items in the values list (this is for lazy-loading)
291
+		if ($onlySelected) {
292
+			$values->each($addOption);
293
+			return $options;
294
+		}
295
+
296
+		$source->each($addOption);
297
+		return $options;
298
+	}
299
+
300
+	/**
301
+	 * {@inheritdoc}
302
+	 */
303
+	public function setValue($value, $source = null)
304
+	{
305
+		if ($source instanceof DataObject) {
306
+			$name = $this->getName();
307
+
308
+			if ($source->hasMethod($name)) {
309
+				$value = $source->$name()->column($this->getTitleField());
310
+			}
311
+		}
312
+
313
+		if (!is_array($value)) {
314
+			return parent::setValue($value);
315
+		}
316
+
317
+		return parent::setValue(array_filter($value));
318
+	}
319
+
320
+	/**
321
+	 * {@inheritdoc}
322
+	 */
323
+	public function getAttributes()
324
+	{
325
+		return array_merge(
326
+			parent::getAttributes(),
327
+			[
328
+				'name' => $this->getName() . '[]',
329
+				'style' => 'width: 100%',
330
+				'data-schema' => json_encode($this->getSchemaData()),
331
+			]
332
+		);
333
+	}
334
+
335
+	/**
336
+	 * {@inheritdoc}
337
+	 */
338
+	public function saveInto(DataObjectInterface $record)
339
+	{
340
+		$name = $this->getName();
341
+		$titleField = $this->getTitleField();
342
+		$values = $this->Value();
343
+		$relation = $record->$name();
344
+		$ids = [];
345
+
346
+		if (!$values) {
347
+			$values = [];
348
+		}
349
+
350
+		if (empty($record) || empty($titleField)) {
351
+			return;
352
+		}
353
+
354
+		if (!$record->hasMethod($name)) {
355
+			throw new Exception(
356
+				sprintf("%s does not have a %s method", get_class($record), $name)
357
+			);
358
+		}
359
+
360
+		foreach ($values as $key => $value) {
361
+			// Get or create record
362
+			$record = $this->getOrCreateTag($value);
363
+			if ($record) {
364
+				$ids[] = $record->ID;
365
+				$values[$key] = $record->Title;
366
+			}
367
+		}
368
+
369
+		$relation->setByIDList(array_filter($ids));
370
+	}
371
+
372
+	/**
373
+	 * Get or create tag with the given value
374
+	 *
375
+	 * @param  string $term
376
+	 * @return DataObject|bool
377
+	 */
378
+	protected function getOrCreateTag($term)
379
+	{
380
+		// Check if existing record can be found
381
+		$source = $this->getSourceList();
382
+		$titleField = $this->getTitleField();
383
+		$record = $source
384
+			->filter($titleField, $term)
385
+			->first();
386
+		if ($record) {
387
+			return $record;
388
+		}
389
+
390
+		// Create new instance if not yet saved
391
+		if ($this->getCanCreate()) {
392
+			$dataClass = $source->dataClass();
393
+			$record = Injector::inst()->create($dataClass);
394
+
395
+			if (is_array($term)) {
396
+				$term = $term['Value'];
397
+			}
398
+
399
+			$record->{$titleField} = $term;
400
+			$record->write();
401
+			if ($source instanceof SS_List) {
402
+				$source->add($record);
403
+			}
404
+			return $record;
405
+		}
406
+
407
+		return false;
408
+	}
409
+
410
+	/**
411
+	 * Returns a JSON string of tags, for lazy loading.
412
+	 *
413
+	 * @param  HTTPRequest $request
414
+	 * @return HTTPResponse
415
+	 */
416
+	public function suggest(HTTPRequest $request)
417
+	{
418
+		$tags = $this->getTags($request->getVar('term'));
419
+
420
+		$response = HTTPResponse::create();
421
+		$response->addHeader('Content-Type', 'application/json');
422
+		$response->setBody(json_encode(['items' => $tags]));
423
+
424
+		return $response;
425
+	}
426
+
427
+	/**
428
+	 * Returns array of arrays representing tags.
429
+	 *
430
+	 * @param  string $term
431
+	 * @return array
432
+	 */
433
+	protected function getTags($term)
434
+	{
435
+		$source = $this->getSourceList();
436
+
437
+		$titleField = $this->getTitleField();
438
+
439
+		$query = $source
440
+			->filter($titleField . ':PartialMatch:nocase', $term)
441
+			->sort($titleField)
442
+			->limit($this->getLazyLoadItemLimit());
443
+
444
+		// Map into a distinct list
445
+		$items = [];
446
+		$titleField = $this->getTitleField();
447
+		foreach ($query->map('ID', $titleField) as $id => $title) {
448
+			$items[$title] = [
449
+				'Title' => $title,
450
+				'Value' => $title,
451
+			];
452
+		}
453
+
454
+		return array_values($items);
455
+	}
456
+
457
+	/**
458
+	 * DropdownField assumes value will be a scalar so we must
459
+	 * override validate. This only applies to Silverstripe 3.2+
460
+	 *
461
+	 * @param Validator $validator
462
+	 * @return bool
463
+	 */
464
+	public function validate($validator)
465
+	{
466
+		return true;
467
+	}
468
+
469
+	/**
470
+	 * Converts the field to a readonly variant.
471
+	 *
472
+	 * @return ReadonlyTagField
473
+	 */
474
+	public function performReadonlyTransformation()
475
+	{
476
+		/** @var ReadonlyTagField $copy */
477
+		$copy = $this->castedCopy(ReadonlyTagField::class);
478
+		$copy->setSourceList($this->getSourceList());
479
+		return $copy;
480
+	}
481
+
482
+	/**
483
+	 * Prevent the default, which would return "tag"
484
+	 *
485
+	 * @return string
486
+	 */
487
+	public function Type()
488
+	{
489
+		return '';
490
+	}
491
+
492
+	public function getSchemaStateDefaults()
493
+	{
494
+		$data = parent::getSchemaStateDefaults();
495
+
496
+		// Add options to 'data'
497
+		$data['lazyLoad'] = $this->getShouldLazyLoad();
498
+		$data['multi'] = $this->getIsMultiple();
499
+		$data['optionUrl'] = $this->getSuggestURL();
500
+		$data['creatable'] = $this->getCanCreate();
501
+		$options = $this->getOptions(true);
502
+		$data['value'] = $options->count() ? $options->toNestedArray() : null;
503
+
504
+		return $data;
505
+	}
506 506
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -220,7 +220,7 @@  discard block
 block discarded – undo
220 220
         $schema = array_merge(
221 221
             parent::getSchemaDataDefaults(),
222 222
             [
223
-                'name' => $this->getName() . '[]',
223
+                'name' => $this->getName().'[]',
224 224
                 'lazyLoad' => $this->getShouldLazyLoad(),
225 225
                 'creatable' => $this->getCanCreate(),
226 226
                 'multi' => $this->getIsMultiple(),
@@ -277,7 +277,7 @@  discard block
 block discarded – undo
277 277
         }
278 278
 
279 279
         // Prep a function to parse a dataobject into an option
280
-        $addOption = function (DataObject $item) use ($options, $values) {
280
+        $addOption = function(DataObject $item) use ($options, $values) {
281 281
             $titleField = $this->getTitleField();
282 282
             $option = $item->$titleField;
283 283
             $options->push(ArrayData::create([
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
         return array_merge(
326 326
             parent::getAttributes(),
327 327
             [
328
-                'name' => $this->getName() . '[]',
328
+                'name' => $this->getName().'[]',
329 329
                 'style' => 'width: 100%',
330 330
                 'data-schema' => json_encode($this->getSchemaData()),
331 331
             ]
@@ -437,7 +437,7 @@  discard block
 block discarded – undo
437 437
         $titleField = $this->getTitleField();
438 438
 
439 439
         $query = $source
440
-            ->filter($titleField . ':PartialMatch:nocase', $term)
440
+            ->filter($titleField.':PartialMatch:nocase', $term)
441 441
             ->sort($titleField)
442 442
             ->limit($this->getLazyLoadItemLimit());
443 443
 
Please login to merge, or discard this patch.
tests/TagFieldTest.php 1 patch
Indentation   +377 added lines, -377 removed lines patch added patch discarded remove patch
@@ -20,381 +20,381 @@
 block discarded – undo
20 20
  */
21 21
 class TagFieldTest extends SapphireTest
22 22
 {
23
-    /**
24
-     * @var string
25
-     */
26
-    protected static $fixture_file = 'TagFieldTest.yml';
27
-
28
-    /**
29
-     * @var array
30
-     */
31
-    protected static $extra_dataobjects = [
32
-        TagFieldTestBlogTag::class,
33
-        TagFieldTestBlogPost::class,
34
-    ];
35
-
36
-    public function testItSavesLinksToNewTagsOnNewRecords()
37
-    {
38
-        $record = $this->getNewTagFieldTestBlogPost('BlogPost1');
39
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
40
-        $field->setValue(['Tag3', 'Tag4']);
41
-        $field->saveInto($record);
42
-        $record->write();
43
-        $this->compareExpectedAndActualTags(
44
-            ['Tag3', 'Tag4'],
45
-            $record
46
-        );
47
-    }
48
-
49
-    /**
50
-     * @param string $name
51
-     *
52
-     * @return TagFieldTestBlogPost
53
-     */
54
-    protected function getNewTagFieldTestBlogPost($name)
55
-    {
56
-        return $this->objFromFixture(
57
-            TagFieldTestBlogPost::class,
58
-            $name
59
-        );
60
-    }
61
-
62
-    /**
63
-     * @param array $expected
64
-     * @param TagFieldTestBlogPost $record
65
-     */
66
-    protected function compareExpectedAndActualTags(array $expected, TagFieldTestBlogPost $record)
67
-    {
68
-        $this->compareTagLists($expected, $record->Tags());
69
-    }
70
-
71
-    /**
72
-     * Ensure a source of tags matches the given string tag names
73
-     *
74
-     * @param array $expected
75
-     * @param DataList $actualSource
76
-     */
77
-    protected function compareTagLists(array $expected, DataList $actualSource)
78
-    {
79
-        $actual = array_values($actualSource->map('ID', 'Title')->toArray());
80
-        sort($expected);
81
-        sort($actual);
82
-
83
-        $this->assertEquals(
84
-            $expected,
85
-            $actual
86
-        );
87
-    }
88
-
89
-    public function testItSavesLinksToNewTagsOnExistingRecords()
90
-    {
91
-        $record = $this->getNewTagFieldTestBlogPost('BlogPost1');
92
-        $record->write();
93
-
94
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
95
-        $field->setValue(['Tag3', 'Tag4']);
96
-        $field->saveInto($record);
97
-
98
-        $this->compareExpectedAndActualTags(
99
-            array('Tag3', 'Tag4'),
100
-            $record
101
-        );
102
-    }
103
-
104
-    public function testItSavesLinksToExistingTagsOnNewRecords()
105
-    {
106
-        $record = $this->getNewTagFieldTestBlogPost('BlogPost1');
107
-
108
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
109
-        $field->setValue(['Tag1', 'Tag2']);
110
-        $field->saveInto($record);
111
-
112
-        $record->write();
113
-
114
-        $this->compareExpectedAndActualTags(
115
-            ['Tag1', 'Tag2'],
116
-            $record
117
-        );
118
-    }
119
-
120
-    public function testItSavesLinksToExistingTagsOnExistingRecords()
121
-    {
122
-        $record = $this->getNewTagFieldTestBlogPost('BlogPost1');
123
-        $record->write();
124
-
125
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
126
-        $field->setValue(['Tag1', 'Tag2']);
127
-        $field->saveInto($record);
128
-
129
-        $this->compareExpectedAndActualTags(
130
-            ['Tag1', 'Tag2'],
131
-            $record
132
-        );
133
-    }
134
-
135
-    /**
136
-     * Ensure that {@see TagField::saveInto} respects existing tags
137
-     */
138
-    public function testSaveDuplicateTags()
139
-    {
140
-        $record = $this->getNewTagFieldTestBlogPost('BlogPost2');
141
-        $record->write();
142
-        $tag2ID = $this->idFromFixture(TagFieldTestBlogTag::class, 'Tag2');
143
-
144
-        // Check tags before write
145
-        $this->compareExpectedAndActualTags(
146
-            ['Tag1', '222'],
147
-            $record
148
-        );
149
-        $this->compareTagLists(
150
-            ['Tag1', '222'],
151
-            TagFieldTestBlogTag::get()
152
-        );
153
-        $this->assertContains($tag2ID, TagFieldTestBlogTag::get()->column('ID'));
154
-
155
-        // Write new tags
156
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
157
-        $field->setValue(['222', 'Tag3']);
158
-        $field->saveInto($record);
159
-
160
-        // Check only one new tag was added
161
-        $this->compareExpectedAndActualTags(
162
-            ['222', 'Tag3'],
163
-            $record
164
-        );
165
-
166
-        // Ensure that only one new dataobject was added and that tag2s id has not changed
167
-        $this->compareTagLists(
168
-            ['Tag1', '222', 'Tag3'],
169
-            TagFieldTestBlogTag::get()
170
-        );
171
-        $this->assertContains($tag2ID, TagFieldTestBlogTag::get()->column('ID'));
172
-    }
173
-
174
-    public function testItSuggestsTags()
175
-    {
176
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
177
-
178
-        /**
179
-         * Partial tag title match.
180
-         */
181
-        $request = $this->getNewRequest(['term' => 'Tag']);
182
-
183
-        $this->assertEquals(
184
-            '{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
185
-            $field->suggest($request)->getBody()
186
-        );
187
-
188
-        /**
189
-         * Exact tag title match.
190
-         */
191
-        $request = $this->getNewRequest(['term' => '222']);
192
-
193
-        $this->assertEquals(
194
-            '{"items":[{"Title":"222","Value":"222"}]}',
195
-            $field->suggest($request)->getBody()
196
-        );
197
-
198
-        /**
199
-         * Case-insensitive tag title match.
200
-         */
201
-        $request = $this->getNewRequest(['term' => 'TAG1']);
202
-
203
-        $this->assertEquals(
204
-            '{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
205
-            $field->suggest($request)->getBody()
206
-        );
207
-
208
-        /**
209
-         * No tag title match.
210
-         */
211
-        $request = $this->getNewRequest(['term' => 'unknown']);
212
-
213
-        $this->assertEquals(
214
-            '{"items":[]}',
215
-            $field->suggest($request)->getBody()
216
-        );
217
-    }
218
-
219
-    /**
220
-     * Tests that TagField supports pre-filtered data sources
221
-     */
222
-    public function testRestrictedSuggestions()
223
-    {
224
-        $source = TagFieldTestBlogTag::get()->exclude('Title', 'Tag2');
225
-        $field = new TagField('Tags', '', $source);
226
-
227
-        /**
228
-         * Partial tag title match.
229
-         */
230
-        $request = $this->getNewRequest(['term' => 'Tag']);
231
-
232
-        $this->assertEquals(
233
-            '{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
234
-            $field->suggest($request)->getBody()
235
-        );
236
-
237
-        /**
238
-         * Exact tag title match.
239
-         */
240
-        $request = $this->getNewRequest(['term' => 'Tag1']);
241
-
242
-        $this->assertEquals(
243
-            '{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
244
-            $field->suggest($request)->getBody()
245
-        );
246
-
247
-        /**
248
-         * Excluded item doesn't appear in matches
249
-         */
250
-        $request = $this->getNewRequest(['term' => 'Tag2']);
251
-
252
-        $this->assertEquals(
253
-            '{"items":[]}',
254
-            $field->suggest($request)->getBody()
255
-        );
256
-    }
257
-
258
-    /**
259
-     * @param array $parameters
260
-     *
261
-     * @return HTTPRequest
262
-     */
263
-    protected function getNewRequest(array $parameters)
264
-    {
265
-        return new HTTPRequest(
266
-            'get',
267
-            'TagFieldTestController/TagFieldTestForm/fields/Tags/suggest',
268
-            $parameters
269
-        );
270
-    }
271
-
272
-    public function testItDisplaysValuesFromRelations()
273
-    {
274
-        $record = $this->getNewTagFieldTestBlogPost('BlogPost1');
275
-        $record->write();
276
-
277
-        $form = new Form(
278
-            new TagFieldTestController(),
279
-            'Form',
280
-            new FieldList(
281
-                $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class))
282
-            ),
283
-            new FieldList()
284
-        );
285
-
286
-        $form->loadDataFrom(
287
-            $this->objFromFixture(TagFieldTestBlogPost::class, 'BlogPost2')
288
-        );
289
-
290
-        $ids = TagFieldTestBlogTag::get()->column('Title');
291
-
292
-        $this->assertEquals($field->Value(), $ids);
293
-    }
294
-
295
-    public function testItIgnoresNewTagsIfCannotCreate()
296
-    {
297
-        $this->markTestSkipped(
298
-            'This test has not been updated yet.'
299
-        );
300
-
301
-        $record = new TagFieldTestBlogPost();
302
-        $record->write();
303
-
304
-        $tag = TagFieldTestBlogTag::get()->filter('Title', 'Tag1')->first();
305
-
306
-        $field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class), [$tag->Title, 'Tag3']);
307
-        $field->setCanCreate(false);
308
-        $field->saveInto($record);
309
-
310
-        /**
311
-         * @var TagFieldTestBlogPost $record
312
-         */
313
-        $record = DataObject::get_by_id(TagFieldTestBlogPost::class, $record->ID);
314
-
315
-        $this->compareExpectedAndActualTags(
316
-            ['Tag1'],
317
-            $record
318
-        );
319
-    }
320
-
321
-    /**
322
-     * Test you can save without a source set
323
-     */
324
-    public function testSaveEmptySource()
325
-    {
326
-        $record = new TagFieldTestBlogPost();
327
-        $record->write();
328
-
329
-        // Clear database of tags
330
-        TagFieldTestBlogTag::get()->removeAll();
331
-
332
-        $field = new TagField('Tags', '', TagFieldTestBlogTag::get());
333
-        $field->setValue(['New Tag']);
334
-        $field->setCanCreate(true);
335
-        $field->saveInto($record);
336
-
337
-        $tag = TagFieldTestBlogTag::get()->first();
338
-        $this->assertNotEmpty($tag);
339
-        $this->assertEquals('New Tag', $tag->Title);
340
-        $record = TagFieldTestBlogPost::get()->byID($record->ID);
341
-        $this->assertEquals(
342
-            $tag->ID,
343
-            $record->Tags()->first()->ID
344
-        );
345
-    }
346
-
347
-    /**
348
-     * Test read only fields are returned
349
-     */
350
-    public function testReadonlyTransformation()
351
-    {
352
-        $field = new TagField('Tags', '', TagFieldTestBlogTag::get());
353
-        $readOnlyField = $field->performReadonlyTransformation();
354
-        $this->assertInstanceOf(ReadonlyTagField::class, $readOnlyField);
355
-    }
356
-
357
-    public function testGetSchemaDataDefaults()
358
-    {
359
-        $form = new Form(null, 'Form', new FieldList(), new FieldList());
360
-        $field = new TagField('TestField', 'Test Field', TagFieldTestBlogTag::get());
361
-        $field->setForm($form);
362
-
363
-        $field
364
-            ->setShouldLazyLoad(false)
365
-            ->setCanCreate(false);
366
-
367
-        $schema = $field->getSchemaDataDefaults();
368
-        $this->assertSame('TestField[]', $schema['name']);
369
-        $this->assertFalse($schema['lazyLoad']);
370
-        $this->assertFalse($schema['creatable']);
371
-        $this->assertEquals([
372
-            ['Title' => 'Tag1', 'Value' => 'Tag1', 'Selected' => false],
373
-            ['Title' => '222', 'Value' => '222', 'Selected' => false],
374
-        ], $schema['options']);
375
-
376
-        $field->setValue(['222']);
377
-        $schema = $field->getSchemaDataDefaults();
378
-
379
-        $this->assertEquals([
380
-            ['Title' => 'Tag1', 'Value' => 'Tag1', 'Selected' => false],
381
-            ['Title' => '222', 'Value' => '222', 'Selected' => true],
382
-        ], $schema['options']);
383
-
384
-        $field
385
-            ->setShouldLazyLoad(true)
386
-            ->setCanCreate(true);
387
-
388
-        $schema = $field->getSchemaDataDefaults();
389
-        $this->assertTrue($schema['lazyLoad']);
390
-        $this->assertTrue($schema['creatable']);
391
-        $this->assertContains('suggest', $schema['optionUrl']);
392
-    }
393
-
394
-    public function testSchemaIsAddedToAttributes()
395
-    {
396
-        $field = new TagField('TestField');
397
-        $attributes = $field->getAttributes();
398
-        $this->assertNotEmpty($attributes['data-schema']);
399
-    }
23
+	/**
24
+	 * @var string
25
+	 */
26
+	protected static $fixture_file = 'TagFieldTest.yml';
27
+
28
+	/**
29
+	 * @var array
30
+	 */
31
+	protected static $extra_dataobjects = [
32
+		TagFieldTestBlogTag::class,
33
+		TagFieldTestBlogPost::class,
34
+	];
35
+
36
+	public function testItSavesLinksToNewTagsOnNewRecords()
37
+	{
38
+		$record = $this->getNewTagFieldTestBlogPost('BlogPost1');
39
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
40
+		$field->setValue(['Tag3', 'Tag4']);
41
+		$field->saveInto($record);
42
+		$record->write();
43
+		$this->compareExpectedAndActualTags(
44
+			['Tag3', 'Tag4'],
45
+			$record
46
+		);
47
+	}
48
+
49
+	/**
50
+	 * @param string $name
51
+	 *
52
+	 * @return TagFieldTestBlogPost
53
+	 */
54
+	protected function getNewTagFieldTestBlogPost($name)
55
+	{
56
+		return $this->objFromFixture(
57
+			TagFieldTestBlogPost::class,
58
+			$name
59
+		);
60
+	}
61
+
62
+	/**
63
+	 * @param array $expected
64
+	 * @param TagFieldTestBlogPost $record
65
+	 */
66
+	protected function compareExpectedAndActualTags(array $expected, TagFieldTestBlogPost $record)
67
+	{
68
+		$this->compareTagLists($expected, $record->Tags());
69
+	}
70
+
71
+	/**
72
+	 * Ensure a source of tags matches the given string tag names
73
+	 *
74
+	 * @param array $expected
75
+	 * @param DataList $actualSource
76
+	 */
77
+	protected function compareTagLists(array $expected, DataList $actualSource)
78
+	{
79
+		$actual = array_values($actualSource->map('ID', 'Title')->toArray());
80
+		sort($expected);
81
+		sort($actual);
82
+
83
+		$this->assertEquals(
84
+			$expected,
85
+			$actual
86
+		);
87
+	}
88
+
89
+	public function testItSavesLinksToNewTagsOnExistingRecords()
90
+	{
91
+		$record = $this->getNewTagFieldTestBlogPost('BlogPost1');
92
+		$record->write();
93
+
94
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
95
+		$field->setValue(['Tag3', 'Tag4']);
96
+		$field->saveInto($record);
97
+
98
+		$this->compareExpectedAndActualTags(
99
+			array('Tag3', 'Tag4'),
100
+			$record
101
+		);
102
+	}
103
+
104
+	public function testItSavesLinksToExistingTagsOnNewRecords()
105
+	{
106
+		$record = $this->getNewTagFieldTestBlogPost('BlogPost1');
107
+
108
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
109
+		$field->setValue(['Tag1', 'Tag2']);
110
+		$field->saveInto($record);
111
+
112
+		$record->write();
113
+
114
+		$this->compareExpectedAndActualTags(
115
+			['Tag1', 'Tag2'],
116
+			$record
117
+		);
118
+	}
119
+
120
+	public function testItSavesLinksToExistingTagsOnExistingRecords()
121
+	{
122
+		$record = $this->getNewTagFieldTestBlogPost('BlogPost1');
123
+		$record->write();
124
+
125
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
126
+		$field->setValue(['Tag1', 'Tag2']);
127
+		$field->saveInto($record);
128
+
129
+		$this->compareExpectedAndActualTags(
130
+			['Tag1', 'Tag2'],
131
+			$record
132
+		);
133
+	}
134
+
135
+	/**
136
+	 * Ensure that {@see TagField::saveInto} respects existing tags
137
+	 */
138
+	public function testSaveDuplicateTags()
139
+	{
140
+		$record = $this->getNewTagFieldTestBlogPost('BlogPost2');
141
+		$record->write();
142
+		$tag2ID = $this->idFromFixture(TagFieldTestBlogTag::class, 'Tag2');
143
+
144
+		// Check tags before write
145
+		$this->compareExpectedAndActualTags(
146
+			['Tag1', '222'],
147
+			$record
148
+		);
149
+		$this->compareTagLists(
150
+			['Tag1', '222'],
151
+			TagFieldTestBlogTag::get()
152
+		);
153
+		$this->assertContains($tag2ID, TagFieldTestBlogTag::get()->column('ID'));
154
+
155
+		// Write new tags
156
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
157
+		$field->setValue(['222', 'Tag3']);
158
+		$field->saveInto($record);
159
+
160
+		// Check only one new tag was added
161
+		$this->compareExpectedAndActualTags(
162
+			['222', 'Tag3'],
163
+			$record
164
+		);
165
+
166
+		// Ensure that only one new dataobject was added and that tag2s id has not changed
167
+		$this->compareTagLists(
168
+			['Tag1', '222', 'Tag3'],
169
+			TagFieldTestBlogTag::get()
170
+		);
171
+		$this->assertContains($tag2ID, TagFieldTestBlogTag::get()->column('ID'));
172
+	}
173
+
174
+	public function testItSuggestsTags()
175
+	{
176
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class));
177
+
178
+		/**
179
+		 * Partial tag title match.
180
+		 */
181
+		$request = $this->getNewRequest(['term' => 'Tag']);
182
+
183
+		$this->assertEquals(
184
+			'{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
185
+			$field->suggest($request)->getBody()
186
+		);
187
+
188
+		/**
189
+		 * Exact tag title match.
190
+		 */
191
+		$request = $this->getNewRequest(['term' => '222']);
192
+
193
+		$this->assertEquals(
194
+			'{"items":[{"Title":"222","Value":"222"}]}',
195
+			$field->suggest($request)->getBody()
196
+		);
197
+
198
+		/**
199
+		 * Case-insensitive tag title match.
200
+		 */
201
+		$request = $this->getNewRequest(['term' => 'TAG1']);
202
+
203
+		$this->assertEquals(
204
+			'{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
205
+			$field->suggest($request)->getBody()
206
+		);
207
+
208
+		/**
209
+		 * No tag title match.
210
+		 */
211
+		$request = $this->getNewRequest(['term' => 'unknown']);
212
+
213
+		$this->assertEquals(
214
+			'{"items":[]}',
215
+			$field->suggest($request)->getBody()
216
+		);
217
+	}
218
+
219
+	/**
220
+	 * Tests that TagField supports pre-filtered data sources
221
+	 */
222
+	public function testRestrictedSuggestions()
223
+	{
224
+		$source = TagFieldTestBlogTag::get()->exclude('Title', 'Tag2');
225
+		$field = new TagField('Tags', '', $source);
226
+
227
+		/**
228
+		 * Partial tag title match.
229
+		 */
230
+		$request = $this->getNewRequest(['term' => 'Tag']);
231
+
232
+		$this->assertEquals(
233
+			'{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
234
+			$field->suggest($request)->getBody()
235
+		);
236
+
237
+		/**
238
+		 * Exact tag title match.
239
+		 */
240
+		$request = $this->getNewRequest(['term' => 'Tag1']);
241
+
242
+		$this->assertEquals(
243
+			'{"items":[{"Title":"Tag1","Value":"Tag1"}]}',
244
+			$field->suggest($request)->getBody()
245
+		);
246
+
247
+		/**
248
+		 * Excluded item doesn't appear in matches
249
+		 */
250
+		$request = $this->getNewRequest(['term' => 'Tag2']);
251
+
252
+		$this->assertEquals(
253
+			'{"items":[]}',
254
+			$field->suggest($request)->getBody()
255
+		);
256
+	}
257
+
258
+	/**
259
+	 * @param array $parameters
260
+	 *
261
+	 * @return HTTPRequest
262
+	 */
263
+	protected function getNewRequest(array $parameters)
264
+	{
265
+		return new HTTPRequest(
266
+			'get',
267
+			'TagFieldTestController/TagFieldTestForm/fields/Tags/suggest',
268
+			$parameters
269
+		);
270
+	}
271
+
272
+	public function testItDisplaysValuesFromRelations()
273
+	{
274
+		$record = $this->getNewTagFieldTestBlogPost('BlogPost1');
275
+		$record->write();
276
+
277
+		$form = new Form(
278
+			new TagFieldTestController(),
279
+			'Form',
280
+			new FieldList(
281
+				$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class))
282
+			),
283
+			new FieldList()
284
+		);
285
+
286
+		$form->loadDataFrom(
287
+			$this->objFromFixture(TagFieldTestBlogPost::class, 'BlogPost2')
288
+		);
289
+
290
+		$ids = TagFieldTestBlogTag::get()->column('Title');
291
+
292
+		$this->assertEquals($field->Value(), $ids);
293
+	}
294
+
295
+	public function testItIgnoresNewTagsIfCannotCreate()
296
+	{
297
+		$this->markTestSkipped(
298
+			'This test has not been updated yet.'
299
+		);
300
+
301
+		$record = new TagFieldTestBlogPost();
302
+		$record->write();
303
+
304
+		$tag = TagFieldTestBlogTag::get()->filter('Title', 'Tag1')->first();
305
+
306
+		$field = new TagField('Tags', '', new DataList(TagFieldTestBlogTag::class), [$tag->Title, 'Tag3']);
307
+		$field->setCanCreate(false);
308
+		$field->saveInto($record);
309
+
310
+		/**
311
+		 * @var TagFieldTestBlogPost $record
312
+		 */
313
+		$record = DataObject::get_by_id(TagFieldTestBlogPost::class, $record->ID);
314
+
315
+		$this->compareExpectedAndActualTags(
316
+			['Tag1'],
317
+			$record
318
+		);
319
+	}
320
+
321
+	/**
322
+	 * Test you can save without a source set
323
+	 */
324
+	public function testSaveEmptySource()
325
+	{
326
+		$record = new TagFieldTestBlogPost();
327
+		$record->write();
328
+
329
+		// Clear database of tags
330
+		TagFieldTestBlogTag::get()->removeAll();
331
+
332
+		$field = new TagField('Tags', '', TagFieldTestBlogTag::get());
333
+		$field->setValue(['New Tag']);
334
+		$field->setCanCreate(true);
335
+		$field->saveInto($record);
336
+
337
+		$tag = TagFieldTestBlogTag::get()->first();
338
+		$this->assertNotEmpty($tag);
339
+		$this->assertEquals('New Tag', $tag->Title);
340
+		$record = TagFieldTestBlogPost::get()->byID($record->ID);
341
+		$this->assertEquals(
342
+			$tag->ID,
343
+			$record->Tags()->first()->ID
344
+		);
345
+	}
346
+
347
+	/**
348
+	 * Test read only fields are returned
349
+	 */
350
+	public function testReadonlyTransformation()
351
+	{
352
+		$field = new TagField('Tags', '', TagFieldTestBlogTag::get());
353
+		$readOnlyField = $field->performReadonlyTransformation();
354
+		$this->assertInstanceOf(ReadonlyTagField::class, $readOnlyField);
355
+	}
356
+
357
+	public function testGetSchemaDataDefaults()
358
+	{
359
+		$form = new Form(null, 'Form', new FieldList(), new FieldList());
360
+		$field = new TagField('TestField', 'Test Field', TagFieldTestBlogTag::get());
361
+		$field->setForm($form);
362
+
363
+		$field
364
+			->setShouldLazyLoad(false)
365
+			->setCanCreate(false);
366
+
367
+		$schema = $field->getSchemaDataDefaults();
368
+		$this->assertSame('TestField[]', $schema['name']);
369
+		$this->assertFalse($schema['lazyLoad']);
370
+		$this->assertFalse($schema['creatable']);
371
+		$this->assertEquals([
372
+			['Title' => 'Tag1', 'Value' => 'Tag1', 'Selected' => false],
373
+			['Title' => '222', 'Value' => '222', 'Selected' => false],
374
+		], $schema['options']);
375
+
376
+		$field->setValue(['222']);
377
+		$schema = $field->getSchemaDataDefaults();
378
+
379
+		$this->assertEquals([
380
+			['Title' => 'Tag1', 'Value' => 'Tag1', 'Selected' => false],
381
+			['Title' => '222', 'Value' => '222', 'Selected' => true],
382
+		], $schema['options']);
383
+
384
+		$field
385
+			->setShouldLazyLoad(true)
386
+			->setCanCreate(true);
387
+
388
+		$schema = $field->getSchemaDataDefaults();
389
+		$this->assertTrue($schema['lazyLoad']);
390
+		$this->assertTrue($schema['creatable']);
391
+		$this->assertContains('suggest', $schema['optionUrl']);
392
+	}
393
+
394
+	public function testSchemaIsAddedToAttributes()
395
+	{
396
+		$field = new TagField('TestField');
397
+		$attributes = $field->getAttributes();
398
+		$this->assertNotEmpty($attributes['data-schema']);
399
+	}
400 400
 }
Please login to merge, or discard this patch.