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) { |
||
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); |
||
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
|
|||
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 |
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.