Issues (6)

src/ExternalURLField.php (3 issues)

1
<?php
2
3
namespace Sunnysideup\ExternalURLField;
4
5
use SilverStripe\Forms\TextField;
6
use SilverStripe\ORM\DataObject;
7
use SilverStripe\ORM\FieldType\DBHTMLText;
8
9
/**
10
 * ExternalURLField.
11
 *
12
 * Form field for entering, saving, validating external urls.
13
 */
14
class ExternalURLField extends TextField
15
{
16
    /**
17
     * @var array
18
     */
19
    protected $config = [];
20
21
    /**
22
     * Default configuration.
23
     *
24
     * URL validation regular expression was sourced from
25
     *
26
     * @see https://gist.github.com/dperini/729294
27
     *
28
     * @var array
29
     */
30
    private static $default_config = [
31
        'defaultparts' => [
32
            'scheme' => 'https',
33
        ],
34
        'removeparts' => [
35
            'scheme' => false,
36
            'user' => true,
37
            'pass' => true,
38
            'host' => false,
39
            'port' => false,
40
            'path' => false,
41
            'query' => false,
42
            'fragment' => false,
43
        ],
44
        'html5validation' => true,
45
        'validregex' => '%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)'
46
            . '?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)'
47
            . '(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))'
48
            . '(?::\d+)?(?:[^\s]*)?$%iu',
49
    ];
50
51
    public function __construct($name, $title = null, $value = null)
52
    {
53
        $this->config = $this->config()->default_config;
54
55
        parent::__construct($name, $title, $value);
56
    }
57
58
    public function Type()
59
    {
60
        return 'url text';
61
    }
62
63
    /**
64
     * @param string $name
65
     * @param mixed  $val
66
     */
67
    public function setConfig($name, $val = null)
68
    {
69
        if (is_array($name) && null === $val) {
0 ignored issues
show
The condition is_array($name) is always false.
Loading history...
70
            foreach ($name as $n => $value) {
71
                $this->setConfig($n, $value);
72
            }
73
74
            return $this;
75
        }
76
77
        if (is_array($this->config[$name])) {
78
            if (! is_array($val)) {
79
                user_error("The value for {$name} must be an array");
80
            }
81
82
            $this->config[$name] = array_merge($this->config[$name], $val);
0 ignored issues
show
It seems like $val can also be of type null; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

82
            $this->config[$name] = array_merge($this->config[$name], /** @scrutinizer ignore-type */ $val);
Loading history...
83
        } elseif (isset($this->config[$name])) {
84
            $this->config[$name] = $val;
85
        }
86
87
        return $this;
88
    }
89
90
    /**
91
     * @param string $name Optional, returns the whole configuration array if empty
92
     *
93
     * @return array|mixed
94
     */
95
    public function getConfig($name = null)
96
    {
97
        if ($name) {
98
            return isset($this->config[$name]) ? $this->config[$name] : null;
99
        }
100
101
        return $this->config;
102
    }
103
104
    /**
105
     * Set additional attributes.
106
     *
107
     * @return array Attributes
108
     */
109
    public function getAttributes()
110
    {
111
        $parentAttributes = parent::getAttributes();
112
        $attributes = [];
113
114
        if (! isset($parentAttributes['placeholder'])) {
115
            $attributes['placeholder'] = $this->config['defaultparts']['scheme'] . '://example.com'; //example url
116
        }
117
118
        if ($this->config['html5validation']) {
119
            $attributes += [
120
                'type' => 'url', //html5 field type
121
                'pattern' => 'https?://.+', //valid urls only
122
            ];
123
        }
124
125
        return array_merge(
126
            $parentAttributes,
127
            $attributes
128
        );
129
    }
130
131
    /**
132
     * Rebuild url on save.
133
     *
134
     * @param string           $url
135
     * @param array|DataObject $data {@see Form::loadDataFrom}
136
     *
137
     * @return $this
138
     */
139
    public function setValue($url, $data = null)
140
    {
141
        if ($url) {
142
            $url = $this->rebuildURL($url);
143
        }
144
145
        return parent::setValue($url, $data);
146
    }
147
148
    /**
149
     * Server side validation, using a regular expression.
150
     *
151
     * @param mixed $validator
152
     */
153
    public function validate($validator)
154
    {
155
        $this->value = trim(string: (string) $this->value);
156
        $regex = $this->config['validregex'];
157
        if ($this->value && $regex && ! preg_match($regex, $this->value)) {
158
            $validator->validationError(
159
                $this->name,
160
                _t('ExternalURLField.VALIDATION', 'Please enter a valid URL'),
161
                'validation'
162
            );
163
164
            return false;
165
        }
166
167
        return true;
168
    }
169
170
    public function RightTitle()
171
    {
172
        if ($this->value) {
173
            return DBHTMLText::create_field(DBHTMLText::class, parent::RightTitle() . '<a href="' . $this->value . '" target="_blank" onclick="event.stopPropagation();"rel="noreferrer noopener">open ↗</a>');
174
        }
175
176
        return parent::RightTitle();
177
    }
178
179
    /**
180
     * Add config scheme, if missing.
181
     * Remove the parts of the url we don't want.
182
     * Set any defaults, if missing.
183
     * Remove any trailing slash, and rebuild.
184
     *
185
     * @param mixed $url
186
     *
187
     * @return string
188
     */
189
    protected function rebuildURL($url)
190
    {
191
        $defaults = $this->config['defaultparts'];
192
        if (! preg_match('#^[a-zA-Z]+://#', $url)) {
193
            $url = $defaults['scheme'] . '://' . $url;
194
        }
195
196
        $parts = parse_url($url);
197
        if (! $parts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parts 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...
198
            //can't parse url, abort
199
            return '';
200
        }
201
202
        foreach (array_keys($parts) as $part) {
203
            if (true === $this->config['removeparts'][$part]) {
204
                unset($parts[$part]);
205
            }
206
        }
207
208
        // this causes errors!
209
        // $parts = array_filter($defaults, fn ($default) => ! isset($parts[$part]));
210
211
        return rtrim(http_build_url($defaults, $parts), '/');
212
    }
213
}
214