Failed Conditions
Branch try/refactor-route (c848cf)
by Atanas
05:25 queued 02:17
created

UrlCondition::makeUrl()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 18
ccs 10
cts 11
cp 0.9091
rs 9.9
cc 3
nc 1
nop 1
crap 3.0067
1
<?php
2
/**
3
 * @package   WPEmerge
4
 * @author    Atanas Angelov <[email protected]>
5
 * @copyright 2017-2019 Atanas Angelov
6
 * @license   https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0
7
 * @link      https://wpemerge.com/
8
 */
9
10
namespace WPEmerge\Routing\Conditions;
11
12
use WPEmerge\Exceptions\ConfigurationException;
13
use WPEmerge\Helpers\Url as UrlUtility;
14
use WPEmerge\Requests\RequestInterface;
15
use WPEmerge\Support\Arr;
16
17
/**
18
 * Check against the current url
19
 */
20
class UrlCondition implements ConditionInterface {
21
	const WILDCARD = '*';
22
23
	/**
24
	 * URL to check against.
25
	 *
26
	 * @var string
27
	 */
28
	protected $url = '';
29
30
	/**
31
	 * URL where.
32
	 *
33
	 * @var array<string, string>
34
	 */
35
	protected $url_where = [];
36
37
	/**
38
	 * Pattern to detect parameters in urls.
39
	 *
40
	 * @suppress HtmlUnknownTag
41
	 * @var string
42
	 */
43
	protected $url_pattern = '~
44
		(?:/)                     # match leading slash
45
		(?:\{)                    # opening curly brace
46
			(?P<name>[a-z]\w*)    # string starting with a-z and followed by word characters for the parameter name
47
			(?P<optional>\?)?     # optionally allow the user to mark the parameter as option using a literal ?
48
		(?:\})                    # closing curly brace
49
		(?=/)                     # lookahead for a trailing slash
50
	~ix';
51
52
	/**
53
	 * Pattern to detect valid parameters in url segments.
54
	 *
55
	 * @var string
56
	 */
57
	protected $parameter_pattern = '[^/]+';
58
59
	/**
60
	 * Constructor.
61
	 *
62
	 * @codeCoverageIgnore
63
	 * @param string $url
64
	 * @param array<string, string> $where
65
	 */
66
	public function __construct( $url, $where = [] ) {
67
		$this->setUrl( $url );
68
		$this->setUrlWhere( $where );
69
	}
70
71
	/**
72
	 * Make a new instance.
73
	 *
74
	 * @codeCoverageIgnore
75
	 * @param string $url
76
	 * @param array<string, string> $where
77
	 * @return self
78
	 */
79
	protected function make( $url, $where = [] ) {
80
		return new self( $url, $where );
81
	}
82
83
	/**
84
	 * {@inheritDoc}
85
	 */
86 1
	protected function whereIsSatisfied( RequestInterface $request ) {
87 1
		$where = $this->getUrlWhere();
88 1
		$arguments = $this->getArguments( $request );
89
90 1
		foreach ( $where as $parameter => $pattern ) {
91 1
			$value = Arr::get( $arguments, $parameter, '' );
92
93 1
			if ( ! preg_match( $pattern, $value ) ) {
94 1
				return false;
95
			}
96
		}
97
98 1
		return true;
99
	}
100
101
	/**
102
	 * {@inheritDoc}
103
	 */
104 3
	public function isSatisfied( RequestInterface $request ) {
105 3
		if ( $this->getUrl() === static::WILDCARD ) {
106 1
			return true;
107
		}
108
109 2
		$validation_pattern = $this->getValidationPattern( $this->getUrl() );
110 2
		$url = UrlUtility::getPath( $request );
111 2
		$match = (bool) preg_match( $validation_pattern, $url );
112
113 2
		if ( ! $match || empty( $this->getUrlWhere() ) ) {
114 2
			return $match;
115
		}
116
117 1
		return $this->whereIsSatisfied( $request );
118
	}
119
120
	/**
121
	 * {@inheritDoc}
122
	 */
123 1
	public function getArguments( RequestInterface $request ) {
124 1
		$validation_pattern = $this->getValidationPattern( $this->getUrl() );
125 1
		$url = UrlUtility::getPath( $request );
126 1
		$matches = [];
127 1
		$success = preg_match( $validation_pattern, $url, $matches );
128
129 1
		if ( ! $success ) {
130 1
			return []; // this should not normally happen
131
		}
132
133 1
		$arguments = [];
134 1
		$parameter_names = $this->getParameterNames( $this->getUrl() );
135 1
		foreach ( $parameter_names as $parameter_name ) {
136 1
			$arguments[ $parameter_name ] = isset( $matches[ $parameter_name ] ) ? $matches[ $parameter_name ] : '';
137
		}
138
139 1
		return $arguments;
140
	}
141
142
	/**
143
	 * Get the url for this condition.
144
	 *
145
	 * @return string
146
	 */
147 2
	public function getUrl() {
148 2
		return $this->url;
149
	}
150
151
	/**
152
	 * Set the url for this condition.
153
	 *
154
	 * @param  string $url
155
	 * @return void
156
	 */
157 2
	public function setUrl( $url ) {
158 2
		if ( $url !== static::WILDCARD ) {
159 1
			$url = UrlUtility::addLeadingSlash( UrlUtility::addTrailingSlash( $url ) );
160
		}
161
162 2
		$this->url = $url;
163 2
	}
164
165
	/**
166
	 * Make a URL with filled in parameters.
167
	 *
168
	 * @param  array  $arguments
169
	 * @return string
170
	 */
171
	public function makeUrl( $arguments = [] ) {
172 1
		$url = preg_replace_callback( $this->url_pattern, function ( $matches ) use ( $arguments ) {
173 1
			$name = $matches['name'];
174 1
			$optional = ! empty( $matches['optional'] );
175 1
			$value = '/' . urlencode( Arr::get( $arguments, $name, '' ) );
1 ignored issue
show
Bug introduced by
It seems like WPEmerge\Support\Arr::get($arguments, $name, '') can also be of type array; however, parameter $str of urlencode() does only seem to accept string, 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

175
			$value = '/' . urlencode( /** @scrutinizer ignore-type */ Arr::get( $arguments, $name, '' ) );
Loading history...
176
177 1
			if ( $value === '/' ) {
178 1
				if ( ! $optional ) {
179
					throw new ConfigurationException( "Required URL parameter \"$name\" is not specified." );
180
				}
181
182 1
				$value = '';
183
			}
184
185 1
			return $value;
186 1
		}, $this->getUrl() );
187
188 1
		return UrlUtility::addLeadingSlash( UrlUtility::removeTrailingSlash( $url ) );
189
	}
190
191
	/**
192
	 * {@inheritDoc}
193
	 */
194 1
	public function getUrlWhere() {
195 1
		return $this->url_where;
196
	}
197
198
	/**
199
	 * {@inheritDoc}
200
	 */
201 1
	public function setUrlWhere( $where ) {
202 1
		$this->url_where = $where;
203 1
	}
204
205
	/**
206
	 * Append a url to this one returning a new instance.
207
	 *
208
	 * @param  string                $url
209
	 * @param  array<string, string> $where
210
	 * @return static
211
	 */
212 3
	public function concatenate( $url, $where = [] ) {
213 3
		if ( $this->getUrl() === static::WILDCARD || $url === static::WILDCARD ) {
214 1
			return $this->make( static::WILDCARD );
215
		}
216
217 2
		$leading = UrlUtility::addLeadingSlash( UrlUtility::removeTrailingSlash( $this->getUrl() ), true );
218 2
		$trailing = UrlUtility::addLeadingSlash( UrlUtility::addTrailingSlash( $url ) );
219
220 2
		return $this->make( $leading . $trailing, array_merge(
221 2
			$this->getUrlWhere(),
222 2
			$where
223
		) );
224
	}
225
226
	/**
227
	 * Get parameter names as defined in the url.
228
	 *
229
	 * @param  string   $url
230
	 * @return string[]
231
	 */
232 1
	protected function getParameterNames( $url ) {
233 1
		$matches = [];
234 1
		preg_match_all( $this->url_pattern, $url, $matches );
235 1
		return $matches['name'];
236
	}
237
238
	/**
239
	 * Validation pattern replace callback.
240
	 *
241
	 * @param  array  $matches
242
	 * @param  array  $parameters
243
	 * @return string
244
	 */
245 1
	protected function replacePatternParameterWithPlaceholder( $matches, &$parameters ) {
246 1
		$name = $matches['name'];
247 1
		$optional = ! empty( $matches['optional'] );
248
249 1
		$replacement = '/(?P<' . $name . '>' . $this->parameter_pattern . ')';
250
251 1
		if ( $optional ) {
252 1
			$replacement = '(?:' . $replacement . ')?';
253
		}
254
255 1
		$hash = sha1( implode( '_', [
256 1
			count( $parameters ),
257 1
			$replacement,
258 1
			uniqid( 'wpemerge_', true ),
259
		] ) );
260 1
		$placeholder = '___placeholder_' . $hash . '___';
261 1
		$parameters[ $placeholder ] = $replacement;
262
263 1
		return $placeholder;
264
	}
265
266
	/**
267
	 * Get pattern to test whether normal urls match the parameter-based one.
268
	 *
269
	 * @param  string  $url
270
	 * @param  boolean $wrap
271
	 * @return string
272
	 */
273 1
	public function getValidationPattern( $url, $wrap = true ) {
274 1
		$parameters = [];
275
276
		// Replace all parameters with placeholders
277 1
		$validation_pattern = preg_replace_callback( $this->url_pattern, function ( $matches ) use ( &$parameters ) {
278 1
			return $this->replacePatternParameterWithPlaceholder( $matches, $parameters );
279 1
		}, $url );
280
281
		// Quote the remaining string so that it does not get evaluated as a pattern.
282
		$validation_pattern = preg_quote( $validation_pattern, '~' );
283
284
		// Replace the placeholders with the real parameter patterns.
285
		$validation_pattern = str_replace(
286
			array_keys( $parameters ),
287
			array_values( $parameters ),
288
			$validation_pattern
289
		);
290
291
		// Match the entire url; make trailing slash optional.
292
		$validation_pattern = '^' . $validation_pattern . '?$';
293
294
		if ( $wrap ) {
295
			$validation_pattern = '~' . $validation_pattern . '~';
296
		}
297
298
		return $validation_pattern;
299
	}
300
}
301