requireDefaultRecords()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 16
rs 9.8333
cc 4
nc 4
nop 0
1
<?php
2
3
namespace PhpTek\Exodus\Model;
4
5
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use PhpTek\Exodus\Tool\StaticSiteMimeProcessor;
7
use SilverStripe\Core\ClassInfo;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\Forms\LiteralField;
10
use SilverStripe\Forms\GridField\GridFieldAddNewButton;
11
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
12
use SilverStripe\Assets\File;
13
use SilverStripe\ORM\FieldType\DBInt;
14
use SilverStripe\ORM\FieldType\DBText;
15
use SilverStripe\ORM\FieldType\DBVarchar;
16
use SilverStripe\Forms\TextareaField;
17
use SilverStripe\Forms\DropdownField;
18
use SilverStripe\ORM\ValidationResult;
19
use SilverStripe\Dev\Debug;
20
21
/**
22
 * Represents a single import-rule that applies to some or all of the content to be imported.
23
 */
24
class StaticSiteContentSourceImportSchema extends DataObject
25
{
26
    /**
27
     * Default
28
     *
29
     * @var string
30
     */
31
    private static $default_applies_to = '.*';
32
33
    /**
34
     * @var string
35
     */
36
    private static $table_name = 'StaticSiteContentSourceImportSchema';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
37
38
    /**
39
     *
40
     * @var array
41
     */
42
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
43
        "DataType" => DBVarchar::class,
44
        "Order" => DBInt::class,
45
        "AppliesTo" => DBVarchar::class,
46
        "MimeTypes" => DBText::class,
47
        "Notes" => DBText::class,   // Purely informational. Not used in imports.
48
    ];
49
50
    /**
51
     *
52
     * @var array
53
     */
54
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
55
        "AppliesTo",
56
        "DataType",
57
        "Order",
58
    ];
59
60
    /**
61
     *
62
     * @var array
63
     */
64
    private static $field_labels = [
0 ignored issues
show
introduced by
The private property $field_labels is not used, and could be removed.
Loading history...
65
        "AppliesTo" => "URL Pattern",
66
        "DataType" => "Data Type",
67
        "Order" => "Priority",
68
        "MimeTypes" => "Mime-types",
69
    ];
70
71
    /**
72
     *
73
     * @var string
74
     */
75
    private static $default_sort = "Order";
0 ignored issues
show
introduced by
The private property $default_sort is not used, and could be removed.
Loading history...
76
77
    /**
78
     *
79
     * @var array
80
     */
81
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
82
        "ContentSource" => StaticSiteContentSource::class,
83
    ];
84
85
    /**
86
     *
87
     * @var array
88
     */
89
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
90
        "ImportRules" => StaticSiteContentSourceImportRule::class,
91
    ];
92
93
    /**
94
     * Used as the title in the CMS.
95
     *
96
     * @return string
97
     */
98
    public function getTitle(): string
99
    {
100
        $type = ClassInfo::shortName($this->DataType);
101
102
        return sprintf('%s (%s)', $type, $this->AppliesTo);
103
    }
104
105
    /**
106
     *
107
     * @return FieldList
0 ignored issues
show
Bug introduced by
The type PhpTek\Exodus\Model\FieldList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
108
     */
109
    public function getCMSFields()
110
    {
111
        $fields = parent::getCMSFields();
112
        $fields->removeFieldFromTab('Root.Main', 'DataType');
113
        $fields->removeByName('ContentSourceID');
114
        $dataObjects = ClassInfo::subclassesFor(DataObject::class);
115
116
        array_shift($dataObjects);
117
        natcasesort($dataObjects);
118
119
        $fields->insertBefore('Order', LiteralField::create(
120
            'ImportIntro',
121
            ''
122
            . '<p class="message notice">Map MIME-Types to Silverstripe Data Types and'
123
            . ' relate them to one or more Import Rules (See below).</p>'
124
        ));
125
        $fields->dataFieldbyName('Order')
126
            ->setDescription('Schema are assigned a higher priority through lower numbers.')
127
            ->setAttribute('style', 'width: 100px');
128
129
        $fields->dataFieldByName('AppliesTo')
130
            ->setDescription('A full or partial URI whose content is suited to the Data Type selected below. Supports regular expressions.');
131
        $fields->addFieldToTab('Root.Main', DropdownField::create('DataType', 'Data Type', $dataObjects));
132
        $mimes = TextareaField::create('MimeTypes', 'Mime-types')
133
            ->setRows(3)
134
            ->setDescription('Be sure to pick a Mime-type that the above Data Type supports'
135
            . ' e.g. text/html for <strong>SiteTree</strong>,'
136
            . ' image/png, mage/jpeg, image/webp etc for <strong>Image</strong>'
137
            . ' or application/pdf, text/csv etc for <strong>File</strong>.'
138
            . ' Separate multiple Mimes by a newline.');
139
        $fields->addFieldToTab('Root.Main', $mimes);
140
        $notes = TextareaField::create('Notes', 'Notes')
141
            ->setDescription('Use this field to add any notes about this schema.'
142
            . ' (Purely informational. Data is not used in imports).');
143
        $fields->addFieldToTab('Root.Main', $notes);
144
145
        $importRules = $fields->dataFieldByName('ImportRules');
146
        $fields->removeFieldFromTab('Root', 'ImportRules');
147
        $fields->dataFieldByName('DataType')->setDescription(''
148
            . 'The Silverstripe content class with which content from the selected'
149
            . ' Mime-Types will be associated.');
150
151
        // Don't show for File subclasses, these obviously don't require CSS-based import rules
152
        if ($this->DataType && in_array(File::class, ClassInfo::ancestry($this->DataType))) {
153
            return $fields;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fields returns the type SilverStripe\Forms\FieldList which is incompatible with the documented return type PhpTek\Exodus\Model\FieldList.
Loading history...
154
        }
155
156
        if ($importRules) {
157
            $conf = $importRules->getConfig();
158
            $conf->removeComponentsByType([
159
                GridFieldAddExistingAutocompleter::class,
160
                GridFieldAddNewButton::class
161
            ]);
162
            $addNewButton = (new GridFieldAddNewButton('before'))->setButtonName("Add Rule");
163
            $conf->addComponent($addNewButton);
164
            $fields->addFieldToTab('Root.Main', $importRules);
165
            $fields->insertBefore('ImportRules', LiteralField::create(
166
                'ImportRuleIntro',
167
                ''
168
                . '<p class="message notice">An import rule determines how content located at crawled URLs'
169
                . ' should be imported into a Data Type\'s fields with the use of CSS selectors.'
170
                . ' Where more than one schema exists for the same Data Type, they\'ll be processed in the order of Priority,'
171
                . ' where the first one to match a URI and Mime combination is the one that will be used.<p>'
172
            ));
173
        }
174
175
        return $fields;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fields returns the type SilverStripe\Forms\FieldList which is incompatible with the documented return type PhpTek\Exodus\Model\FieldList.
Loading history...
176
    }
177
178
    /**
179
     *
180
     * @return void
181
     */
182
    public function requireDefaultRecords()
183
    {
184
        foreach (StaticSiteContentSource::get() as $source) {
185
            if (!$source->Schemas()->count()) {
186
                Debug::message("Making a schema for $source->ID");
187
                $defaultSchema = StaticSiteContentSourceImportSchema::create();
188
                $defaultSchema->Order = 1000000;
189
                $defaultSchema->AppliesTo = self::$default_applies_to;
190
                $defaultSchema->DataType = Page::class;
191
                $defaultSchema->ContentSourceID = $source->ID;
192
                $defaultSchema->MimeTypes = "text/html";
193
                $defaultSchema->write();
194
195
                foreach (StaticSiteContentSourceImportRule::get()->filter(['SchemaID' => 0]) as $rule) {
196
                    $rule->SchemaID = $defaultSchema->ID;
197
                    $rule->write();
198
                }
199
            }
200
        }
201
    }
202
203
    /**
204
     * Return the import rules in a format suitable for configuring StaticSiteContentExtractor.
205
     *
206
     * @return array $output. A map of field name => [CSS selector, CSS selector, ...]
207
     */
208
    public function getImportRules(): array
209
    {
210
        $output = [];
211
212
        foreach ($this->ImportRules() as $rule) {
0 ignored issues
show
Bug introduced by
The method ImportRules() does not exist on PhpTek\Exodus\Model\Stat...ntentSourceImportSchema. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

212
        foreach ($this->/** @scrutinizer ignore-call */ ImportRules() as $rule) {
Loading history...
213
            if (!isset($output[$rule->FieldName])) {
214
                $output[$rule->FieldName] = [];
215
            }
216
217
            $ruleArray = [
218
                'selector' => trim((string) $rule->CSSSelector),
219
                'attribute' => $rule->Attribute,
220
                'plaintext' => $rule->PlainText,
221
                'excludeselectors' => preg_split("#\n#", trim((string) $rule->ExcludeCSSSelector)),
222
                'outerhtml' => $rule->OuterHTML,
223
            ];
224
225
            $output[$rule->FieldName][] = $ruleArray;
226
        }
227
228
        return $output;
229
    }
230
231
    /**
232
     *
233
     * @return \ValidationResult
0 ignored issues
show
Bug introduced by
The type ValidationResult was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
234
     */
235
    public function validate()
236
    {
237
        $result = ValidationResult::create();
238
        $mime = $this->validateMimes();
239
        $appliesTo = $this->validateUrlPattern();
240
241
        if (!is_bool($mime)) {
242
            $result->addError('Invalid Mime-type "' . $mime . '" for DataType "' . $this->DataType . '"');
243
        }
244
245
        if (!is_bool($appliesTo)) {
246
            $result->addError('Invalid PCRE expression "' . $appliesTo . '"');
247
        }
248
249
        return $result;
250
    }
251
252
    /**
253
     *
254
     * Validate user-inputted mime-types until we use some sort of multi-select list in the CMS to select from (@todo).
255
     *
256
     * @return mixed boolean|string Boolean true if all is OK, otherwise the invalid mimeType to be shown in the CMS UI
257
     */
258
    public function validateMimes()
259
    {
260
        $selectedMimes = StaticSiteMimeProcessor::get_mimetypes_from_text($this->MimeTypes);
0 ignored issues
show
Unused Code introduced by
The assignment to $selectedMimes is dead and can be removed.
Loading history...
261
262
        $dt = $this->DataType ?? $_POST['DataType']; // @todo
263
        if (!$dt) {
264
            return true; // probably just creating
265
        }
266
267
        /*
268
         * This is v.sketch. It relies on the name of user-entered DataTypes containing
269
         * the string we want to match on in its classname = bad
270
         * @todo prolly just replace this wih a regex..
271
         */
272
        switch ($dt) {
273
            case stristr($dt, 'image') !== false:
274
                $type = 'image';
275
                break;
276
            case stristr($dt, 'file') !== false:
277
                $type = 'file';
278
                break;
279
            case stristr($dt, 'page') !== false:
280
            default:
281
                $type = 'sitetree';
282
                break;
283
        }
284
285
        $mimesForSSType = StaticSiteMimeProcessor::get_mime_for_ss_type($type);
286
        $mimes = $mimesForSSType ? $mimesForSSType : [];
287
288
        foreach ($mimes as $mime) {
289
            if (!in_array($mime, $mimesForSSType)) {
290
                return $mime;
291
            }
292
        }
293
294
        return true;
295
    }
296
297
    /**
298
     *
299
     * Prevent ugly CMS console errors if user-defined regex's are not 100% PCRE compatible.
300
     *
301
     * @return mixed string | boolean
302
     */
303
    public function validateUrlPattern()
304
    {
305
        // Basic check uses negative lookbehind and checks if glob chars exist which are _not_ preceeded by a '.' char
306
        if (preg_match("#(?<!.)(\+|\*)#", $this->AppliesTo)) {
307
            return $this->AppliesTo;
308
        }
309
310
        return true;
311
    }
312
}
313