Passed
Pull Request — 4 (#930)
by Steve
03:44
created

ElementalAreasValidator::getElementID()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 10
rs 10
1
<?php
2
3
namespace DNADesign\Elemental\Validators;
4
5
use DNADesign\Elemental\Controllers\ElementalAreaController;
6
use DNADesign\Elemental\Forms\EditFormFactory;
7
use DNADesign\Elemental\Models\BaseElement;
8
use DNADesign\Elemental\Models\ElementalArea;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Forms\Validator;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\ORM\ValidationResult;
14
15
class ElementalAreasValidator extends Validator
16
{
17
    /**
18
     * @param array $data
19
     */
20
    public function php($data)
21
    {
22
        $valid = true;
23
        $areaErrors = [];
24
        $areaFieldNames = $this->getElementalAreaFieldNames($data['ClassName']);
25
        foreach ($areaFieldNames as $areaFieldName) {
26
            $elementsData = $data[$areaFieldName] ?? [];
27
            if (empty($elementsData)) {
28
                continue;
29
            }
30
            foreach (array_values($elementsData) as $elementData) {
31
                $elementID = $this->getElementID($elementData);
32
                if (!$elementID) {
33
                    continue;
34
                }
35
                $key = sprintf(EditFormFactory::FIELD_NAMESPACE_TEMPLATE, $elementID, 'ClassName');
36
                $className = $elementData[$key] ?? '';
37
                if (!$className) {
38
                    continue;
39
                }
40
                /** @var BaseElement $element */
41
                $element = DataObject::get_by_id($className, $elementID, false);
0 ignored issues
show
Bug introduced by
$elementID of type string is incompatible with the type boolean|integer expected by parameter $idOrCache of SilverStripe\ORM\DataObject::get_by_id(). ( Ignorable by Annotation )

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

41
                $element = DataObject::get_by_id($className, /** @scrutinizer ignore-type */ $elementID, false);
Loading history...
42
                if (!$element) {
43
                    continue;
44
                }
45
                $originalTitle = $element->Title ??
46
                   sprintf('(Untitled %s)', ucfirst($element->config()->get('singular_name')));
47
                $formData = ElementalAreaController::removeNamespacesFromFields($elementData, $elementID);
0 ignored issues
show
Bug introduced by
$elementID of type string is incompatible with the type integer expected by parameter $elementID of DNADesign\Elemental\Cont...eNamespacesFromFields(). ( Ignorable by Annotation )

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

47
                $formData = ElementalAreaController::removeNamespacesFromFields($elementData, /** @scrutinizer ignore-type */ $elementID);
Loading history...
48
                $element->updateFromFormData($formData);
49
                /** @var ValidationResult $validationResult */
50
                $validationResult = $element->validate();
51
                if ($validationResult->isValid()) {
52
                    continue;
53
                }
54
                if (!array_key_exists($areaFieldName, $areaErrors)) {
55
                    $areaErrors[$areaFieldName] = [
56
                        'The elements below have the following errors:' // TODO _t()
57
                    ];
58
                }
59
                foreach ($validationResult->getMessages() as $message) {
60
                    $this->validationError(
61
                        "PageElements_{$elementID}_{$message['fieldName']}",
62
                        $message['message'],
63
                        $message['messageType'],
64
                        $message['messageCast']
65
                    );
66
                    $areaErrors[$areaFieldName][] = sprintf(
67
                        '%s - %s',
68
                        $originalTitle,
69
                        $message['message']
70
                    );
71
                }
72
                $valid = false;
73
            }
74
        }
75
        if (!$valid) {
76
            foreach ($areaErrors as $areaFieldName => $errors) {
77
                $this->validationError(
78
                    $areaFieldName,
79
                    implode('<br>', $errors),
80
                    ValidationResult::TYPE_ERROR,
81
                    ValidationResult::CAST_HTML
82
                );
83
            }
84
            // TODO: see what happens when you have multiple cms tabs
85
            // Show a generic form message. Ideally this would be done in admin LeftAndMain.EditForm.js
86
            // TODO: this is defined in en.js, needs to be in en.yml too (preferably admin, not elemental)
87
            $msg = _t(
88
                'VALIDATION_ERRORS_ON_PAGE',
89
                'There are validation errors on this page, please fix them before saving or publishing.'
90
            );
91
            // If message above is change to javascript, instead set a blank string here to hide the
92
            // generic form message by overriding any PageElement_3_Title type of message which will
93
            // show as a generic form message since it won't match dataFieldByName($field) in
94
            // Form::loadMessageFrom($data)
95
            $this->validationError('GenericFormMessage', $msg);
96
        }
97
        return $valid;
98
    }
99
100
    /**
101
     * @param string $parentClassName
102
     * @return array
103
     */
104
    private function getElementalAreaFieldNames(string $parentClassName): array
105
    {
106
        $fieldNames = [];
107
        $hasOnes = Config::inst()->get($parentClassName, 'has_one');
108
        foreach ($hasOnes as $fieldName => $className) {
109
            if (!(Injector::inst()->get($className) instanceof ElementalArea)) {
110
                continue;
111
            }
112
            $fieldNames[] = $fieldName;
113
        }
114
        return $fieldNames;
115
    }
116
117
    /**
118
     * @param array $elementData
119
     * @return string
120
     */
121
    private function getElementID(array $elementData): string
122
    {
123
        foreach (array_keys($elementData) as $key) {
124
            $rx = str_replace(['%d', '%s'], ['([0-9]+)', '(.+)'], EditFormFactory::FIELD_NAMESPACE_TEMPLATE);
125
            if (!preg_match("#^{$rx}$#", $key, $match)) {
126
                continue;
127
            }
128
            return $match[1];
129
        }
130
        return '';
131
    }
132
}
133