ModelAdminExcelExtension   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 4
Metric Value
eloc 117
dl 0
loc 225
rs 9.44
c 9
b 0
f 4
wmc 37

6 Methods

Rating   Name   Duplication   Size   Complexity  
A downloadsample() 0 8 4
A onAfterInit() 0 3 1
A updateModelImporters() 0 23 4
B updateGridFieldConfig() 0 43 10
D updateImportForm() 0 94 16
A getGridFieldExportButton() 0 8 2
1
<?php
2
3
namespace LeKoala\ExcelImportExport\Extensions;
4
5
use SilverStripe\Forms\Form;
6
use SilverStripe\Core\Extension;
7
use SilverStripe\Admin\ModelAdmin;
8
use SilverStripe\Forms\CheckboxField;
9
use SilverStripe\Forms\OptionsetField;
10
use LeKoala\ExcelImportExport\ExcelBulkLoader;
11
use LeKoala\ExcelImportExport\ExcelImportExport;
12
use SilverStripe\Forms\GridField\GridFieldConfig;
13
use SilverStripe\Forms\GridField\GridFieldExportButton;
14
use SilverStripe\Forms\GridField\GridFieldImportButton;
15
use LeKoala\ExcelImportExport\ExcelGridFieldExportButton;
16
use LeKoala\ExcelImportExport\ExcelGridFieldImportButton;
17
use LeKoala\ExcelImportExport\ExcelGroupBulkLoader;
18
use LeKoala\ExcelImportExport\ExcelMemberBulkLoader;
19
use SilverStripe\Admin\SecurityAdmin;
20
21
/**
22
 * Extends {@link ModelAdmin}. to bind new forms and features
23
 *
24
 * @property \SilverStripe\Admin\LeftAndMain $owner
25
 * @author Koala
26
 */
27
class ModelAdminExcelExtension extends Extension
28
{
29
    /**
30
     * @var array<string>
31
     */
32
    private static $allowed_actions = array(
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
33
        'downloadsample'
34
    );
35
36
    /**
37
     * @return void
38
     */
39
    public function onAfterInit()
40
    {
41
        $this->updateModelImporters();
42
    }
43
44
    /**
45
     * Replace default model import with a predefined value
46
     *
47
     * @return void
48
     */
49
    protected function updateModelImporters()
50
    {
51
        $owner = $this->owner;
52
        $config = $this->owner::config();
53
54
        // Overwrite model imports
55
        $importerClasses = $config->get('model_importers');
56
57
        if ($owner instanceof SecurityAdmin) {
58
            $importerClasses = [
59
                "users" => ExcelMemberBulkLoader::class,
60
                "SilverStripe\Security\Member" => ExcelMemberBulkLoader::class,
61
                "groups" => ExcelGroupBulkLoader::class,
62
                "SilverStripe\Security\Group" => ExcelGroupBulkLoader::class,
63
            ];
64
            $config->set('model_importers', $importerClasses);
65
        } elseif (is_null($importerClasses)) {
66
            $models = $owner->getManagedModels();
67
            foreach (array_keys($models) as $modelName) {
68
                $importerClasses[$modelName] = ExcelBulkLoader::class;
69
            }
70
71
            $config->set('model_importers', $importerClasses);
72
        }
73
    }
74
75
    /**
76
     * @return void
77
     */
78
    public function downloadsample()
79
    {
80
        $owner = $this->owner;
81
        $importer = $owner->getRequest()->getVar('importer');
82
        if ($importer && class_exists($importer) && method_exists($importer, 'getSampleFile')) {
83
            $importer::getSampleFile();
84
        } else {
85
            ExcelImportExport::sampleFileForClass($owner->getModelClass());
86
        }
87
    }
88
89
    /**
90
     * @param GridFieldConfig $config
91
     * @return void
92
     */
93
    public function updateGridFieldConfig(GridFieldConfig $config)
94
    {
95
        $owner = $this->owner;
96
        $class = $owner->getModelClass();
97
        $classConfig = $owner->config();
98
99
        // Add/remove csv export. Replace with our own implementation if necessary.
100
        if ($classConfig->export_csv) {
101
            $GridFieldExportButton = $this->getGridFieldExportButton($config);
102
            if ($GridFieldExportButton) {
103
                if ($classConfig->use_framework_csv) {
104
                    $GridFieldExportButton->setExportColumns(ExcelImportExport::exportFieldsForClass($class));
105
                } else {
106
                    $config->removeComponentsByType(GridFieldExportButton::class);
107
                    $ExcelGridFieldExportButton = new ExcelGridFieldExportButton('buttons-before-left');
108
                    $ExcelGridFieldExportButton->setExportType('csv');
109
                    $config->addComponent($ExcelGridFieldExportButton);
110
                }
111
            }
112
        } else {
113
            $config->removeComponentsByType(GridFieldExportButton::class);
114
        }
115
116
        // Add/remove csv export (add by default)
117
        if ($classConfig->export_excel) {
118
            $ExcelGridFieldExportButton = new ExcelGridFieldExportButton('buttons-before-left');
119
            $config->addComponent($ExcelGridFieldExportButton);
120
        }
121
122
        // Rename import button
123
        $config->removeComponentsByType(GridFieldImportButton::class);
124
        if ((is_bool($owner->showImportForm)
125
                && $owner->showImportForm)
126
            ||
127
            (is_array($owner->showImportForm)
128
                && in_array($class, $owner->showImportForm))
129
        ) {
130
            $importForm = $owner->ImportForm();
131
            if ($importForm) {
132
                $ExcelGridFieldImportButton = new ExcelGridFieldImportButton('buttons-before-left');
133
                $ExcelGridFieldImportButton->setImportForm($importForm);
134
                $ExcelGridFieldImportButton->setModalTitle(_t('ExcelImportExport.IMPORTFROMFILE', 'Import from a file'));
135
                $config->addComponent($ExcelGridFieldImportButton);
136
            }
137
        }
138
    }
139
140
    /**
141
     * @param \SilverStripe\Forms\GridField\GridFieldConfig $config
142
     * @return GridFieldExportButton|null
143
     */
144
    protected function getGridFieldExportButton($config)
145
    {
146
        /** @var GridFieldExportButton|null $comp */
147
        $comp = $config->getComponentByType(GridFieldExportButton::class);
148
        if ($comp) {
149
            return $comp;
150
        }
151
        return null;
152
    }
153
154
    /**
155
     * @param Form $form
156
     * @return void
157
     */
158
    public function updateImportForm(Form $form)
159
    {
160
        $owner = $this->owner;
161
        $class = $owner->getModelClass();
162
        $classConfig = $owner->config();
163
164
        $modelSNG = singleton($class);
165
        $modelConfig = $modelSNG->config();
166
        $modelName = $modelSNG->i18n_singular_name();
167
168
        $fields = $form->Fields();
169
170
        // We can implement a custom handler
171
        $importHandlers = [];
172
        $htmlDesc = '';
173
        $useDefaultSample = true;
174
        if ($modelSNG->hasMethod('listImportHandlers')) {
175
            $importHandlers = array_merge([
176
                'default' => _t('ExcelImportExport.DefaultHandler', 'Default import handler'),
177
            ], $modelSNG->listImportHandlers());
178
179
            $supportOnlyUpdate = [];
180
            foreach ($importHandlers as $class => $label) {
181
                if (!class_exists($class)) {
182
                    continue;
183
                }
184
                if (method_exists($class, 'setOnlyUpdate')) {
185
                    $supportOnlyUpdate[] = $class;
186
                }
187
                if (method_exists($class, 'getImportDescription')) {
188
                    $htmlDesc .= '<div class="js-import-desc" data-name="' . $class . '" hidden>' . $class::getImportDescription() . '</div>';
0 ignored issues
show
Bug introduced by
Are you sure $class of type object can be used in concatenation? ( Ignorable by Annotation )

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

188
                    $htmlDesc .= '<div class="js-import-desc" data-name="' . /** @scrutinizer ignore-type */ $class . '" hidden>' . $class::getImportDescription() . '</div>';
Loading history...
189
                }
190
                if (method_exists($class, 'getSampleFileLink')) {
191
                    $useDefaultSample = false;
192
                    $htmlDesc .= '<div class="js-import-desc" data-name="' . $class . '" hidden>' . $class::getSampleFileLink() . '</div>';
193
                }
194
            }
195
196
            if (!$useDefaultSample) {
0 ignored issues
show
introduced by
The condition $useDefaultSample is always true.
Loading history...
197
                $htmlDesc = '<div class="js-import-desc" data-name="default" hidden>' . ExcelImportExport::createDownloadSampleLink() . '</div>' . $htmlDesc;
198
            }
199
        }
200
201
        /** @var \SilverStripe\Forms\FileField|null $file */
202
        $file = $fields->dataFieldByName('_CsvFile');
203
        if ($file) {
0 ignored issues
show
introduced by
$file is of type SilverStripe\Forms\FileField, thus it always evaluated to true.
Loading history...
204
            $csvDescription = ExcelImportExport::getValidExtensionsText();
205
            if ($useDefaultSample) {
0 ignored issues
show
introduced by
The condition $useDefaultSample is always true.
Loading history...
206
                $downloadSample = ExcelImportExport::createDownloadSampleLink();
207
                $csvDescription .= '. ' . $downloadSample;
208
            }
209
            $file->setDescription($csvDescription);
210
            $file->getValidator()->setAllowedExtensions(ExcelImportExport::getValidExtensions());
211
        }
212
213
        // Hide by default, but allow on per class basis (opt-in)
214
        if ($classConfig->hide_replace_data && !$modelConfig->show_replace_data) {
215
            // This is way too dangerous and customers don't understand what this is most of the time
216
            $fields->removeByName("EmptyBeforeImport");
217
        }
218
        // If you cannot delete, you cannot empty
219
        if (!$modelSNG->canDelete()) {
220
            $fields->removeByName('EmptyBeforeImport');
221
        }
222
223
        // We moved the specs into a nice to use download sample button
224
        $fields->removeByName("SpecFor{$modelName}");
225
226
        if (!empty($importHandlers)) {
227
            $form->Fields()->push($OnlyUpdateRecords = new CheckboxField("OnlyUpdateRecords", _t('ExcelImportExport.OnlyUpdateRecords', "Only update records")));
228
            $OnlyUpdateRecords->setAttribute("data-handlers", implode(",", $supportOnlyUpdate));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $supportOnlyUpdate does not seem to be defined for all execution paths leading up to this point.
Loading history...
229
230
            $form->Fields()->push($ImportHandler = new OptionsetField("ImportHandler", _t('ExcelImportExport.PleaseSelectImportHandler', "Please select the import handler"), $importHandlers));
231
            // Simply check of this is supported or not for the given handler (if not, disable it)
232
            $js = <<<JS
233
var desc=document.querySelectorAll('.js-import-desc');var cb=document.querySelector('#OnlyUpdateRecords');var accepted=cb.dataset.handlers.split(',');var item=([...this.querySelectorAll('input')].filter((input) => input.checked)[0]); cb.disabled=(item && accepted.includes(item.value)) ? '': 'disabled';desc.forEach((el)=>el.hidden=!item||el.dataset.name!=item.value);;
234
JS;
235
            $ImportHandler->setAttribute("onclick", $js);
236
            if ($htmlDesc) {
237
                $ImportHandler->setDescription($htmlDesc); // Description is an HTMLFragment
238
            }
239
        }
240
241
        $actions = $form->Actions();
242
243
        // Update import button
244
        $import = $actions->dataFieldByName('action_import');
245
        if ($import) {
246
            $import->setTitle(_t(
247
                'ExcelImportExport.ImportExcel',
248
                "Import from Excel"
249
            ));
250
            $import->removeExtraClass('btn-outline-secondary');
251
            $import->addExtraClass('btn-primary');
252
        }
253
    }
254
}
255