PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.20
VikAppointments Services Booking Calendar v1.2.20
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / adapter / application / route.php
vikappointments / libraries / adapter / application Last commit date
application.php 1 month ago document.php 1 month ago object.php 1 month ago registry.php 1 month ago route.php 1 month ago version.php 1 month ago
route.php
475 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.application
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Route adapter class.
16 *
17 * @since 10.0
18 */
19 class JRoute
20 {
21 /**
22 * A list of component shortcodes.
23 *
24 * @var array
25 */
26 protected static $shortcodes = array();
27
28 /**
29 * Translates an internal URL to a human-readable URL.
30 *
31 * @param string $url Absolute or Relative URI.
32 * @param boolean $xhtml Replace & by &amp; for XML compliance.
33 * @param integer $ssl Secure state for the resolved URI.
34 * 0: (default) No change, use the protocol currently used in the request.
35 * 1: Make URI secure using global secure site URI.
36 * 2: Make URI unsecure using the global unsecure site URI.
37 *
38 * @return string The translated humanly readable URL.
39 *
40 * @uses getPermalink()
41 * @uses replace()
42 */
43 public static function _($url, $xhtml = true, $ssl = null)
44 {
45 // parse URL to obtain query args (true: as array)
46 $args = JUri::getInstance($url)->getQuery(true);
47
48 // prepend current URI if starts with 'index.php'
49 if (strpos($url, 'index.php') === 0)
50 {
51 $url = static::getPermalink();
52 $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($args);
53 }
54
55 // if 'option' and 'view' arguments are not empty, try to route URLs
56 if (!empty($args['option']) && (!empty($args['view']) || !empty($args['Itemid'])))
57 {
58 $url = static::replace($args, $url);
59 }
60
61 /**
62 * Prepend base URL in case the system
63 * was not able to find a permalink.
64 *
65 * @since 10.1.29
66 */
67 if (preg_match("/^\?/", $url))
68 {
69 $url = JUri::root() . $url;
70 }
71
72 // replace & with &amp; for XML compliance
73 if ($xhtml)
74 {
75 $url = str_replace('&', '&amp;', $url);
76 }
77
78 if ($ssl == 1)
79 {
80 // force HTTPS
81 $url = str_replace('http://', 'https://', $url);
82 }
83 else if ($ssl == 2)
84 {
85 // force HTTP
86 $url = str_replace('https://', 'http://', $url);
87 }
88
89 return $url;
90 }
91
92 /**
93 * Proxy for underscore method.
94 * Needed to bypass the issue reported here:
95 * @link https://meta.trac.wordpress.org/ticket/3601
96 *
97 * @since 10.1.32 Renamed from `u`.
98 */
99 public static function rewrite($url, $xhtml = true, $ssl = null)
100 {
101 return static::_($url, $xhtml, $ssl);
102 }
103
104 /**
105 * Replaces the plain URL with the rewritten one.
106 *
107 * @param array $args The URL parts.
108 * @param string $url The URL to rewrite.
109 *
110 * @return string The rewritten URL, if possible.
111 *
112 * @uses getPermalink()
113 * @uses matchShortcode()
114 * @uses withQueryString()
115 */
116 protected static function replace(array $args, $url)
117 {
118 // make safe by replacing initial 'com_' (if any)
119 $option = preg_replace("/^com_/", "", $args['option']);
120 unset($args['option']);
121
122 // check if the shortcodes have been already cached
123 if (!isset(static::$shortcodes[$option]))
124 {
125 // get model to access shortcodes
126 $model = JModel::getInstance($option, 'shortcodes', 'admin');
127
128 /**
129 * Make sure the model exists for the requested option.
130 *
131 * @since 10.1.19
132 */
133 if ($model)
134 {
135 // cache the component shortcodes
136 static::$shortcodes[$option] = $model->all();
137 }
138 else
139 {
140 // create an empty list
141 static::$shortcodes[$option] = array();
142 }
143 }
144
145 $post_id = false;
146
147 if (!empty($args['view']))
148 {
149 // try to rewrite the URL with a matching shortcode
150 $post_id = static::matchShortcode(static::$shortcodes[$option], $args);
151 }
152
153 if (!$post_id && !empty($args['Itemid']))
154 {
155 // if the Itemid is set, get the POST permalink directly
156 $post_id = (int) $args['Itemid'];
157 }
158
159 if ($post_id)
160 {
161 $url = static::getPermalink($post_id);
162 $url = static::withQueryString($url, $post_id, static::$shortcodes[$option], $args);
163 }
164
165 /**
166 * Try to instantiate an external router.
167 *
168 * @since 10.1.19
169 */
170 $router = JFactory::getApplication()->getRouter($option);
171
172 if ($router)
173 {
174 // route URL using the specified arguments
175 $url = $router->build($url);
176 }
177
178 return $url;
179 }
180
181 /**
182 * Wrapper method to extend the function used to obtain
183 * the current permalink or the post link.
184 *
185 * @param mixed $post_id The post ID. Null to obtain the current link.
186 *
187 * @return string The permalink.
188 */
189 protected static function getPermalink($post_id = null)
190 {
191 // if the post ID is set, get the permalink directly
192 if ($post_id)
193 {
194 /**
195 * Added support to WPML, since it is unable to properly obtain the
196 * correct permalink depending the requested language tag.
197 *
198 * @since 10.1.55
199 */
200 $wpml_post_lang = apply_filters('wpml_post_language_details', null, $post_id);
201
202 /**
203 * Make sure we haven't received an error object from WPML.
204 *
205 * @since 10.1.60
206 */
207 if ($wpml_post_lang && !is_wp_error($wpml_post_lang))
208 {
209 // WPML supported, go ahead
210 return apply_filters('wpml_permalink', get_permalink($post_id), $wpml_post_lang['language_code']);
211 }
212
213 // WPML not supported, fallback to the default behavior
214 return get_permalink($post_id);
215 }
216
217 // otherwise get the current permalink
218 $link = get_permalink();
219
220 // return the permalink only if it is not empty
221 if ($link)
222 {
223 return $link;
224 }
225
226 // obtain the post ID from the current URL
227 $post_id = url_to_postid(JUri::current());
228
229 // if the current URL doesn't match any post, return an empty string
230 if (!$post_id)
231 {
232 return '';
233 }
234
235 // recursive call to obtain the permalink of the post found
236 return static::getPermalink($post_id);
237 }
238
239 /**
240 * Returns a URL containing the specified query string.
241 *
242 * @param string $url The URL to which append the query string.
243 * @param integer $post_id The post ID.
244 * @param array $shortcodes The shortcodes list.
245 * @param array $args The args to build the query string.
246 *
247 * @return string The URL with the query string.
248 */
249 protected static function withQueryString($url, $post_id, array $shortcodes, array $args = array())
250 {
251 // unset always the Item ID
252 unset($args['Itemid']);
253
254 // iterate the shortcodes
255 foreach ($shortcodes as $shortcode)
256 {
257 // process only if the given post ID matches the one assigned to the shortcode
258 if ($post_id == $shortcode->post_id)
259 {
260 // get shortcodes parameters, view and language
261 $json = (array) json_decode($shortcode->json, true);
262 $json['view'] = $shortcode->type;
263 $json['lang'] = $shortcode->lang;
264
265 // get the keys that the shortcode doesn't contain
266 $diff = self::diff($args, $json);
267
268 // if there are differences between the args, append them to the URL
269 if (count($diff))
270 {
271 $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($diff);
272 }
273
274 // return the URL with the query string (do not proceed)
275 return $url;
276 }
277 }
278
279 if (count($args))
280 {
281 /**
282 * if no shortcodes found, attach the whole query string
283 *
284 * @since 10.1.8
285 */
286 $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($args);
287 }
288
289 // nothing found, return plain URL
290 return $url;
291 }
292
293 /**
294 * Checks if there is a shortcode object that matches the given URL parts.
295 * In addition, the shortcode MUST own an existing post ID.
296 *
297 * @param array $shortcodes The shortcode objects list.
298 * @param array $args The URL query parts.
299 *
300 * @return mixed The post ID found, false on failure.
301 */
302 protected static function matchShortcode($shortcodes, array $args)
303 {
304 $post_id = false;
305 $last_count = 0;
306
307 if (empty($args['lang']))
308 {
309 // inject current langtag within URL arguments, only if not specified
310 // by query string
311 $args['lang'] = JFactory::getLanguage()->getTag();
312 }
313
314 foreach ($shortcodes as $shortcode)
315 {
316 $count = 0;
317
318 // decode JSON data
319 $json = json_decode($shortcode->json, true);
320 // inject VIEW name
321 $json['view'] = $shortcode->type;
322 // inject shortcode LANGTAG
323 $json['lang'] = $shortcode->lang;
324
325 // proceed only if the view is the same
326 if ($json['view'] == $args['view'])
327 {
328 // iterate the URL arguments
329 foreach ($args as $k => $v)
330 {
331 // the property should be matched only if the shortcode
332 // owns a parameter with the same name (defined in xml view)
333 // and value.
334 if (isset($json[$k]))
335 {
336 /**
337 * Improved the comparison of the language tag, which might be
338 * handled in different ways from different sections of WP.
339 *
340 * @since 10.1.38
341 */
342 if ($k === 'lang')
343 {
344 // check whether at least a language code doesn't mention the country code
345 if (!preg_match("/[_\-]/", $json[$k]) || !preg_match("/[_\-]/", $v))
346 {
347 // remove country code from locale assigned to the shortcode
348 $json[$k] = preg_split("/[_\-]/", $json[$k]);
349 $json[$k] = array_shift($json[$k]);
350
351 // remove country code from URL locale
352 $v = preg_split("/[_\-]/", $v);
353 $v = array_shift($v);
354 }
355
356 // normalize locale separators
357 $json[$k] = str_replace('-', '_', $json[$k]);
358 $v = str_replace('-', '_', $v);
359 }
360
361 // make sure both the values are scalar
362 if (is_scalar($json[$k]) && is_scalar($v))
363 {
364 if (!strcmp($json[$k], $v))
365 {
366 // if the shortcode owns the parameter and it is equals
367 // to the one specified in the URL, increase the counter
368 $count++;
369 }
370 }
371 /**
372 * Added support for arrays in query string, such as cid[].
373 *
374 * @since 10.1.27
375 */
376 else
377 {
378 // treat both the arguments as arrays
379 $json[$k] = (array) $json[$k];
380 $v = (array) $v;
381
382 // sort them to maximize the possibility of having matching URLs
383 sort($json[$k]);
384 sort($v);
385
386 if ($json[$k] == $v)
387 {
388 // increase the counter as we have a matching parameter
389 $count++;
390 }
391 }
392 }
393 }
394
395 // if the count of matches is higher than the last one,
396 // update the flags with the shortcode post ID and the current count
397 if ($count > $last_count)
398 {
399 $post_id = $shortcode->post_id;
400 $last_count = $count;
401 }
402 }
403 }
404
405 return $post_id;
406 }
407
408 /**
409 * Implementation of array_diff_assoc with recursion.
410 *
411 * @param array array1 The array to compare from.
412 * @param array array2 An array to compare against.
413 *
414 * @return array Returns an array containing all the values from array1
415 * that are not present in the second array.
416 *
417 * @since 10.1.27
418 */
419 private static function diff($array1, $array2)
420 {
421 $difference = array();
422
423 foreach ($array1 as $key => $value)
424 {
425 /**
426 * Improved the comparison of the language tag, which might be
427 * handled in different ways from different sections of WP.
428 *
429 * @since 10.1.49
430 */
431 if ($key === 'lang')
432 {
433 // check whether at least a language code doesn't mention the country code
434 if (!preg_match("/[_\-]/", $value) || !preg_match("/[_\-]/", $array2[$key]))
435 {
436 // remove country code from locale assigned to the shortcode
437 $array2[$key] = preg_split("/[_\-]/", $array2[$key]);
438 $array2[$key] = array_shift($array2[$key]);
439
440 // remove country code from URL locale
441 $value = preg_split("/[_\-]/", $value);
442 $value = array_shift($value);
443 }
444
445 // normalize locale separators
446 $array2[$key] = str_replace('-', '_', $array2[$key]);
447 $value = str_replace('-', '_', $value);
448 }
449
450 if (is_array($value))
451 {
452 if (!isset($array2[$key]) || !is_array($array2[$key]))
453 {
454 $difference[$key] = $value;
455 }
456 else
457 {
458 $new_diff = self::diff($value, $array2[$key]);
459
460 if (!empty($new_diff))
461 {
462 $difference[$key] = $new_diff;
463 }
464 }
465 }
466 else if (!array_key_exists($key, $array2) || $array2[$key] !== $value)
467 {
468 $difference[$key] = $value;
469 }
470 }
471
472 return $difference;
473 }
474 }
475