Completed
Push — 4.0 ( b59aea...80f83b )
by Loz
52s queued 21s
created

MultiSelectField::saveInto()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 7
nop 1
dl 0
loc 19
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms;
4
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\ORM\DataObjectInterface;
7
use SilverStripe\ORM\Relation;
8
9
/**
10
 * Represents a SelectField that may potentially have multiple selections, and may have
11
 * a {@link ManyManyList} as a data source.
12
 */
13
abstract class MultiSelectField extends SelectField
14
{
15
16
    /**
17
     * List of items to mark as checked, and may not be unchecked
18
     *
19
     * @var array
20
     */
21
    protected $defaultItems = array();
22
23
    protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_MULTISELECT;
24
25
    /**
26
     * Extracts the value of this field, normalised as an array.
27
     * Scalar values will return a single length array, even if empty
28
     *
29
     * @return array List of values as an array
30
     */
31
    public function getValueArray()
32
    {
33
        return $this->getListValues($this->Value());
34
    }
35
36
    /**
37
     * Default selections, regardless of the {@link setValue()} settings.
38
     * Note: Items marked as disabled through {@link setDisabledItems()} can still be
39
     * selected by default through this method.
40
     *
41
     * @param array $items Collection of array keys, as defined in the $source array
42
     * @return $this Self reference
43
     */
44
    public function setDefaultItems($items)
45
    {
46
        $this->defaultItems = $this->getListValues($items);
47
        return $this;
48
    }
49
50
    /**
51
     * Default selections, regardless of the {@link setValue()} settings.
52
     *
53
     * @return array
54
     */
55
    public function getDefaultItems()
56
    {
57
        return $this->defaultItems;
58
    }
59
60
    /**
61
     * Load a value into this MultiSelectField
62
     *
63
     * @param mixed $value
64
     * @param null|array|DataObject $obj {@see Form::loadDataFrom}
65
     * @return $this
66
     */
67
    public function setValue($value, $obj = null)
68
    {
69
        // If we're not passed a value directly,
70
        // we can look for it in a relation method on the object passed as a second arg
71
        if ($obj instanceof DataObject) {
72
            $this->loadFrom($obj);
73
        } else {
74
            parent::setValue($value);
75
        }
76
        return $this;
77
    }
78
79
    /**
80
     * Load the value from the dataobject into this field
81
     *
82
     * @param DataObject|DataObjectInterface $record
83
     */
84
    public function loadFrom(DataObjectInterface $record)
85
    {
86
        $fieldName = $this->getName();
87
        if (empty($fieldName) || empty($record)) {
88
            return;
89
        }
90
91
        $relation = $record->hasMethod($fieldName)
0 ignored issues
show
Bug introduced by
The method hasMethod() does not exist on SilverStripe\ORM\DataObjectInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SilverStripe\ORM\DataObjectInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

91
        $relation = $record->/** @scrutinizer ignore-call */ hasMethod($fieldName)
Loading history...
92
            ? $record->$fieldName()
93
            : null;
94
95
        // Detect DB relation or field
96
        if ($relation instanceof Relation) {
97
            // Load ids from relation
98
            $value = array_values($relation->getIDList());
99
            parent::setValue($value);
100
        } elseif ($record->hasField($fieldName)) {
0 ignored issues
show
Bug introduced by
The method hasField() does not exist on SilverStripe\ORM\DataObjectInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SilverStripe\ORM\DataObjectInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

100
        } elseif ($record->/** @scrutinizer ignore-call */ hasField($fieldName)) {
Loading history...
101
            $value = $this->stringDecode($record->$fieldName);
102
            parent::setValue($value);
103
        }
104
    }
105
106
107
    /**
108
     * Save the current value of this MultiSelectField into a DataObject.
109
     * If the field it is saving to is a has_many or many_many relationship,
110
     * it is saved by setByIDList(), otherwise it creates a comma separated
111
     * list for a standard DB text/varchar field.
112
     *
113
     * @param DataObject|DataObjectInterface $record The record to save into
114
     */
115
    public function saveInto(DataObjectInterface $record)
116
    {
117
        $fieldName = $this->getName();
118
        if (empty($fieldName) || empty($record)) {
119
            return;
120
        }
121
122
        $relation = $record->hasMethod($fieldName)
123
            ? $record->$fieldName()
124
            : null;
125
126
        // Detect DB relation or field
127
        $items = $this->getValueArray();
128
        if ($relation instanceof Relation) {
129
            // Save ids into relation
130
            $relation->setByIDList($items);
131
        } elseif ($record->hasField($fieldName)) {
132
            // Save dataValue into field
133
            $record->$fieldName = $this->stringEncode($items);
134
        }
135
    }
136
137
    /**
138
     * Encode a list of values into a string, or null if empty (to simplify empty checks)
139
     *
140
     * @param array $value
141
     * @return string|null
142
     */
143
    public function stringEncode($value)
144
    {
145
        return $value
146
            ? json_encode(array_values($value))
147
            : null;
148
    }
149
150
    /**
151
     * Extract a string value into an array of values
152
     *
153
     * @param string $value
154
     * @return array
155
     */
156
    protected function stringDecode($value)
157
    {
158
        // Handle empty case
159
        if (empty($value)) {
160
            return array();
161
        }
162
163
        // If json deserialisation fails, then fallover to legacy format
164
        $result = json_decode($value, true);
165
        if ($result !== false) {
166
            return $result;
167
        }
168
169
        throw new \InvalidArgumentException("Invalid string encoded value for multi select field");
170
    }
171
172
    /**
173
     * Validate this field
174
     *
175
     * @param Validator $validator
176
     * @return bool
177
     */
178
    public function validate($validator)
179
    {
180
        $values = $this->getValueArray();
181
        $validValues = $this->getValidValues();
182
183
        // Filter out selected values not in the data source
184
        $self = $this;
185
        $invalidValues = array_filter(
186
            $values,
187
            function ($userValue) use ($self, $validValues) {
188
                foreach ($validValues as $formValue) {
189
                    if ($self->isSelectedValue($formValue, $userValue)) {
190
                        return false;
191
                    }
192
                }
193
                return true;
194
            }
195
        );
196
        if (empty($invalidValues)) {
197
            return true;
198
        }
199
200
        // List invalid items
201
        $validator->validationError(
202
            $this->getName(),
203
            _t(
204
                'SilverStripe\\Forms\\MultiSelectField.SOURCE_VALIDATION',
205
                "Please select values within the list provided. Invalid option(s) {value} given",
206
                array('value' => implode(',', $invalidValues))
207
            ),
208
            "validation"
209
        );
210
        return false;
211
    }
212
213
    /**
214
     * Transforms the source data for this CheckboxSetField
215
     * into a comma separated list of values.
216
     *
217
     * @return ReadonlyField
218
     */
219
    public function performReadonlyTransformation()
220
    {
221
        $field = $this->castedCopy('SilverStripe\\Forms\\LookupField');
222
        $field->setSource($this->getSource());
223
        $field->setReadonly(true);
224
225
        return $field;
226
    }
227
}
228