Passed
Push — master ( 2d893f...5de32f )
by Atanas
02:41
created

replacePatternParameterWithPlaceholder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 2
dl 0
loc 19
ccs 13
cts 13
cp 1
crap 2
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   WPEmerge
4
 * @author    Atanas Angelov <[email protected]>
5
 * @copyright 2018 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
	 * {@inheritDoc}
72
	 */
73 1
	protected function whereIsSatisfied( RequestInterface $request ) {
74 1
		$where = $this->getUrlWhere();
75 1
		$arguments = $this->getArguments( $request );
76
77 1
		foreach ( $where as $parameter => $pattern ) {
78 1
			$value = Arr::get( $arguments, $parameter, '' );
79
80 1
			if ( ! preg_match( $pattern, $value ) ) {
81 1
				return false;
82
			}
83
		}
84
85 1
		return true;
86
	}
87
88
	/**
89
	 * {@inheritDoc}
90
	 */
91 3
	public function isSatisfied( RequestInterface $request ) {
92 3
		if ( $this->getUrl() === static::WILDCARD ) {
93 1
			return true;
94
		}
95
96 2
		$validation_pattern = $this->getValidationPattern( $this->getUrl() );
97 2
		$url = UrlUtility::getPath( $request );
98 2
		$match = (bool) preg_match( $validation_pattern, $url );
99
100 2
		if ( ! $match || empty( $this->getUrlWhere() ) ) {
101 2
			return $match;
102
		}
103
104 1
		return $this->whereIsSatisfied( $request );
105
	}
106
107
	/**
108
	 * {@inheritDoc}
109
	 */
110 1
	public function getArguments( RequestInterface $request ) {
111 1
		$validation_pattern = $this->getValidationPattern( $this->getUrl() );
112 1
		$url = UrlUtility::getPath( $request );
113 1
		$matches = [];
114 1
		$success = preg_match( $validation_pattern, $url, $matches );
115
116 1
		if ( ! $success ) {
117 1
			return []; // this should not normally happen
118
		}
119
120 1
		$arguments = [];
121 1
		$parameter_names = $this->getParameterNames( $this->getUrl() );
122 1
		foreach ( $parameter_names as $parameter_name ) {
123 1
			$arguments[ $parameter_name ] = isset( $matches[ $parameter_name ] ) ? $matches[ $parameter_name ] : '';
124
		}
125
126 1
		return $arguments;
127
	}
128
129
	/**
130
	 * Get the url for this condition.
131
	 *
132
	 * @return string
133
	 */
134 2
	public function getUrl() {
135 2
		return $this->url;
136
	}
137
138
	/**
139
	 * Set the url for this condition.
140
	 *
141
	 * @param  string $url
142
	 * @return void
143
	 */
144 2
	public function setUrl( $url ) {
145 2
		if ( $url !== static::WILDCARD ) {
146 1
			$url = UrlUtility::addLeadingSlash( UrlUtility::addTrailingSlash( $url ) );
147
		}
148
149 2
		$this->url = $url;
150 2
	}
151
152
	/**
153
	 * {@inheritDoc}
154
	 */
155 1
	public function getUrlWhere() {
156 1
		return $this->url_where;
157
	}
158
159
	/**
160
	 * {@inheritDoc}
161
	 */
162 1
	public function setUrlWhere( $where ) {
163 1
		$this->url_where = $where;
164 1
	}
165
166
	/**
167
	 * Append a url to this one returning a new instance.
168
	 *
169
	 * @param  string                $url
170
	 * @param  array<string, string> $where
171
	 * @return static
172
	 */
173 3
	public function concatenate( $url, $where = [] ) {
174 3
		if ( $this->getUrl() === static::WILDCARD || $url === static::WILDCARD ) {
175 1
			return new static( static::WILDCARD );
176
		}
177
178 2
		$leading = UrlUtility::addLeadingSlash( UrlUtility::removeTrailingSlash( $this->getUrl() ), true );
179 2
		$trailing = UrlUtility::addLeadingSlash( UrlUtility::addTrailingSlash( $url ) );
180
181 2
		$concatenated = new static( $leading . $trailing, array_merge(
182 2
			$this->getUrlWhere(),
183 2
			$where
184
		) );
185
186 2
		return $concatenated;
187
	}
188
189
	/**
190
	 * Get parameter names as defined in the url.
191
	 *
192
	 * @param  string        $url
193
	 * @return array<string>
194
	 */
195 1
	protected function getParameterNames( $url ) {
196 1
		$matches = [];
197 1
		preg_match_all( $this->url_pattern, $url, $matches );
198 1
		return $matches['name'];
199
	}
200
201
	/**
202
	 * Validation pattern replace callback.
203
	 *
204
	 * @param  array  $matches
205
	 * @param  array  $parameters
206
	 * @return string
207
	 */
208 1
	protected function replacePatternParameterWithPlaceholder( $matches, &$parameters ) {
209 1
		$name = $matches['name'];
210 1
		$optional = ! empty( $matches['optional'] );
211
212 1
		$replacement = '/(?P<' . $name . '>' . $this->parameter_pattern . ')';
213
214 1
		if ( $optional ) {
215 1
			$replacement = '(?:' . $replacement . ')?';
216
		}
217
218 1
		$hash = sha1( implode( '_', [
219 1
			count( $parameters ),
220 1
			$replacement,
221 1
			uniqid( 'wpemerge_', true ),
222
		] ) );
223 1
		$placeholder = '___placeholder_' . $hash . '___';
224 1
		$parameters[ $placeholder ] = $replacement;
225
226 1
		return $placeholder;
227
	}
228
229
	/**
230
	 * Get pattern to test whether normal urls match the parameter-based one.
231
	 *
232
	 * @param  string  $url
233
	 * @param  boolean $wrap
234
	 * @return string
235
	 */
236 1
	public function getValidationPattern( $url, $wrap = true ) {
237 1
		$parameters = [];
238
239
		// Replace all parameters with placeholders
240 1
		$validation_pattern = preg_replace_callback( $this->url_pattern, function ( $matches ) use ( &$parameters ) {
241 1
			return $this->replacePatternParameterWithPlaceholder( $matches, $parameters );
242 1
		}, $url );
243
244
		// Quote the remaining string so that it does not get evaluated as a pattern.
245
		$validation_pattern = preg_quote( $validation_pattern, '~' );
246
247
		// Replace the placeholders with the real parameter patterns.
248
		$validation_pattern = str_replace( array_keys( $parameters ), array_values( $parameters ), $validation_pattern );
249
250
		// Match the entire url; make trailing slash optional.
251
		$validation_pattern = '^' . $validation_pattern . '?$';
252
253
		if ( $wrap ) {
254
			$validation_pattern = '~' . $validation_pattern . '~';
255
		}
256
257
		return $validation_pattern;
258
	}
259
}
260