Passed
Push — master ( b101db...a45b24 )
by Robbie
05:17
created

ArchiveAdmin::getOtherModelSelectorField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 1
dl 0
loc 19
rs 9.8333
c 0
b 0
f 0
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\Injectable;
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\GridFieldViewButton;
18
use SilverStripe\ORM\ArrayList;
19
use SilverStripe\ORM\DataObject;
20
use SilverStripe\Security\Member;
21
use SilverStripe\Versioned\GridFieldRestoreAction;
22
use SilverStripe\Versioned\Versioned;
23
use SilverStripe\Versioned\VersionedGridFieldState\VersionedGridFieldState;
24
use SilverStripe\VersionedAdmin\ArchiveViewProvider;
25
use SilverStripe\View\ArrayData;
26
use SilverStripe\View\Requirements;
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
        Requirements::javascript('silverstripe/versioned-admin:client/dist/js/bundle.js');
50
        Requirements::css('silverstripe/versioned-admin:client/dist/styles/bundle.css');
51
        Requirements::javascript('silverstripe/cms: client/dist/js/bundle.js');
52
        Requirements::css('silverstripe/cms: client/dist/styles/bundle.css');
53
    }
54
55
    /**
56
     * Produces an edit form with relevant prioritised tabs for Pages, Blocks and Files
57
     *
58
     * @param int|null $id
59
     * @param FieldList|null $fields
60
     * @return Form A Form object with one tab per {@link \SilverStripe\Forms\GridField\GridField}
61
     */
62
    public function getEditForm($id = null, $fields = null)
63
    {
64
        $fields = new FieldList();
65
        $modelClass = $this->request->getVar('others') ? 'others' : $this->modelClass;
66
67
        if (ClassInfo::hasMethod(Injectable::singleton($this->modelClass), 'getArchiveField')) {
68
            $listField = Injectable::singleton($this->modelClass)->getArchiveField();
69
            $fields->push($listField);
70
        } else {
71
            $otherVersionedObjects = $this->getVersionedModels('other');
72
            $modelSelectField = $this->getOtherModelSelectorField($modelClass);
73
            $fields->push($modelSelectField);
74
75
            // If a valid other model name is passed via a request param
76
            // then show a gridfield with archived records
77
            if (array_search($modelClass, $otherVersionedObjects)) {
78
                $listField = $this->createArchiveGridField('Others', $modelClass);
79
80
                $listColumns = $listField->getConfig()->getComponentByType(GridFieldDataColumns::class);
81
                $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

81
                $listColumns->/** @scrutinizer ignore-call */ 
82
                              setDisplayFields([
Loading history...
82
                    'Title' => _t(__CLASS__ . '.COLUMN_TITLE', 'Title'),
83
                    'LastEdited.Ago' => _t(__CLASS__ . '.COLUMN_DATEARCHIVED', 'Date Archived'),
84
                    'AuthorID' => _t(__CLASS__ . '.COLUMN_ARCHIVEDBY', 'Archived By'),
85
                ]);
86
                $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

86
                $listColumns->/** @scrutinizer ignore-call */ 
87
                              setFieldFormatting([
Loading history...
87
                    'AuthorID' => 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

87
                    'AuthorID' => 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...
88
                        $member = Member::get_by_id($val);
89
                        return $member ? $member->Name : null;
0 ignored issues
show
introduced by
$member is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
90
                    },
91
                ]);
92
93
                $fields->push($listField);
94
            }
95
        }
96
97
        $form = Form::create(
98
            $this,
99
            'EditForm',
100
            $fields,
101
            FieldList::create()
102
        )->setHTMLID('Form_EditForm');
103
        $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
104
        $form->setAttribute('data-pjax-fragment', 'CurrentForm');
105
        $form->addExtraClass(
106
            'ArchiveAdmin discardchanges cms-edit-form cms-panel-padded center flexbox-area-grow '.
107
            $this->BaseCSSClasses()
108
        );
109
        $form->setFormAction(Controller::join_links(
110
            $this->Link($this->sanitiseClassName($this->modelClass)),
111
            'EditForm'
112
        ));
113
114
        $this->extend('updateEditForm', $form);
115
116
        return $form;
117
    }
118
119
    /**
120
     * Create a gridfield which displays archived objects
121
     *
122
     * @param string $title
123
     * @param string $class
124
     * @return GridField
125
     */
126
    public static function createArchiveGridField($title, $class)
127
    {
128
        $config = GridFieldConfig_Base::create();
129
        $config->removeComponentsByType(VersionedGridFieldState::class);
130
        $config->addComponent(new GridFieldDetailForm);
131
        $config->addComponent(new GridFieldViewButton);
132
        $config->addComponent(new GridFieldRestoreAction);
133
        $config->addComponent(new GridField_ActionMenu);
134
135
        $items = Versioned::get_including_deleted($class);
136
        $items = $items->filterByCallback(function ($item) {
137
            return $item->isArchived();
138
        });
139
140
        $field = GridField::create(
141
            $title,
142
            false,
143
            $items->sort('LastEdited DESC'),
144
            $config
145
        );
146
        $field->setModelClass($class);
147
148
        return $field;
149
    }
150
151
    /**
152
     * Returns versioned objects, can be filtered for 'main' (has a tab)
153
     * or 'other' and is exposed through the 'Others' tab, returns all
154
     * by default
155
     *
156
     * @param string|null $filter Filter by 'main' or 'other'
157
     * @param boolean $forDisplay Include titles as values in the returned array
158
     * @return array
159
     */
160
    public function getVersionedModels($filter = null, $forDisplay = false)
161
    {
162
        // Get dataobjects with staged versioning
163
        $versionedClasses = array_filter(
164
            ClassInfo::subclassesFor(DataObject::class),
165
            function ($class) {
166
                return (
167
                    DataObject::has_extension($class, Versioned::class) &&
168
                    DataObject::singleton($class)->hasStages()
169
                );
170
            }
171
        );
172
173
        // If there is a valid filter passed
174
        if ($filter && in_array($filter, ['main', 'other'])) {
175
            $archiveProviders = ClassInfo::implementorsOf(ArchiveViewProvider::class);
176
            $archiveProviderClasses = [];
177
178
            // Get the classes that are decalred as handled by ArchiveViewProviders
179
            foreach ($archiveProviders as $provider) {
180
                $archiveProviderClass = Injectable::singleton($provider)->getArchiveFieldClass();
181
                $archiveProviderClasses[] = $archiveProviderClass;
182
            }
183
184
            switch ($filter) {
185
                case 'other':
186
                    $handledClasses = [];
187
                    // Get any subclasses that would also be handled by those providers
188
                    foreach ($archiveProviderClasses as $archiveProviderClass) {
189
                        $handledClasses = array_merge(
190
                            $handledClasses,
191
                            array_keys(ClassInfo::subclassesFor($archiveProviderClass))
192
                        );
193
                    }
194
                    $versionedClasses = array_filter(
195
                        $versionedClasses,
196
                        function ($class) use ($handledClasses) {
197
                            return !in_array(strtolower($class), $handledClasses);
198
                        }
199
                    );
200
                    break;
201
                default: // 'main'
202
                    $versionedClasses = array_filter(
203
                        $versionedClasses,
204
                        function ($class) use ($archiveProviderClasses) {
205
                            return in_array($class, $archiveProviderClasses);
206
                        }
207
                    );
208
                    break;
209
            }
210
        }
211
212
        // Formats array as [$className => i18n_plural_name]
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
213
        if ($forDisplay) {
214
            $versionedClasses = array_flip($versionedClasses);
215
216
            foreach (array_keys($versionedClasses) as $className) {
217
                $versionedClasses[$className] = ucfirst($className::singleton()->i18n_plural_name());
218
            }
219
        }
220
221
        return $versionedClasses;
222
    }
223
224
    /**
225
     * Creates a dropdown field that displays other archived models
226
     *
227
     * @param string $currentModel The model that is currently selected
228
     * @return DropdownField
229
     */
230
    public function getOtherModelSelectorField($currentModel = '')
231
    {
232
        $otherVersionedObjects = $this->getVersionedModels('other', true);
233
234
        $modelSelectField = DropdownField::create(
235
            'OtherDropdown',
236
            _t(__CLASS__ . '.SELECT_TYPE', 'Select a content type'),
237
            $otherVersionedObjects,
238
            $currentModel
239
        );
240
        $modelSelectField->setAttribute(
241
            'data-others-archive-url',
242
            $this->Link('/')
243
        );
244
        $modelSelectField->addExtraClass('other-model-selector');
245
        $modelSelectField->setEmptyString(_t(__CLASS__ . '.SELECT_EMPTY', 'Select…'));
246
        $modelSelectField->setHasEmptyDefault(true);
247
248
        return $modelSelectField;
249
    }
250
251
    /**
252
     * Use 'Archives' as the top title rather than the model title
253
     *
254
     * @param bool $unlinked
255
     * @return ArrayList
256
     */
257
    public function Breadcrumbs($unlinked = false)
258
    {
259
        $items = parent::Breadcrumbs($unlinked);
260
261
        $items[0]->Title = $this->menu_title();
262
263
        return $items;
264
    }
265
266
    /**
267
     * Archive admin needs some extra logic for whether an archive tab should be shown
268
     *
269
     * @return array Map of class name to an array of 'title' (see {@link $managed_models})
270
     */
271
    public function getManagedModels()
272
    {
273
        $models = $this->getVersionedModels();
274
275
        // Normalize models to have their model class in array key and all names as the value are uppercased
276
        foreach ($models as $k => $v) {
277
            $archivedModels[$v] = array('title' => ucfirst(singleton($v)->i18n_plural_name()));
278
            unset($archivedModels[$k]);
279
        }
280
281
        return $archivedModels;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $archivedModels seems to be defined by a foreach iteration on line 276. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
282
    }
283
284
    /**
285
     * Add the special 'Others' tab
286
     *
287
     * @return ArrayList An ArrayList of all managed models to build the tabs for this ModelAdmin
288
     */
289
    public function getManagedModelTabs()
290
    {
291
        $forms = new ArrayList();
292
293
        $mainModels = $this->getVersionedModels('main', true);
294
        foreach ($mainModels as $class => $title) {
295
            $forms->push(new ArrayData(array(
296
                'Title' => $title,
297
                'ClassName' => $class,
298
                'Link' => $this->Link($this->sanitiseClassName($class)),
299
                'LinkOrCurrent' => ($class === $this->modelClass) ? 'current' : 'link'
300
            )));
301
        }
302
303
        $otherModels = $this->getVersionedModels('other', true);
304
        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...
305
            $isOtherActive = (
306
                $this->request->getVar('others') !== null ||
307
                array_key_exists($this->modelClass, $otherModels)
308
            );
309
            $forms->push(new ArrayData([
310
                'Title' => _t(__CLASS__ . '.TAB_OTHERS', 'Others'),
311
                'ClassName' => 'Others',
312
                'Link' => $this->Link('?others=1'),
313
                'LinkOrCurrent' => ($isOtherActive ? 'current' : 'link')
314
            ]));
315
        }
316
317
        $forms->first()->LinkOrCurrent = 'link';
318
319
        return $forms;
320
    }
321
}
322