SortableUploadField::saveInto()   C
last analyzed

Complexity

Conditions 12
Paths 15

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 28
c 1
b 0
f 0
nc 15
nop 1
dl 0
loc 50
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Bummzack\SortableFile\Forms;
4
5
use Psr\Log\LoggerInterface;
6
use SilverStripe\AssetAdmin\Forms\UploadField;
7
use SilverStripe\Assets\File;
8
use SilverStripe\Model\List\ArrayList;
9
use SilverStripe\Model\List\SS_List;
10
use SilverStripe\ORM\DataList;
11
use SilverStripe\ORM\DataObjectInterface;
12
use SilverStripe\ORM\DB;
13
use SilverStripe\ORM\ManyManyList;
14
use SilverStripe\ORM\ManyManyThroughList;
15
use SilverStripe\ORM\ManyManyThroughQueryManipulator;
16
use SilverStripe\ORM\Queries\SQLUpdate;
17
use SilverStripe\ORM\UnsavedRelationList;
18
19
/**
20
 * Extension of the UploadField to add sorting of files
21
 *
22
 * @author bummzack
23
 * @skipUpgrade
24
 */
25
class SortableUploadField extends UploadField
26
{
27
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
28
        'logger' => '%$Psr\Log\LoggerInterface',
29
    ];
30
31
    /**
32
     * The column to be used for sorting
33
     * @var string
34
     */
35
    protected $sortColumn = 'SortOrder';
36
37
    /**
38
     * Raw submitted form data
39
     * @var null|array
40
     */
41
    protected $rawSubmittal = null;
42
43
    /**
44
     * @var LoggerInterface
45
     */
46
    public $logger;
47
48
    public function getSchemaDataDefaults()
49
    {
50
        $defaults = parent::getSchemaDataDefaults();
51
        // Add a sortable prop for the react component
52
        $defaults['sortable'] = true;
53
        return $defaults;
54
    }
55
56
    /**
57
     * Set the column to be used for sorting
58
     * @param string $sortColumn
59
     * @return $this
60
     */
61
    public function setSortColumn($sortColumn)
62
    {
63
        $this->sortColumn = $sortColumn;
64
        return $this;
65
    }
66
67
    /**
68
     * Returns the column to be used for sorting
69
     * @return string
70
     */
71
    public function getSortColumn()
72
    {
73
        return $this->sortColumn;
74
    }
75
76
    /**
77
     * Return the files in sorted order
78
     * @return File[]|SS_List
79
     */
80
    public function getItems()
81
    {
82
        $items = parent::getItems();
83
84
        // An ArrayList won't contain our sort-column, thus it has to be sorted by the raw submittal data.
85
        // This is an issue that's seemingly exclusive to saving SiteConfig.
86
        if (($items instanceof ArrayList) && !empty($this->rawSubmittal)) {
87
            // flip the array, so that we can look up index by ID
88
            $sortLookup = array_flip($this->rawSubmittal);
89
            $itemsArray = $items->toArray();
90
            usort($itemsArray, function ($itemA, $itemB) use ($sortLookup) {
91
                if (isset($sortLookup[$itemA->ID]) && isset($sortLookup[$itemB->ID])) {
92
                    return $sortLookup[$itemA->ID] - $sortLookup[$itemB->ID];
93
                }
94
                return 0;
95
            });
96
97
            return ArrayList::create($itemsArray);
98
        }
99
100
        if ($items instanceof SS_List) {
101
            return $items->sort([$this->getSortColumn() => 'ASC', 'ID' => 'ASC']);
102
        }
103
104
        return $items;
105
    }
106
107
    public function saveInto(DataObjectInterface $record)
108
    {
109
        parent::saveInto($record);
110
111
        // Check required relation details are available
112
        $fieldname = $this->getName();
113
        if (!$fieldname || !is_array($this->rawSubmittal)) {
114
            return $this;
115
        }
116
117
        // Check type of relation
118
        $relation = $record->hasMethod($fieldname) ? $record->$fieldname() : null;
119
        if ($relation) {
120
            $idList = $this->getItemIDs();
121
            $rawList = $this->rawSubmittal;
122
            $sortColumn = $this->getSortColumn();
123
124
            if ($relation instanceof ManyManyList) {
125
                try {
126
                    // Apply the sorting, wrapped in a transaction.
127
                    // If something goes wrong, the DB will not contain invalid data
128
                    DB::get_conn()->withTransaction(function () use ($relation, $idList, $rawList, $record, $sortColumn) {
129
                        $this->sortManyManyRelation($relation, $idList, $rawList, $record, $sortColumn);
130
                    });
131
                } catch (\Exception $ex) {
132
                    $this->logger->warning('Unable to sort files in sortable relation.', ['exception' => $ex]);
133
                }
134
            } elseif ($relation instanceof ManyManyThroughList) {
135
                try {
136
                    // Apply the sorting, wrapped in a transaction.
137
                    // If something goes wrong, the DB will not contain invalid data
138
                    DB::get_conn()->withTransaction(function () use ($relation, $idList, $rawList, $sortColumn) {
139
                        $this->sortManyManyThroughRelation($relation, $idList, $rawList, $sortColumn);
140
                    });
141
                } catch (\Exception $ex) {
142
                    $this->logger->warning('Unable to sort files in sortable relation.', ['exception' => $ex]);
143
                }
144
            } elseif ($relation instanceof UnsavedRelationList) {
145
                // With an unsaved relation list the items can just be removed and re-added
146
                $sort = 0;
147
                $relation->removeAll();
148
                foreach ($rawList as $id) {
149
                    if (in_array($id, $idList)) {
150
                        $relation->add($id, [$sortColumn => $sort++]);
151
                    }
152
                }
153
            }
154
        }
155
156
        return $this;
157
    }
158
159
    public function setSubmittedValue($value, $data = null)
160
    {
161
        // Intercept the incoming IDs since they are properly sorted
162
        if (is_array($value) && isset($value['Files'])) {
163
            $this->rawSubmittal = $value['Files'];
164
        }
165
        return $this->setValue($value, $data);
166
    }
167
168
    /**
169
     * Apply sorting to a many_many relation
170
     * @param ManyManyList $relation
171
     * @param array $idList
172
     * @param array $rawList
173
     * @param DataObjectInterface $record
174
     * @param $sortColumn
175
     */
176
    protected function sortManyManyRelation(
177
        ManyManyList $relation,
178
        array $idList,
179
        array $rawList,
180
        DataObjectInterface $record,
181
        $sortColumn
182
    ) {
183
        $relation->getForeignID();
184
        $ownerIdField = $relation->getForeignKey();
185
        $fileIdField = $relation->getLocalKey();
186
        $joinTable = '"' . $relation->getJoinTable() . '"';
187
        $sort = 0;
188
        foreach ($rawList as $id) {
189
            if (in_array($id, $idList)) {
190
                // Use SQLUpdate to update the data in the join-table.
191
                // This is safe to do, since new records have already been written to the DB in the
192
                // parent::saveInto call.
193
                SQLUpdate::create($joinTable)
194
                    ->setWhere([
195
                        "\"$ownerIdField\" = ?" => $record->ID,
196
                        "\"$fileIdField\" = ?" => $id
197
                    ])
198
                    ->assign($sortColumn, $sort++)
199
                    ->execute();
200
            }
201
        }
202
    }
203
204
    /**
205
     * Apply sorting to a many_many_through relation
206
     * @param ManyManyThroughList $relation
207
     * @param array $idList
208
     * @param array $rawList
209
     * @param $sortColumn
210
     * @throws \SilverStripe\Core\Validation\ValidationException
211
     */
212
    protected function sortManyManyThroughRelation(
213
        ManyManyThroughList $relation,
214
        array $idList,
215
        array $rawList,
216
        $sortColumn
217
    ) {
218
        $relation->getForeignID();
219
        $dataQuery = $relation->dataQuery();
220
        $manipulators = $dataQuery->getDataQueryManipulators();
221
        $manyManyManipulator = null;
222
        foreach ($manipulators as $manipulator) {
223
            if ($manipulator instanceof ManyManyThroughQueryManipulator) {
224
                $manyManyManipulator = $manipulator;
225
                break;
226
            }
227
        }
228
229
        if (!$manyManyManipulator) {
230
            throw new \LogicException('No ManyManyThroughQueryManipulator found');
231
        }
232
233
        $joinClass = $manyManyManipulator->getJoinClass();
234
        $ownerIDField = $manyManyManipulator->getForeignKey();
235
        $fileIdField = $manyManyManipulator->getLocalKey();
236
237
        $sort = 0;
238
        foreach ($rawList as $id) {
239
            if (in_array($id, $idList)) {
240
                $fileRecord = DataList::create($joinClass)->filter([
241
                    $ownerIDField => $relation->getForeignID(),
242
                    $fileIdField  => $id
243
                ])->first();
244
245
                if ($fileRecord) {
246
                    $fileRecord->setField($sortColumn, $sort++);
247
                    $fileRecord->write();
248
                }
249
            }
250
        }
251
    }
252
}
253