/libraries/src/Component/Router/Rules/StandardRules.php

https://github.com/joomla/joomla-cms · PHP · 275 lines · 153 code · 51 blank · 71 comment · 41 complexity · 19ef20cc8e1266a0bcfb18e1365968f8 MD5 · raw file

  1. <?php
  2. /**
  3. * Joomla! Content Management System
  4. *
  5. * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
  6. * @license GNU General Public License version 2 or later; see LICENSE.txt
  7. */
  8. namespace Joomla\CMS\Component\Router\Rules;
  9. use Joomla\CMS\Component\Router\RouterView;
  10. /**
  11. * Rule for the standard handling of component routing
  12. *
  13. * @since 3.4
  14. */
  15. class StandardRules implements RulesInterface
  16. {
  17. /**
  18. * Router this rule belongs to
  19. *
  20. * @var RouterView
  21. * @since 3.4
  22. */
  23. protected $router;
  24. /**
  25. * Class constructor.
  26. *
  27. * @param RouterView $router Router this rule belongs to
  28. *
  29. * @since 3.4
  30. */
  31. public function __construct(RouterView $router)
  32. {
  33. $this->router = $router;
  34. }
  35. /**
  36. * Dummy method to fulfil the interface requirements
  37. *
  38. * @param array &$query The query array to process
  39. *
  40. * @return void
  41. *
  42. * @since 3.4
  43. */
  44. public function preprocess(&$query)
  45. {
  46. }
  47. /**
  48. * Parse the URL
  49. *
  50. * @param array &$segments The URL segments to parse
  51. * @param array &$vars The vars that result from the segments
  52. *
  53. * @return void
  54. *
  55. * @since 3.4
  56. */
  57. public function parse(&$segments, &$vars)
  58. {
  59. // Get the views and the currently active query vars
  60. $views = $this->router->getViews();
  61. $active = $this->router->menu->getActive();
  62. if ($active) {
  63. $vars = array_merge($active->query, $vars);
  64. }
  65. // We don't have a view or its not a view of this component! We stop here
  66. if (!isset($vars['view']) || !isset($views[$vars['view']])) {
  67. return;
  68. }
  69. // Copy the segments, so that we can iterate over all of them and at the same time modify the original segments
  70. $tempSegments = $segments;
  71. // Iterate over the segments as long as a segment fits
  72. foreach ($tempSegments as $segment) {
  73. // Our current view is nestable. We need to check first if the segment fits to that
  74. if ($views[$vars['view']]->nestable) {
  75. if (\is_callable(array($this->router, 'get' . ucfirst($views[$vars['view']]->name) . 'Id'))) {
  76. $key = \call_user_func_array(array($this->router, 'get' . ucfirst($views[$vars['view']]->name) . 'Id'), array($segment, $vars));
  77. // Did we get a proper key? If not, we need to look in the child-views
  78. if ($key) {
  79. $vars[$views[$vars['view']]->key] = $key;
  80. array_shift($segments);
  81. continue;
  82. }
  83. } else {
  84. // The router is not complete. The get<View>Id() method is missing.
  85. return;
  86. }
  87. }
  88. // Lets find the right view that belongs to this segment
  89. $found = false;
  90. foreach ($views[$vars['view']]->children as $view) {
  91. if (!$view->key) {
  92. if ($view->name === $segment) {
  93. // The segment is a view name
  94. $parent = $views[$vars['view']];
  95. $vars['view'] = $view->name;
  96. $found = true;
  97. if ($view->parent_key && isset($vars[$parent->key])) {
  98. $parent_key = $vars[$parent->key];
  99. $vars[$view->parent_key] = $parent_key;
  100. unset($vars[$parent->key]);
  101. }
  102. break;
  103. }
  104. } elseif (\is_callable(array($this->router, 'get' . ucfirst($view->name) . 'Id'))) {
  105. // Hand the data over to the router specific method and see if there is a content item that fits
  106. $key = \call_user_func_array(array($this->router, 'get' . ucfirst($view->name) . 'Id'), array($segment, $vars));
  107. if ($key) {
  108. // Found the right view and the right item
  109. $parent = $views[$vars['view']];
  110. $vars['view'] = $view->name;
  111. $found = true;
  112. if ($view->parent_key && isset($vars[$parent->key])) {
  113. $parent_key = $vars[$parent->key];
  114. $vars[$view->parent_key] = $parent_key;
  115. unset($vars[$parent->key]);
  116. }
  117. $vars[$view->key] = $key;
  118. break;
  119. }
  120. }
  121. }
  122. if (!$found) {
  123. return;
  124. }
  125. array_shift($segments);
  126. }
  127. }
  128. /**
  129. * Build a standard URL
  130. *
  131. * @param array &$query The vars that should be converted
  132. * @param array &$segments The URL segments to create
  133. *
  134. * @return void
  135. *
  136. * @since 3.4
  137. */
  138. public function build(&$query, &$segments)
  139. {
  140. if (!isset($query['Itemid'], $query['view'])) {
  141. return;
  142. }
  143. // Get the menu item belonging to the Itemid that has been found
  144. $item = $this->router->menu->getItem($query['Itemid']);
  145. if (
  146. $item === null
  147. || $item->component !== 'com_' . $this->router->getName()
  148. || !isset($item->query['view'])
  149. ) {
  150. return;
  151. }
  152. // Get menu item layout
  153. $mLayout = isset($item->query['layout']) ? $item->query['layout'] : null;
  154. // Get all views for this component
  155. $views = $this->router->getViews();
  156. // Return directly when the URL of the Itemid is identical with the URL to build
  157. if ($item->query['view'] === $query['view']) {
  158. $view = $views[$query['view']];
  159. if (!$view->key) {
  160. unset($query['view']);
  161. if (isset($query['layout']) && $mLayout === $query['layout']) {
  162. unset($query['layout']);
  163. }
  164. return;
  165. }
  166. if (isset($query[$view->key]) && $item->query[$view->key] == (int) $query[$view->key]) {
  167. unset($query[$view->key]);
  168. while ($view) {
  169. unset($query[$view->parent_key]);
  170. $view = $view->parent;
  171. }
  172. unset($query['view']);
  173. if (isset($query['layout']) && $mLayout === $query['layout']) {
  174. unset($query['layout']);
  175. }
  176. return;
  177. }
  178. }
  179. // Get the path from the view of the current URL and parse it to the menu item
  180. $path = array_reverse($this->router->getPath($query), true);
  181. $found = false;
  182. foreach ($path as $element => $ids) {
  183. $view = $views[$element];
  184. if ($found === false && $item->query['view'] === $element) {
  185. if ($view->nestable) {
  186. $found = true;
  187. } elseif ($view->children) {
  188. $found = true;
  189. continue;
  190. }
  191. }
  192. if ($found === false) {
  193. // Jump to the next view
  194. continue;
  195. }
  196. if ($ids) {
  197. if ($view->nestable) {
  198. $found2 = false;
  199. foreach (array_reverse($ids, true) as $id => $segment) {
  200. if ($found2) {
  201. $segments[] = str_replace(':', '-', $segment);
  202. } elseif ((int) $item->query[$view->key] === (int) $id) {
  203. $found2 = true;
  204. }
  205. }
  206. } elseif ($ids === true) {
  207. $segments[] = $element;
  208. } else {
  209. $segments[] = str_replace(':', '-', current($ids));
  210. }
  211. }
  212. if ($view->parent_key) {
  213. // Remove parent key from query
  214. unset($query[$view->parent_key]);
  215. }
  216. }
  217. if ($found) {
  218. unset($query[$views[$query['view']]->key], $query['view']);
  219. if (isset($query['layout']) && $mLayout === $query['layout']) {
  220. unset($query['layout']);
  221. }
  222. }
  223. }
  224. }