ArchiveAdmin::createArchiveGridField()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 42
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 29
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 42
rs 9.456
1
<?php
2
3
namespace SilverStripe\VersionedAdmin;
4
5
use SilverStripe\Admin\ModelAdmin;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Core\ClassInfo;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Forms\DropdownField;
10
use SilverStripe\Forms\FieldList;
11
use SilverStripe\Forms\Form;
12
use SilverStripe\Forms\GridField\GridField;
13
use SilverStripe\Forms\GridField\GridField_ActionMenu;
14
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
15
use SilverStripe\Forms\GridField\GridFieldDataColumns;
16
use SilverStripe\Forms\GridField\GridFieldDetailForm;
17
use SilverStripe\Forms\GridField\GridFieldFilterHeader;
18
use SilverStripe\Forms\GridField\GridFieldViewButton;
19
use SilverStripe\ORM\ArrayList;
20
use SilverStripe\ORM\DataObject;
21
use SilverStripe\ORM\FieldType\DBDatetime;
22
use SilverStripe\Versioned\GridFieldRestoreAction;
23
use SilverStripe\Versioned\Versioned;
24
use SilverStripe\Versioned\VersionedGridFieldState\VersionedGridFieldState;
25
use SilverStripe\VersionedAdmin\Interfaces\ArchiveViewProvider;
26
use SilverStripe\View\ArrayData;
27
28
/**
29
 * Archive admin is a section of the CMS that displays archived records
30
 * from versioned objects and allows for users to restore them.
31
 *
32
 * Shows tabs for any implementors of {@link ArchiveViewProvider} and
33
 * display any other versioned objects in a dropdown
34
 */
35
class ArchiveAdmin extends ModelAdmin
36
{
37
    private static $url_segment = 'archive';
0 ignored issues
show
introduced by
The private property $url_segment is not used, and could be removed.
Loading history...
38
39
    private static $menu_title = 'Archives';
0 ignored issues
show
introduced by
The private property $menu_title is not used, and could be removed.
Loading history...
40
41
    private static $menu_icon_class = 'font-icon-box';
0 ignored issues
show
introduced by
The private property $menu_icon_class is not used, and could be removed.
Loading history...
42
43
    public $showSearchForm = false;
44
45
    protected function init()
46
    {
47
        parent::init();
48
    }
49
50
    /**
51
     * Produces an edit form with relevant prioritised tabs for Pages, Blocks and Files
52
     *
53
     * @param int|null $id
54
     * @param FieldList|null $fields
55
     * @return Form A Form object with one tab per {@link \SilverStripe\Forms\GridField\GridField}
56
     */
57
    public function getEditForm($id = null, $fields = null)
58
    {
59
        $fields = FieldList::create();
60
        $modelClass = $this->request->getVar('others') ? 'others' : $this->modelClass;
61
        $classInst = Injector::inst()->get($this->modelClass);
62
63
        if (ClassInfo::hasMethod($classInst, 'getArchiveField')) {
64
            $listField = $classInst->getArchiveField();
65
            $fields->push($listField);
66
        } else {
67
            $otherVersionedObjects = $this->getVersionedModels('other');
68
            $modelSelectField = $this->getOtherModelSelectorField($modelClass);
69
            $fields->push($modelSelectField);
70
71
            // If a valid other model name is passed via a request param
72
            // then show a gridfield with archived records
73
            if (array_search($modelClass, $otherVersionedObjects)) {
74
                $listField = $this->createArchiveGridField('Others', $modelClass);
75
76
                $listColumns = $listField->getConfig()->getComponentByType(GridFieldDataColumns::class);
77
                $listColumns->setDisplayFields([
0 ignored issues
show
Bug introduced by
The method setDisplayFields() does not exist on SilverStripe\Forms\GridField\GridFieldComponent. It seems like you code against a sub-type of SilverStripe\Forms\GridField\GridFieldComponent such as SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldDataColumns or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\Tests...ndlerTest\TestComponent or SilverStripe\Forms\GridField\GridFieldDetailForm. ( Ignorable by Annotation )

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

77
                $listColumns->/** @scrutinizer ignore-call */ 
78
                              setDisplayFields([
Loading history...
78
                    'Title' => _t(__CLASS__ . '.COLUMN_TITLE', 'Title'),
79
                    'Versions.first.LastEdited' => _t(__CLASS__ . '.COLUMN_DATEARCHIVED', 'Date Archived'),
80
                    'Versions.first.Author.Name' => _t(__CLASS__ . '.COLUMN_ARCHIVEDBY', 'Archived By'),
81
                ]);
82
                $listColumns->setFieldFormatting([
0 ignored issues
show
Bug introduced by
The method setFieldFormatting() does not exist on SilverStripe\Forms\GridField\GridFieldComponent. It seems like you code against a sub-type of SilverStripe\Forms\GridField\GridFieldComponent such as SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldDataColumns or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\Tests...ndlerTest\TestComponent or SilverStripe\Forms\GridField\GridFieldDetailForm. ( Ignorable by Annotation )

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

82
                $listColumns->/** @scrutinizer ignore-call */ 
83
                              setFieldFormatting([
Loading history...
83
                    'Versions.first.LastEdited' => function ($val, $item) {
0 ignored issues
show
Unused Code introduced by
The parameter $item is not used and could be removed. ( Ignorable by Annotation )

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

83
                    'Versions.first.LastEdited' => function ($val, /** @scrutinizer ignore-unused */ $item) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
                        return DBDatetime::create_field('Datetime', $val)->Ago();
85
                    },
86
                ]);
87
88
                $fields->push($listField);
89
            }
90
        }
91
92
        $form = Form::create(
93
            $this,
94
            'EditForm',
95
            $fields,
96
            FieldList::create()
97
        )->setHTMLID('Form_EditForm');
98
        $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
99
        $form->setAttribute('data-pjax-fragment', 'CurrentForm');
100
        $form->addExtraClass(
101
            'ArchiveAdmin discardchanges cms-edit-form cms-panel-padded center flexbox-area-grow ' .
102
            $this->BaseCSSClasses()
103
        );
104
        $form->setFormAction(Controller::join_links(
105
            $this->Link($this->sanitiseClassName($this->modelClass)),
106
            'EditForm'
107
        ));
108
109
        $this->extend('updateEditForm', $form);
110
111
        return $form;
112
    }
113
114
    /**
115
     * Create a gridfield which displays archived objects
116
     *
117
     * @param string $title
118
     * @param string $class
119
     * @return GridField
120
     */
121
    public static function createArchiveGridField($title, $class)
122
    {
123
        $config = GridFieldConfig_Base::create();
124
        $config->removeComponentsByType(VersionedGridFieldState::class);
125
        $config->removeComponentsByType(GridFieldFilterHeader::class);
126
        $config->addComponent(new GridFieldDetailForm());
127
        $config->addComponent(new GridFieldViewButton());
128
        $config->addComponent(new GridFieldRestoreAction());
129
        $config->addComponent(new GridField_ActionMenu());
130
131
        $list = singleton($class)->get();
132
        $baseTable = singleton($list->dataClass())->baseTable();
133
        $liveTable = $baseTable . '_Live';
134
135
        $list = $list
136
            ->setDataQueryParam('Versioned.mode', 'latest_versions');
137
        // Join a temporary alias BaseTable_Draft, renaming this on execution to BaseTable
138
        // See Versioned::augmentSQL() For reference on this alias
139
        $draftTable = $baseTable . '_Draft';
140
        $list = $list
141
            ->leftJoin(
142
                $draftTable,
143
                "\"{$baseTable}\".\"ID\" = \"{$draftTable}\".\"ID\""
144
            );
145
146
        $list = $list->leftJoin(
147
            $liveTable,
148
            "\"{$baseTable}\".\"ID\" = \"{$liveTable}\".\"ID\""
149
        );
150
151
        $list = $list->where("\"{$draftTable}\".\"ID\" IS NULL");
152
        $list = $list->sort('LastEdited DESC');
153
154
        $field = GridField::create(
155
            $title,
156
            false,
157
            $list,
158
            $config
159
        );
160
        $field->setModelClass($class);
161
162
        return $field;
163
    }
164
165
    /**
166
     * Returns versioned objects, can be filtered for 'main' (has a tab)
167
     * or 'other' and is exposed through the 'Others' tab, returns all
168
     * by default
169
     *
170
     * @param string|null $filter Filter by 'main' or 'other'
171
     * @param boolean $forDisplay Include titles as values in the returned array
172
     * @return array
173
     */
174
    public function getVersionedModels($filter = null, $forDisplay = false)
175
    {
176
        // Get dataobjects with staged versioning
177
        $versionedClasses = array_filter(
178
            ClassInfo::subclassesFor(DataObject::class),
179
            function ($class) {
180
                return (
181
                    DataObject::has_extension($class, Versioned::class) &&
182
                    DataObject::singleton($class)->hasStages()
183
                );
184
            }
185
        );
186
187
        $archiveProviders = ClassInfo::implementorsOf(ArchiveViewProvider::class);
188
189
        $disabledHandledClasses = [];
190
191
        // Get the classes that are declared as handled by the disabled providers
192
        foreach ($archiveProviders as $provider) {
193
            if (!Injector::inst()->get($provider)->isArchiveFieldEnabled()) {
194
                $disabledProviderClass = Injector::inst()->get($provider)->getArchiveFieldClass();
195
                $disabledHandledClasses[] = $disabledProviderClass;
196
197
                $disabledHandledClasses = array_merge(
198
                    $disabledHandledClasses,
199
                    array_keys(ClassInfo::subclassesFor($disabledProviderClass))
200
                );
201
            }
202
        }
203
204
        // Remove any subclasses that would also be handled by those disabled providers
205
        $versionedClasses = array_diff_key($versionedClasses, array_flip($disabledHandledClasses));
206
207
        // If there is a valid filter passed
208
        if ($filter && in_array($filter, ['main', 'other'])) {
209
            $archiveProviderClasses = [];
210
211
            // Get the classes that are decalred as handled by ArchiveViewProviders
212
            foreach ($archiveProviders as $provider) {
213
                $archiveProviderClass = Injector::inst()->get($provider)->getArchiveFieldClass();
214
                $archiveProviderClasses[] = $archiveProviderClass;
215
            }
216
217
            switch ($filter) {
218
                case 'other':
219
                    $handledClasses = [];
220
                    // Get any subclasses that would also be handled by those providers
221
                    foreach ($archiveProviderClasses as $archiveProviderClass) {
222
                        $handledClasses = array_merge(
223
                            $handledClasses,
224
                            array_keys(ClassInfo::subclassesFor($archiveProviderClass))
225
                        );
226
                    }
227
                    $versionedClasses = array_filter(
228
                        $versionedClasses,
229
                        function ($class) use ($handledClasses) {
230
                            return !in_array(strtolower($class), $handledClasses);
231
                        }
232
                    );
233
                    break;
234
                default: // 'main'
235
                    $versionedClasses = array_filter(
236
                        $versionedClasses,
237
                        function ($class) use ($archiveProviderClasses) {
238
                            return in_array($class, $archiveProviderClasses);
239
                        }
240
                    );
241
                    break;
242
            }
243
        }
244
245
        // Formats array as [$className => i18n_plural_name]
246
        if ($forDisplay) {
247
            $versionedClasses = array_flip($versionedClasses);
248
249
            foreach (array_keys($versionedClasses) as $className) {
250
                $versionedClasses[$className] = ucfirst($className::singleton()->i18n_plural_name());
251
            }
252
        }
253
254
        return $versionedClasses;
255
    }
256
257
    /**
258
     * Creates a dropdown field that displays other archived models
259
     *
260
     * @param string $currentModel The model that is currently selected
261
     * @return DropdownField
262
     */
263
    public function getOtherModelSelectorField($currentModel = '')
264
    {
265
        $otherVersionedObjects = $this->getVersionedModels('other', true);
266
267
        $modelSelectField = DropdownField::create(
268
            'OtherDropdown',
269
            _t(__CLASS__ . '.SELECT_TYPE', 'Select a content type'),
270
            $otherVersionedObjects,
271
            $currentModel
272
        );
273
        $modelSelectField->setAttribute(
274
            'data-others-archive-url',
275
            $this->Link('/')
276
        );
277
        $modelSelectField->addExtraClass('other-model-selector');
278
        $modelSelectField->setEmptyString(_t(__CLASS__ . '.SELECT_EMPTY', 'Select…'));
279
        $modelSelectField->setHasEmptyDefault(true);
280
281
        return $modelSelectField;
282
    }
283
284
    /**
285
     * Use 'Archives' as the top title rather than the model title
286
     *
287
     * @param bool $unlinked
288
     * @return ArrayList
289
     */
290
    public function Breadcrumbs($unlinked = false)
291
    {
292
        $items = parent::Breadcrumbs($unlinked);
293
294
        $items[0]->Title = $this->menu_title();
295
296
        return $items;
297
    }
298
299
    /**
300
     * Archive admin needs some extra logic for whether an archive tab should be shown
301
     *
302
     * @return array Map of class name to an array of 'title' (see {@link $managed_models})
303
     */
304
    public function getManagedModels()
305
    {
306
        $models = $this->getVersionedModels();
307
308
        // Normalize models to have their model class in array key and all names as the value are uppercased
309
        foreach ($models as $k => $v) {
310
            $archivedModels[$v] = array('title' => ucfirst(singleton($v)->i18n_plural_name()));
311
            unset($archivedModels[$k]);
312
        }
313
314
        return $archivedModels;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $archivedModels seems to be defined by a foreach iteration on line 309. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
315
    }
316
317
    /**
318
     * Add the special 'Others' tab
319
     *
320
     * @return ArrayList An ArrayList of all managed models to build the tabs for this ModelAdmin
321
     */
322
    public function getManagedModelTabs()
323
    {
324
        $forms = ArrayList::create();
325
        $mainModels = $this->getVersionedModels('main', true);
326
327
        foreach ($mainModels as $class => $title) {
328
            $classInst = Injector::inst()->get($class);
329
            if (
330
                ClassInfo::hasMethod($classInst, 'isArchiveFieldEnabled')
331
                && $classInst->isArchiveFieldEnabled()
332
            ) {
333
                $forms->push(ArrayData::create([
334
                    'Title' => $title,
335
                    'ClassName' => $class,
336
                    'Link' => $this->Link($this->sanitiseClassName($class)),
337
                    'LinkOrCurrent' => ($class === $this->modelClass) ? 'current' : 'link'
338
                ]));
339
            }
340
        }
341
342
        $otherModels = $this->getVersionedModels('other', true);
343
        if ($otherModels) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $otherModels of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
344
            $isOtherActive = (
345
                $this->request->getVar('others') !== null ||
346
                array_key_exists($this->modelClass, $otherModels)
347
            );
348
            $forms->push(ArrayData::create([
349
                'Title' => _t(__CLASS__ . '.TAB_OTHERS', 'Others'),
350
                'ClassName' => 'Others',
351
                'Link' => $this->Link('?others=1'),
352
                'LinkOrCurrent' => ($isOtherActive ? 'current' : 'link')
353
            ]));
354
        }
355
356
        $forms->first()->LinkOrCurrent = 'link';
357
358
        return $forms;
359
    }
360
}
361