Passed
Push — master ( 091915...c8a845 )
by Roman
03:50
created

SortableUploadField::getSchemaDataDefaults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
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\ORM\ArrayList;
9
use SilverStripe\ORM\DataObjectInterface;
10
use SilverStripe\ORM\DB;
11
use SilverStripe\ORM\ManyManyList;
12
use SilverStripe\ORM\Queries\SQLUpdate;
13
use SilverStripe\ORM\Sortable;
14
use SilverStripe\ORM\SS_List;
15
use SilverStripe\ORM\UnsavedRelationList;
16
17
/**
18
 * Extension of the UploadField to add sorting of files
19
 *
20
 * @author bummzack
21
 * @skipUpgrade
22
 */
23
class SortableUploadField extends UploadField
24
{
25
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
26
        'logger' => '%$Psr\Log\LoggerInterface',
27
    ];
28
29
    /**
30
     * The column to be used for sorting
31
     * @var string
32
     */
33
    protected $sortColumn = 'SortOrder';
34
35
    /**
36
     * Raw submitted form data
37
     * @var null|array
38
     */
39
    protected $rawSubmittal = null;
40
41
    /**
42
     * @var LoggerInterface
43
     */
44
    public $logger;
45
46
    public function getSchemaDataDefaults()
47
    {
48
        $defaults = parent::getSchemaDataDefaults();
49
        // Add a sortable prop for the react component
50
        $defaults['sortable'] = true;
51
        return $defaults;
52
    }
53
54
    /**
55
     * Set the column to be used for sorting
56
     * @param string $sortColumn
57
     * @return $this
58
     */
59
    public function setSortColumn($sortColumn)
60
    {
61
        $this->sortColumn = $sortColumn;
62
        return $this;
63
    }
64
65
    /**
66
     * Returns the column to be used for sorting
67
     * @return string
68
     */
69
    public function getSortColumn()
70
    {
71
        return $this->sortColumn;
72
    }
73
74
    /**
75
     * Return the files in sorted order
76
     * @return File[]|SS_List
77
     */
78
    public function getItems()
79
    {
80
        $items = parent::getItems();
81
82
        // An ArrayList won't contain our sort-column, thus it has to be sorted by the raw submittal data.
83
        // This is an issue that's seemingly exclusive to saving SiteConfig.
84
        if (($items instanceof ArrayList) && !empty($this->rawSubmittal)) {
85
            // flip the array, so that we can look up index by ID
86
            $sortLookup = array_flip($this->rawSubmittal);
87
            $itemsArray = $items->toArray();
88
            usort($itemsArray, function ($itemA, $itemB) use ($sortLookup) {
89
                if (isset($sortLookup[$itemA->ID]) && isset($sortLookup[$itemB->ID])) {
90
                    return $sortLookup[$itemA->ID] - $sortLookup[$itemB->ID];
91
                }
92
                return 0;
93
            });
94
95
            return ArrayList::create($itemsArray);
96
        }
97
98
        if ($items instanceof Sortable) {
99
            return $items->sort([$this->getSortColumn() => 'ASC', 'ID' => 'ASC']);
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\ORM\Sortable::sort() has too many arguments starting with array($this->getSortColu...> 'ASC', 'ID' => 'ASC'). ( Ignorable by Annotation )

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

99
            return $items->/** @scrutinizer ignore-call */ sort([$this->getSortColumn() => 'ASC', 'ID' => 'ASC']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
100
        }
101
102
        return $items;
103
    }
104
105
    public function saveInto(DataObjectInterface $record)
106
    {
107
        parent::saveInto($record);
108
109
        // Check required relation details are available
110
        $fieldname = $this->getName();
111
        if (!$fieldname || !is_array($this->rawSubmittal)) {
112
            return $this;
113
        }
114
115
        // Check type of relation
116
        $relation = $record->hasMethod($fieldname) ? $record->$fieldname() : null;
117
        if ($relation) {
118
            $idList = $this->getItemIDs();
119
            $rawList = $this->rawSubmittal;
120
            $sortColumn = $this->getSortColumn();
121
122
            if ($relation instanceof ManyManyList) {
123
                try {
124
                    DB::get_conn()->withTransaction(function () use ($relation, $idList, $rawList, $record, $sortColumn) {
125
                        $relation->getForeignID();
126
                        $ownerIdField = $relation->getForeignKey();
127
                        $fileIdField = $relation->getLocalKey();
128
                        $joinTable = '"'. $relation->getJoinTable() .'"';
129
130
                        $sort = 0;
131
                        foreach ($rawList as $id) {
132
                            if (in_array($id, $idList)) {
133
                                // Use SQLUpdate to update the data in the join-table.
134
                                // This is safe to do, since new records have already been written to the DB in the
135
                                // parent::saveInto call.
136
                                SQLUpdate::create($joinTable)
137
                                    ->setWhere([
138
                                        "\"$ownerIdField\" = ?" => $record->ID,
139
                                        "\"$fileIdField\" = ?" => $id
140
                                    ])
141
                                    ->assign($sortColumn, $sort++)
142
                                    ->execute();
143
                            }
144
                        }
145
                    });
146
                } catch (\Exception $ex) {
147
                    $this->logger->warning('Unable to sort files in sortable relation.', ['exception' => $ex]);
148
                }
149
            } elseif ($relation instanceof UnsavedRelationList) {
150
                // With an unsaved relation list the items can just be removed and re-added
151
                $sort = 0;
152
                $relation->removeAll();
153
                foreach ($rawList as $id) {
154
                    if (in_array($id, $idList)) {
155
                        $relation->add($id, [$sortColumn => $sort++]);
156
                    }
157
                }
158
            }
159
        }
160
161
        return $this;
162
    }
163
164
    public function setSubmittedValue($value, $data = null)
165
    {
166
        // Intercept the incoming IDs since they are properly sorted
167
        if (is_array($value) && isset($value['Files'])) {
168
            $this->rawSubmittal = $value['Files'];
169
        }
170
        return $this->setValue($value, $data);
171
    }
172
}
173