Completed
Push — master ( eaf16e...6f8e07 )
by Mohamed
11:01
created

Moo_EditableField::onBeforeWrite()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0312

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 14
cts 16
cp 0.875
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 14
nc 5
nop 0
crap 4.0312
1
<?php
2
3
/**
4
 * Moo_EditableField is a base class for editable fields to extend.
5
 *
6
 * @author  Mohamed Alsharaf <[email protected]>
7
 *
8
 * @property string $Title
9
 * @property string $Name
10
 * @property int    $Required
11
 * @property string $CustomErrorMessage
12
 * @property string $CustomSettings
13
 */
14
class Moo_EditableField extends DataObject
15
{
16
    private static $db = [
17
        'Name'               => 'Varchar',
18
        'Title'              => 'Varchar(255)',
19
        'Required'           => 'Boolean',
20
        'CustomErrorMessage' => 'Varchar(255)',
21
        'CustomSettings'     => 'Text',
22
    ];
23
    private static $singular_name = 'Editable Field';
24
    private static $plural_name   = 'Editable Fields';
25
26
    protected $customSettingsFields = [];
27
    /**
28
     * Instance of FormField.
29
     *
30
     * @var FormField
31
     */
32
    protected $field;
33
34
    /**
35
     * To prevent having tables for each fields minor settings we store it as
36
     * a serialized array in the database.
37
     *
38
     * @return array Return all the Settings
39
     */
40 8
    public function getSettings()
41 1
    {
42 8
        return (!empty($this->CustomSettings)) ? unserialize($this->CustomSettings) : [];
43
    }
44
45
    /**
46
     * Set the custom settings for this field as we store the minor details in
47
     * a serialized array in the database.
48
     *
49
     * @param array $settings the custom settings
50
     */
51 8
    public function setSettings($settings = [])
52
    {
53 8
        $this->CustomSettings = serialize($settings);
54 8
    }
55
56
    /**
57
     * Set a given field setting. Appends the option to the settings or overrides
58
     * the existing value.
59
     *
60
     * @param string $key
61
     * @param string $value
62
     */
63 1
    public function setSetting($key, $value)
64
    {
65 1
        $settings       = $this->getSettings();
66 1
        $settings[$key] = $value;
67
68 1
        $this->setSettings($settings);
69 1
    }
70
71
    /**
72
     * Return just one custom setting or empty string if it does
73
     * not exist.
74
     *
75
     * @param string $setting
76
     *
77
     * @return string
78
     */
79 3
    public function getSetting($setting)
80
    {
81 3
        $settings = $this->getSettings();
82 3
        if (isset($settings) && count($settings) > 0) {
83 2
            if (isset($settings[$setting])) {
84 2
                return $settings[$setting];
85
            }
86
        }
87
88 3
        return '';
89
    }
90
91
    /**
92
     * Get the path to the icon for this field type, relative to the site root.
93
     *
94
     * @return string
95
     */
96 1
    public function getIcon()
97
    {
98 1
        return 'editablefield/images/formfields/' . strtolower(substr($this->class, 4)) . '.png';
99
    }
100
101 1
    public function getIconTag()
102
    {
103 1
        return '<img src="' . $this->getIcon() . '"/>';
104
    }
105
106 1
    public function getCMSFields()
107
    {
108 1
        $fields = parent::getCMSFields();
109
110
        // Remove field to be recreated in separate tabs
111 1
        $fields->removeByName([
112 1
            'Required', 'CustomErrorMessage', 'CustomSettings', 'Options',
113 1
        ]);
114
115
        //Implement custom field Configuration on this field. Includes such things as
116
//        * settings and options of a given editable form field.
117
118 1
        $fields->addFieldsToTab('Root.FieldConfiguration', [
119 1
            new TextField(
120 1
                $this->getSettingName('RightTitle'), _t('Moo_EditableField.RIGHTTITLE', 'Right Title'),
121 1
                $this->getSetting('RightTitle')
122 1
            ),
123 1
        ]);
124
125
        $validateFields = [
126 1
            new CheckboxField('Required', _t('Moo_EditableField.REQUIRED', 'Is this field Required?'),
127 1
                $this->Required), new TextField('CustomErrorMessage',
128 1
                _t('Moo_EditableField.CUSTOMERROR', 'Custom Error Message'),
129 1
                $this->CustomErrorMessage),
130 1
        ];
131 1
        $fields->addFieldsToTab('Root.Validation', $validateFields);
132
133 1
        if (method_exists($this, 'getFieldConfiguration')) {
134 1
            $v = $this->getFieldConfiguration();
0 ignored issues
show
Bug introduced by
The method getFieldConfiguration() does not exist on Moo_EditableField. Did you maybe mean config()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
135 1
            $fields->addFieldsToTab('Root.FieldConfiguration', $v);
136 1
        }
137
138 1
        if (method_exists($this, 'getFieldValidationOptions')) {
139
            $v = $this->getFieldValidationOptions();
0 ignored issues
show
Documentation Bug introduced by
The method getFieldValidationOptions does not exist on object<Moo_EditableField>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
140
            if (is_array($v)) {
141
                $fields->addFieldsToTab('Root.Validation', $v);
142
            }
143
        }
144
145 1
        return $fields;
146
    }
147
148 1
    public function getCMSValidator()
149
    {
150 1
        return new RequiredFields(
151 1
            'Title', 'Name'
152 1
        );
153
    }
154
155
    /**
156
     * Returns the Title for rendering in the front-end (with XML values escaped).
157
     *
158
     * @return string
159
     */
160 8
    public function getTitle()
161
    {
162 8
        return Convert::raw2att($this->getField('Title'));
0 ignored issues
show
Bug Compatibility introduced by
The expression \Convert::raw2att($this->getField('Title')); of type array|string adds the type array to the return on line 162 which is incompatible with the return type of the parent method DataObject::getTitle of type string.
Loading history...
163
    }
164
165
    /**
166
     * Generate a name for the Setting field.
167
     *
168
     * @param string $field
169
     *
170
     * @return string
171
     */
172 1
    public function getSettingName($field)
173
    {
174 1
        return 'CustomSettings[' . $field . ']';
175
    }
176
177
    /**
178
     * How to save the data submitted in this field into the database object
179
     * which this field represents.
180
     *
181
     * Any class's which call this should also call
182
     * {@link parent::populateFromPostData()} to ensure that this method is
183
     * called
184
     *
185
     * @param array $data
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
186
     *
187
     * @throws ValidationException
188
     */
189 8
    public function onBeforeWrite()
190
    {
191 8
        $r = parent::onBeforeWrite();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $r is correct as parent::onBeforeWrite() (which targets DataObject::onBeforeWrite()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
192
193 8
        $exists = self::get()->filter('Name', $this->Name)->exclude('ID', $this->ID);
194 8
        if ($exists->count()) {
195
            throw new ValidationException(_t('Moo_EditableField.UNIQUENAME', 'Field name "{name}" must be unique', '',
196
                ['name' => $this->Name]));
0 ignored issues
show
Documentation introduced by
array('name' => $this->Name) is of type array<string,string,{"name":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
197
        }
198
199 8
        $this->Name = preg_replace('/[^a-zA-Z0-9_]+/', '', $this->Name);
200
201 8
        $customSettings = $this->getSettings();
202 8
        if (empty($customSettings)) {
203 8
            $customSettings = (array) Controller::curr()->getRequest()->postVar('CustomSettings');
204 8
        }
205 8
        if (!empty($this->customSettingsFields)) {
206 5
            $customSettings = array_intersect_key($customSettings, array_flip((array) $this->customSettingsFields));
207 5
        }
208 8
        $this->setSettings($customSettings);
209
210 8
        return $r;
211
    }
212
213
    /**
214
     * Return a FormField.
215
     *
216
     * @return FormField
217
     */
218 1
    public function getFormField()
219
    {
220 1
        if (null === $this->field) {
221 1
            $this->field = $this->initFormField();
222 1
        }
223
224 1
        return $this->field;
225
    }
226
227
    /**
228
     * Initiate a form field.
229
     *
230
     * @return FormField
231
     */
232
    protected function initFormField()
233
    {
234
        throw new DomainException(sprintf('%s must be implemented by the class %s', __METHOD__, $this->class));
235
    }
236
237
    /**
238
     * Return the error message for this field. Either uses the custom
239
     * one (if provided) or the default SilverStripe message.
240
     *
241
     * @return Varchar
242
     */
243
    public function getErrorMessage()
244
    {
245
        $title    = strip_tags("'" . ($this->Title ? $this->Title : $this->Name) . "'");
246
        $standard = _t('Form.FIELDISREQUIRED', '{name} is required', ['name' => $title]);
0 ignored issues
show
Documentation introduced by
array('name' => $title) is of type array<string,string,{"name":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
247
248
        // only use CustomErrorMessage if it has a non empty value
249
        $errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
250
251
        return DBField::create_field('Varchar', $errorMessage);
252
    }
253
254 1
    public function onBeforeDuplicate(Moo_EditableField $field)
255
    {
256 1
        $this->owner->Name = $field->Name . uniqid();
0 ignored issues
show
Documentation introduced by
The property owner does not exist on object<Moo_EditableField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
257
    }
258
}
259