Passed
Push — master ( 53e563...0c7a38 )
by Atanas
03:02
created

UrlCondition   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 26
eloc 77
dl 0
loc 255
ccs 87
cts 87
cp 1
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A setUrlWhere() 0 2 1
A make() 0 2 1
A getUrl() 0 2 1
A isSatisfied() 0 14 4
A concatenate() 0 14 3
A replacePatternParameterWithPlaceholder() 0 19 2
A getUrlWhere() 0 2 1
A whereIsSatisfied() 0 13 3
A getParameterNames() 0 4 1
A getArguments() 0 17 4
A getValidationPattern() 0 26 2
A setUrl() 0 6 2
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\Helpers\Url as UrlUtility;
13
use WPEmerge\Requests\RequestInterface;
14
use WPEmerge\Support\Arr;
15
16
/**
17
 * Check against the current url
18
 */
19
class UrlCondition implements ConditionInterface {
20
	const WILDCARD = '*';
21
22
	/**
23
	 * URL to check against.
24
	 *
25
	 * @var string
26
	 */
27
	protected $url = '';
28
29
	/**
30
	 * URL where.
31
	 *
32
	 * @var array<string, string>
33
	 */
34
	protected $url_where = [];
35
36
	/**
37
	 * Pattern to detect parameters in urls.
38
	 *
39
	 * @suppress HtmlUnknownTag
40
	 * @var string
41
	 */
42
	protected $url_pattern = '~
43
		(?:/)                     # match leading slash
44
		(?:\{)                    # opening curly brace
45
			(?P<name>[a-z]\w*)    # string starting with a-z and followed by word characters for the parameter name
46
			(?P<optional>\?)?     # optionally allow the user to mark the parameter as option using literal ?
47
		(?:\})                    # closing curly brace
48
		(?=/)                     # lookahead for a trailing slash
49
	~ix';
50
51
	/**
52
	 * Pattern to detect valid parameters in url segments.
53
	 *
54
	 * @var string
55
	 */
56
	protected $parameter_pattern = '[^/]+';
57
58
	/**
59
	 * Constructor.
60
	 *
61
	 * @codeCoverageIgnore
62
	 * @param string $url
63
	 * @param array<string, string> $where
64
	 */
65
	public function __construct( $url, $where = [] ) {
66
		$this->setUrl( $url );
67
		$this->setUrlWhere( $where );
68
	}
69
70
	/**
71
	 * Make a new instance.
72
	 *
73
	 * @codeCoverageIgnore
74
	 * @param string $url
75
	 * @param array<string, string> $where
76
	 * @return self
77
	 */
78
	protected function make( $url, $where = [] ) {
79
		return new self( $url, $where );
80
	}
81
82
	/**
83
	 * {@inheritDoc}
84
	 */
85 1
	protected function whereIsSatisfied( RequestInterface $request ) {
86 1
		$where = $this->getUrlWhere();
87 1
		$arguments = $this->getArguments( $request );
88
89 1
		foreach ( $where as $parameter => $pattern ) {
90 1
			$value = Arr::get( $arguments, $parameter, '' );
91
92 1
			if ( ! preg_match( $pattern, $value ) ) {
93 1
				return false;
94
			}
95 1
		}
96
97 1
		return true;
98
	}
99
100
	/**
101
	 * {@inheritDoc}
102
	 */
103 3
	public function isSatisfied( RequestInterface $request ) {
104 3
		if ( $this->getUrl() === static::WILDCARD ) {
105 1
			return true;
106
		}
107
108 2
		$validation_pattern = $this->getValidationPattern( $this->getUrl() );
109 2
		$url = UrlUtility::getPath( $request );
110 2
		$match = (bool) preg_match( $validation_pattern, $url );
111
112 2
		if ( ! $match || empty( $this->getUrlWhere() ) ) {
113 2
			return $match;
114
		}
115
116 1
		return $this->whereIsSatisfied( $request );
117
	}
118
119
	/**
120
	 * {@inheritDoc}
121
	 */
122 1
	public function getArguments( RequestInterface $request ) {
123 1
		$validation_pattern = $this->getValidationPattern( $this->getUrl() );
124 1
		$url = UrlUtility::getPath( $request );
125 1
		$matches = [];
126 1
		$success = preg_match( $validation_pattern, $url, $matches );
127
128 1
		if ( ! $success ) {
129 1
			return []; // this should not normally happen
130
		}
131
132 1
		$arguments = [];
133 1
		$parameter_names = $this->getParameterNames( $this->getUrl() );
134 1
		foreach ( $parameter_names as $parameter_name ) {
135 1
			$arguments[ $parameter_name ] = isset( $matches[ $parameter_name ] ) ? $matches[ $parameter_name ] : '';
136 1
		}
137
138 1
		return $arguments;
139
	}
140
141
	/**
142
	 * Get the url for this condition.
143
	 *
144
	 * @return string
145
	 */
146 2
	public function getUrl() {
147 2
		return $this->url;
148
	}
149
150
	/**
151
	 * Set the url for this condition.
152
	 *
153
	 * @param  string $url
154
	 * @return void
155
	 */
156 2
	public function setUrl( $url ) {
157 2
		if ( $url !== static::WILDCARD ) {
158 1
			$url = UrlUtility::addLeadingSlash( UrlUtility::addTrailingSlash( $url ) );
159 1
		}
160
161 2
		$this->url = $url;
162 2
	}
163
164
	/**
165
	 * {@inheritDoc}
166
	 */
167 1
	public function getUrlWhere() {
168 1
		return $this->url_where;
169
	}
170
171
	/**
172
	 * {@inheritDoc}
173
	 */
174 1
	public function setUrlWhere( $where ) {
175 1
		$this->url_where = $where;
176 1
	}
177
178
	/**
179
	 * Append a url to this one returning a new instance.
180
	 *
181
	 * @param  string                $url
182
	 * @param  array<string, string> $where
183
	 * @return static
184
	 */
185 3
	public function concatenate( $url, $where = [] ) {
186 3
		if ( $this->getUrl() === static::WILDCARD || $url === static::WILDCARD ) {
187 1
			return $this->make( static::WILDCARD );
188
		}
189
190 2
		$leading = UrlUtility::addLeadingSlash( UrlUtility::removeTrailingSlash( $this->getUrl() ), true );
191 2
		$trailing = UrlUtility::addLeadingSlash( UrlUtility::addTrailingSlash( $url ) );
192
193 2
		$concatenated = $this->make( $leading . $trailing, array_merge(
194 2
			$this->getUrlWhere(),
195
			$where
196 2
		) );
197
198 2
		return $concatenated;
199
	}
200
201
	/**
202
	 * Get parameter names as defined in the url.
203
	 *
204
	 * @param  string        $url
205
	 * @return array<string>
206
	 */
207 1
	protected function getParameterNames( $url ) {
208 1
		$matches = [];
209 1
		preg_match_all( $this->url_pattern, $url, $matches );
210 1
		return $matches['name'];
211
	}
212
213
	/**
214
	 * Validation pattern replace callback.
215
	 *
216
	 * @param  array  $matches
217
	 * @param  array  $parameters
218
	 * @return string
219
	 */
220 1
	protected function replacePatternParameterWithPlaceholder( $matches, &$parameters ) {
221 1
		$name = $matches['name'];
222 1
		$optional = ! empty( $matches['optional'] );
223
224 1
		$replacement = '/(?P<' . $name . '>' . $this->parameter_pattern . ')';
225
226 1
		if ( $optional ) {
227 1
			$replacement = '(?:' . $replacement . ')?';
228 1
		}
229
230 1
		$hash = sha1( implode( '_', [
231 1
			count( $parameters ),
232 1
			$replacement,
233 1
			uniqid( 'wpemerge_', true ),
234 1
		] ) );
235 1
		$placeholder = '___placeholder_' . $hash . '___';
236 1
		$parameters[ $placeholder ] = $replacement;
237
238 1
		return $placeholder;
239
	}
240
241
	/**
242
	 * Get pattern to test whether normal urls match the parameter-based one.
243
	 *
244
	 * @param  string  $url
245
	 * @param  boolean $wrap
246
	 * @return string
247
	 */
248 1
	public function getValidationPattern( $url, $wrap = true ) {
249 1
		$parameters = [];
250
251
		// Replace all parameters with placeholders
252 1
		$validation_pattern = preg_replace_callback( $this->url_pattern, function ( $matches ) use ( &$parameters ) {
253 1
			return $this->replacePatternParameterWithPlaceholder( $matches, $parameters );
254 1
		}, $url );
255
256
		// Quote the remaining string so that it does not get evaluated as a pattern.
257 1
		$validation_pattern = preg_quote( $validation_pattern, '~' );
258
259
		// Replace the placeholders with the real parameter patterns.
260 1
		$validation_pattern = str_replace(
261 1
			array_keys( $parameters ),
262 1
			array_values( $parameters ),
263
			$validation_pattern
264 1
		);
265
266
		// Match the entire url; make trailing slash optional.
267 1
		$validation_pattern = '^' . $validation_pattern . '?$';
268
269 1
		if ( $wrap ) {
270 1
			$validation_pattern = '~' . $validation_pattern . '~';
271 1
		}
272
273 1
		return $validation_pattern;
274
	}
275
}
276