├── .gitignore ├── CHANGELOG.md ├── releases.json ├── composer.json ├── LICENSE.txt ├── resources └── icon.svg ├── variables └── RouteMapVariable.php ├── RouteMapPlugin.php ├── controllers └── RouteMapController.php ├── services └── RouteMapService.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Route Map Changelog 2 | 3 | ## 1.0.0 -- 2017-08-27 4 | 5 | * Initial release 6 | 7 | Brought to you by [nystudio107](https://nystudio107.com) 8 | -------------------------------------------------------------------------------- /releases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "version": "1.0.0", 4 | "downloadUrl": "https://github.com/nystudio107/routemap/archive/master.zip", 5 | "date": "2017-08-27T21:06:02.849Z", 6 | "notes": [ 7 | "[Added] Initial release" 8 | ] 9 | } 10 | ] -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nystudio107/routemap", 3 | "description": "Returns a list of Craft/Vue/React route rules and entry & asset URLs for ServiceWorkers from Craft entries", 4 | "type": "craft-plugin", 5 | "authors": [ 6 | { 7 | "name": "nystudio107", 8 | "homepage": "https://nystudio107.com" 9 | } 10 | ], 11 | "require": { 12 | "composer/installers": "~1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The Route Map License 2 | Copyright (c) 2015 nystudio107 3 | 4 | Permission is hereby granted, free of charge, to any person or entity obtaining a copy of this software and associated documentation files (the "Software"), to use the software in any capacity, including commercial and for-profit use. Permission is also granted to alter, modify, or extend the Software for your own use, or commission a third-party to perform modifications for you. 5 | 6 | Permission is NOT granted to create derivative works, sublicense, and/or sell copies of the Software. This is not FOSS software. 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /variables/RouteMapVariable.php: -------------------------------------------------------------------------------- 1 | routeMap->getAllUrls($attributes); 32 | } 33 | 34 | /** 35 | * Return the public URLs for a section 36 | * 37 | * @param string $section 38 | * @param array $attributes array of attributes to set on the the 39 | * ElementCriteriaModel 40 | * 41 | * @return array 42 | */ 43 | public function getSectionUrls($section, $attributes = array()) 44 | { 45 | return craft()->routeMap->getSectionUrls($section, $attributes); 46 | } 47 | 48 | /** 49 | * Return all of the route rules 50 | * 51 | * @param string $format 'Craft'|'React'|'Vue' 52 | * 53 | * @return array 54 | */ 55 | public function getAllRouteRules($format) 56 | { 57 | return craft()->routeMap->getAllRouteRules($format); 58 | } 59 | 60 | /** 61 | * @param string $section 62 | * @param string $format 'Craft'|'React'|'Vue' 63 | * 64 | * @return array 65 | */ 66 | public function getSectionRouteRules($section, $format) 67 | { 68 | return craft()->routeMap->getSectionRouteRules($section, $format); 69 | } 70 | 71 | /** 72 | * Get all of the assets of the type $assetTypes that are used in the Entry 73 | * that matches the $url 74 | * 75 | * @param string $url 76 | * @param array $assetTypes 77 | * 78 | * @return array 79 | */ 80 | public function getUrlAssetUrls($url, $assetTypes = array('image')) 81 | { 82 | return craft()->routeMap->getUrlAssetUrls($url, $assetTypes = array('image')); 83 | } 84 | } -------------------------------------------------------------------------------- /RouteMapPlugin.php: -------------------------------------------------------------------------------- 1 | on('entries.onBeforeSaveEntry', function (Event $event) { 27 | craft()->routeMap->invalidateCache(); 28 | }); 29 | } 30 | 31 | /** 32 | * @return mixed 33 | */ 34 | public function getName() 35 | { 36 | return Craft::t('Route Map'); 37 | } 38 | 39 | /** 40 | * @return mixed 41 | */ 42 | public function getDescription() 43 | { 44 | return Craft::t('Returns a list of Craft/Vue/React route rules and entry & asset URLs for ServiceWorkers from Craft entries'); 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getDocumentationUrl() 51 | { 52 | return 'https://github.com/nystudio107/routemap/blob/master/README.md'; 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function getReleaseFeedUrl() 59 | { 60 | return 'https://raw.githubusercontent.com/nystudio107/routemap/master/releases.json'; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getVersion() 67 | { 68 | return '1.0.0'; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getSchemaVersion() 75 | { 76 | return '1.0.0'; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getDeveloper() 83 | { 84 | return 'nystudio107'; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getDeveloperUrl() 91 | { 92 | return 'https://nystudio107.com'; 93 | } 94 | 95 | /** 96 | * @return bool 97 | */ 98 | public function hasCpSection() 99 | { 100 | return false; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /controllers/RouteMapController.php: -------------------------------------------------------------------------------- 1 | request->getParam('attributes', array()); 42 | $this->returnJson( 43 | craft()->routeMap->getAllUrls($attributes) 44 | ); 45 | } 46 | 47 | /** 48 | * Return the public URLs for a section 49 | * 50 | * @param string $section 51 | * @param array $attributes array of attributes to set on the the 52 | * ElementCriteriaModel 53 | * 54 | * @return array 55 | */ 56 | public function actionGetSectionUrls($section) 57 | { 58 | $attributes = craft()->request->getParam('attributes', array()); 59 | $this->returnJson( 60 | craft()->routeMap->getSectionUrls($section, $attributes) 61 | ); 62 | } 63 | 64 | /** 65 | * Return all of the route rules 66 | * 67 | * @param string $format 'Craft'|'React'|'Vue' 68 | * 69 | * @return array 70 | */ 71 | public function actionGetAllRouteRules($format = 'Craft') 72 | { 73 | $this->returnJson( 74 | craft()->routeMap->getAllRouteRules($format) 75 | ); 76 | } 77 | 78 | /** 79 | * @param string $section 80 | * @param string $format 'Craft'|'React'|'Vue' 81 | * 82 | * @return array 83 | */ 84 | public function actionGetSectionRouteRules($section, $format = 'Craft') 85 | { 86 | $this->returnJson( 87 | craft()->routeMap->getSectionRouteRules($section, $format) 88 | ); 89 | } 90 | 91 | /** 92 | * Get all of the assets of the type $assetTypes that are used in the Entry 93 | * that matches the $url 94 | * 95 | * @param string $url 96 | * @param array $assetTypes 97 | * 98 | * @return array 99 | */ 100 | public function actionGetUrlAssetUrls($url) 101 | { 102 | $assetTypes = craft()->request->getParam('assetTypes', array('image')); 103 | $this->returnJson( 104 | craft()->routeMap->getUrlAssetUrls($url, $assetTypes) 105 | ); 106 | } 107 | } -------------------------------------------------------------------------------- /services/RouteMapService.php: -------------------------------------------------------------------------------- 1 | getCacheKey( 49 | $this::ROUTEMAP_CACHE_URLS . $this::ROUTEMAP_CACHE_ALLURLS, 50 | array( 51 | $attributes, 52 | ) 53 | ); 54 | $cachedData = $this->getCachedValue($cacheKey); 55 | if ($cachedData !== false) { 56 | return $cachedData; 57 | } 58 | // Get all of the sections 59 | $sections = craft()->sections->getAllSections(); 60 | foreach ($sections as $section) { 61 | if ($section->hasUrls) { 62 | $urls = array_merge( 63 | $urls, 64 | $this->getSectionUrls( 65 | $section->handle, 66 | $attributes 67 | ) 68 | ); 69 | } 70 | } 71 | 72 | // @TODO: Support CategoryGroups & Category URLs 73 | 74 | // @TODO: Commerce Products & Variant URLs 75 | 76 | // Cache the result 77 | $this->setCachedValue($cacheKey, $urls); 78 | 79 | return $urls; 80 | } 81 | 82 | /** 83 | * Return the public URLs for a section 84 | * 85 | * @param string $section 86 | * @param array $attributes array of attributes to set on the the 87 | * ElementCriteriaModel 88 | * 89 | * @return array 90 | */ 91 | public function getSectionUrls($section, $attributes = array()) 92 | { 93 | $urls = array(); 94 | if (!empty($section)) { 95 | // Just return the data if it's already cached 96 | $cacheKey = $this->getCacheKey( 97 | $this::ROUTEMAP_CACHE_URLS, 98 | array( 99 | $section, 100 | $attributes, 101 | ) 102 | ); 103 | $cachedData = $this->getCachedValue($cacheKey); 104 | if ($cachedData !== false) { 105 | return $cachedData; 106 | } 107 | 108 | // @TODO: This should be extended to handle multiple locales 109 | 110 | // Get all of the entries in the section 111 | $criteria = craft()->elements->getCriteria(ElementType::Entry); 112 | $criteria->section = $section; 113 | $criteria->limit = null; 114 | 115 | // Add in any custom attributes to set on the ElementCriteriaModel 116 | if (!empty($attributes)) { 117 | $criteria->setAttributes($attributes); 118 | } 119 | 120 | // Iterate through the entries and grab their URLs 121 | foreach ($criteria as $entry) { 122 | if (!in_array($entry->url, $urls, true)) { 123 | array_push($urls, $entry->url); 124 | } 125 | } 126 | 127 | // Cache the result 128 | $this->setCachedValue($cacheKey, $urls); 129 | } 130 | 131 | return $urls; 132 | } 133 | 134 | /** 135 | * Return all of the route rules 136 | * 137 | * @param string $format 'Craft'|'React'|'Vue' 138 | * 139 | * @return array 140 | */ 141 | public function getAllRouteRules($format = 'Craft') 142 | { 143 | $routeRules = array(); 144 | // Just return the data if it's already cached 145 | $cacheKey = $this->getCacheKey( 146 | $this::ROUTEMAP_CACHE_RULES . $this::ROUTEMAP_CACHE_ALLURLS, 147 | array( 148 | $format, 149 | ) 150 | ); 151 | $cachedData = $this->getCachedValue($cacheKey); 152 | if ($cachedData !== false) { 153 | return $cachedData; 154 | } 155 | // Get all of the sections 156 | $sections = craft()->sections->getAllSections(); 157 | foreach ($sections as $section) { 158 | if ($section->hasUrls) { 159 | $route = $this->getSectionRouteRules($section->handle, $format); 160 | if (!empty($route)) { 161 | $routeRules[$section->handle] = $route; 162 | } 163 | } 164 | } 165 | 166 | // @TODO: Support CategoryGroups & Category URLs 167 | 168 | // @TODO: Commerce Products & Variant URLs 169 | 170 | // Cache the result 171 | $this->setCachedValue($cacheKey, $routeRules); 172 | 173 | return $routeRules; 174 | } 175 | 176 | /** 177 | * @param string $section 178 | * @param string $format 'Craft'|'React'|'Vue' 179 | * 180 | * @return array 181 | */ 182 | public function getSectionRouteRules($section, $format = 'Craft') 183 | { 184 | $route = array(); 185 | // Just return the data if it's already cached 186 | $cacheKey = $this->getCacheKey( 187 | $this::ROUTEMAP_CACHE_RULES, 188 | array( 189 | $section, 190 | $format, 191 | ) 192 | ); 193 | $cachedData = $this->getCachedValue($cacheKey); 194 | if ($cachedData !== false) { 195 | return $cachedData; 196 | } 197 | // Get the actual section 198 | $sectionModel = craft()->sections->getSectionByHandle($section); 199 | if ($sectionModel) { 200 | // Get section data to return 201 | $route = array( 202 | 'handle' => $sectionModel->handle, 203 | 'type' => $sectionModel->type, 204 | 'url' => $sectionModel->getUrlFormat(), 205 | 'template' => $sectionModel->template, 206 | ); 207 | 208 | // @TODO: This should be extended to handle multiple locales 209 | 210 | // Normalize the routes based on the format 211 | $route = $this->normalizeFormat($format, $route); 212 | 213 | // Cache the result 214 | $this->setCachedValue($cacheKey, $route); 215 | } 216 | 217 | return $route; 218 | } 219 | 220 | /** 221 | * Get all of the assets of the type $assetTypes that are used in the Entry 222 | * that matches the $url 223 | * 224 | * @param string $url 225 | * @param array $assetTypes 226 | * 227 | * @return array 228 | */ 229 | public function getUrlAssetUrls($url, $assetTypes = array('image')) 230 | { 231 | $assetUrls = array(); 232 | // Extract a URI from the URL 233 | $uri = parse_url($url, PHP_URL_PATH); 234 | $uri = ltrim($uri, '/'); 235 | // Just return the data if it's already cached 236 | $cacheKey = $this->getCacheKey( 237 | $this::ROUTEMAP_CACHE_ASSETS, 238 | array( 239 | $uri, 240 | $assetTypes, 241 | ) 242 | ); 243 | $cachedData = $this->getCachedValue($cacheKey); 244 | if ($cachedData !== false) { 245 | return $cachedData; 246 | } 247 | 248 | // Find the element that matches this URI 249 | $element = craft()->elements->getElementByUri($uri, craft()->language, true); 250 | if ($element) { 251 | // Iterate through the fields in this Entry 252 | $fieldLayouts = $element->fieldLayout->getFields(); 253 | foreach ($fieldLayouts as $fieldLayout) { 254 | $field = craft()->fields->getFieldById($fieldLayout->fieldId); 255 | switch ($field->type) { 256 | // Get the URLs of all assets of the type $assetTypes 257 | case "FocusPoint_FocusPoint": 258 | case "Assets": 259 | $assets = $element[$field->handle]; 260 | foreach ($assets as $asset) { 261 | /** @var $asset AssetFileModel */ 262 | if (in_array($asset->kind, $assetTypes)) { 263 | if (!in_array($asset->getUrl(), $assetUrls, true)) { 264 | array_push($assetUrls, $asset->getUrl()); 265 | } 266 | } 267 | } 268 | break; 269 | 270 | // Iterate through all of the matrix blocks 271 | case "Matrix": 272 | $blocks = $element[$field->handle]; 273 | foreach ($blocks as $block) { 274 | /** @var $block MatrixBlockModel */ 275 | /** @var $matrixBlockTypeModel MatrixBlockTypeModel */ 276 | $matrixBlockTypeModel = $block->getType(); 277 | $matrixFields = $matrixBlockTypeModel->getFields(); 278 | 279 | foreach ($matrixFields as $matrixField) { 280 | switch ($matrixField->type) { 281 | // Get the URLs of all assets of the type $assetTypes 282 | case "FocusPoint_FocusPoint": 283 | case "Assets": 284 | $assets = $block[$matrixField->handle]; 285 | foreach ($assets as $asset) { 286 | /** @var $asset AssetFileModel */ 287 | if (in_array($asset->kind, $assetTypes)) { 288 | if (!in_array($asset->getUrl(), $assetUrls, true)) { 289 | array_push($assetUrls, $asset->getUrl()); 290 | } 291 | } 292 | } 293 | break; 294 | } 295 | } 296 | } 297 | break; 298 | 299 | // Iterate through all of the Neo blocks 300 | case "Neo": 301 | $blocks = $element[$field->handle]; 302 | foreach ($blocks as $block) { 303 | $neoBlockTypeModel = $block->getType(); 304 | $fieldLayout = craft()->fields->getLayoutById($neoBlockTypeModel->fieldLayoutId); 305 | $fieldLayoutFields = $fieldLayout->getFields(); 306 | 307 | // Iterate through the fieldLayoutFields 308 | foreach ($fieldLayoutFields as $fieldLayoutField) { 309 | $neoField = $fieldLayoutField->field; 310 | switch ($neoField->type) { 311 | // Get the URLs of all assets of the type $assetTypes 312 | case "FocusPoint_FocusPoint": 313 | case "Assets": 314 | $assets = $block[$neoField->handle]; 315 | foreach ($assets as $asset) { 316 | if (in_array($asset->kind, $assetTypes)) { 317 | if (!in_array($asset->getUrl(), $assetUrls, true)) { 318 | array_push($assetUrls, $asset->getUrl()); 319 | } 320 | } 321 | } 322 | break; 323 | } 324 | } 325 | } 326 | break; 327 | } 328 | } 329 | 330 | // Cache the result 331 | $this->setCachedValue($cacheKey, $assetUrls); 332 | } 333 | 334 | return $assetUrls; 335 | } 336 | 337 | /** 338 | * Invalidate the caches by setting the timestamp to now 339 | */ 340 | public function invalidateCache() 341 | { 342 | // Invalidate the caches by setting the timestamp to now 343 | $cacheKey = $this->getCacheKey( 344 | $this::ROUTEMAP_CACHE_PREFIX . $this::ROUTEMAP_CACHE_TIMESTAMP 345 | ); 346 | craft()->cache->set($cacheKey, time(), false); 347 | } 348 | 349 | // Protected Methods 350 | // ========================================================================= 351 | 352 | /** 353 | * Generate a cache key with the combination of the $prefix and an md5() 354 | * hashed version of the flattened $args array 355 | * 356 | * @param string $prefix 357 | * @param array $args 358 | * 359 | * @return string 360 | */ 361 | protected function getCacheKey($prefix, $args = array()) 362 | { 363 | $cacheKey = $prefix; 364 | $flattenedArgs = ''; 365 | // If an array of $args is passed in, flatten it into a concatenated string 366 | if (!empty($args)) { 367 | foreach ($args as $arg) { 368 | if ((is_object($arg) || is_array($arg)) && !empty($arg)) { 369 | $flattenedArgs .= http_build_query($arg); 370 | } 371 | if (is_string($arg)) { 372 | $flattenedArgs .= $arg; 373 | } 374 | } 375 | // Make an md5 hash out of it 376 | $flattenedArgs = md5($flattenedArgs); 377 | } 378 | 379 | return $cacheKey . $flattenedArgs; 380 | } 381 | 382 | /** 383 | * Get a value from our timestamped cache 384 | * 385 | * @param string $key 386 | * 387 | * @return bool|mixed 388 | */ 389 | protected function getCachedValue($key) 390 | { 391 | // If the cache timestamp doesn't exist, or is not set, assume the value is not cached 392 | $cacheKey = $this->getCacheKey( 393 | $this::ROUTEMAP_CACHE_PREFIX . $this::ROUTEMAP_CACHE_TIMESTAMP 394 | ); 395 | $cacheTimeStamp = craft()->cache->get($cacheKey); 396 | if (($cacheTimeStamp === false) || (!$cacheTimeStamp)) { 397 | return false; 398 | } 399 | 400 | // Get the cached data 401 | $cacheKey = $this->getCacheKey( 402 | $this::ROUTEMAP_CACHE_PREFIX . $key 403 | ); 404 | $data = craft()->cache->get($cacheKey); 405 | // If it's not in the cache, it's not in the cache 406 | if ($data === false) { 407 | return false; 408 | } 409 | // If there's no timestamp or data, assume it's not cached 410 | if (empty($data[$this::ROUTEMAP_CACHE_TIMESTAMP]) || empty($data[$this::ROUTEMAP_CACHE_DATA])) { 411 | return false; 412 | } 413 | // If the data timestamp is older than the cache timestamp, assume it's not cached 414 | $dataTimeStamp = $data[$this::ROUTEMAP_CACHE_TIMESTAMP]; 415 | if ($dataTimeStamp < $cacheTimeStamp) { 416 | return false; 417 | } 418 | 419 | // If we made it this far, return the cached data 420 | return $data[$this::ROUTEMAP_CACHE_DATA]; 421 | } 422 | 423 | /** 424 | * Set a value in our timestamped cache 425 | * 426 | * @param string $key 427 | * @param mixed $data 428 | */ 429 | protected function setCachedValue($key, $data) 430 | { 431 | // If the cache timestamp doesn't exist, set it 432 | $cacheKey = $this->getCacheKey( 433 | $this::ROUTEMAP_CACHE_PREFIX . $this::ROUTEMAP_CACHE_TIMESTAMP 434 | ); 435 | $cacheTimeStamp = craft()->cache->get($cacheKey); 436 | if (($cacheTimeStamp === false) || (!$cacheTimeStamp)) { 437 | $this->invalidateCache(); 438 | } 439 | // Bundle up the data into an array with a timestamp 440 | $cacheData = array( 441 | $this::ROUTEMAP_CACHE_TIMESTAMP => time(), 442 | $this::ROUTEMAP_CACHE_DATA => $data, 443 | ); 444 | // Cache the data 445 | $cacheKey = $this->getCacheKey( 446 | $this::ROUTEMAP_CACHE_PREFIX . $key 447 | ); 448 | craft()->cache->set($cacheKey, $cacheData, false); 449 | } 450 | 451 | /** 452 | * Normalize the routes based on the format 453 | * 454 | * @param string $format 'Craft'|'React'|'Vue' 455 | * @param array $route 456 | * 457 | * @return array 458 | */ 459 | protected function normalizeFormat($format, $route) 460 | { 461 | // Normalize the URL 462 | $route['url'] = $this->normalizeUri($route['url']); 463 | // Transform the URLs depending on the format requested 464 | switch ($format) { 465 | // React & Vue routes have a leading / and {slug} -> :slug 466 | case $this::ROUTE_FORMAT_REACT: 467 | case $this::ROUTE_FORMAT_VUE: 468 | $matchRegEx = "`{(.*?)}`i"; 469 | $replaceRegEx = ":$1"; 470 | $route['url'] = preg_replace($matchRegEx, $replaceRegEx, $route['url']); 471 | // Add a leading / 472 | $route['url'] = '/' . ltrim($route['url'], '/'); 473 | break; 474 | 475 | // Craft-style URLs don't need to be changed 476 | case $this::ROUTE_FORMAT_CRAFT: 477 | default: 478 | // Do nothing 479 | break; 480 | } 481 | 482 | return $route; 483 | } 484 | 485 | /** 486 | * Normalize the URI 487 | * 488 | * @param $url 489 | * 490 | * @return string 491 | */ 492 | protected function normalizeUri($url) 493 | { 494 | // Handle the special '__home__' URI 495 | if ($url == '__home__') { 496 | $url = '/'; 497 | } 498 | 499 | return $url; 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | # DEPRECATED 4 | 5 | This Craft CMS 2.x plugin is no longer supported, but it is fully functional, and you may continue to use it as you see fit. The license also allows you to fork it and make changes as needed for legacy support reasons. 6 | 7 | The Craft CMS 3.x version of this plugin can be found here: [craft-routemap](https://github.com/nystudio107/craft-routemap) and can also be installed via the Craft Plugin Store in the Craft CP. 8 | 9 | # Route Map plugin for Craft CMS 10 | 11 | Returns a list of Craft/Vue/React route rules and entry & asset URLs for ServiceWorkers from Craft entries 12 | 13 | ## Installation 14 | 15 | To install Route Map, follow these steps: 16 | 17 | 1. Download & unzip the file and place the `routemap` directory into your `craft/plugins` directory 18 | 2. -OR- do a `git clone https://github.com/nystudio107/routemap.git` directly into your `craft/plugins` folder. You can then update it with `git pull` 19 | 3. -OR- install with Composer via `composer require nystudio107/routemap` 20 | 4. Install plugin in the Craft Control Panel under Settings > Plugins 21 | 5. The plugin folder should be named `routemap` for Craft to see it. GitHub recently started appending `-master` (the branch name) to the name of the folder for zip file downloads. 22 | 23 | Route Map works on Craft 2.4.x and Craft 2.5.x. 24 | 25 | ## Route Map Overview 26 | 27 | Route Map is a plugin to help bridge the routing gap between frontend technologies like Vue/React and Craft CMS. Using Route Map, you can define your routes in Craft CMS as usual, and use an XHR to get a list of the routes in JSON format for use in your Vue/React frontend (it converts `blog/{slug}` dynamic routes to `/blog/:slug`). 28 | 29 | This allows you to create your routes dynamically in Craft CMS using the AdminCP, and have them translate automatically to your frontend framework of choice. 30 | 31 | Route Map also assists with [ServiceWorkers](https://nystudio107.com/blog/service-workers-and-offline-browsing) by providing a list of all of the URLs on your Craft CMS site, or just the specific sections you're interested in. You can limit the URLs returned via any `ElementCriteriaModel` attributes, and Route Map can even return a list of URLs to all of the Assets that a particular Entry uses (whether in Assets fields, or embedded in Matrix/Neo blocks). 32 | 33 | This allows you, for instance, to have a ServiceWorker that will automatically pre-cache the latest 5 blog entries on your site, as well as any images displayed on those pages, so that they will work with offline browsing. 34 | 35 | Route Map maintains a cache of each requested set of URLs for excellent performance for repeated requests. This cache is automatically cleared whenever entries are created or modified. 36 | 37 | ## Configuring Route Map 38 | 39 | There's nothing to configure. 40 | 41 | ## Using Route Map via XHR 42 | 43 | ### Route Rules 44 | 45 | The controller API endpoint `/admin/actions/routeMap/getAllRouteRules` will return all of your website's route rules in an associative array. By default, they are in Craft CMS format (e.g.: `blog/{slug}`): 46 | 47 | ``` 48 | { 49 | "notFound": { 50 | "handle": "notFound", 51 | "type": "single", 52 | "url": "404", 53 | "template": "404" 54 | }, 55 | "blog": { 56 | "handle": "blog", 57 | "type": "channel", 58 | "url": "blog\/{slug}", 59 | "template": "blog\/_entry" 60 | }, 61 | "blogIndex": { 62 | "handle": "blogIndex", 63 | "type": "single", 64 | "url": "blog", 65 | "template": "blog\/index" 66 | }, 67 | "homepage": { 68 | "handle": "homepage", 69 | "type": "single", 70 | "url": "\/", 71 | "template": "index" 72 | } 73 | } 74 | ``` 75 | 76 | The `format` URL parameter allows you to specify either `Craft` | `React` | `Vue` format for your URL routes. For example, the controller API endpoint `/admin/actions/routeMap/getAllRouteRules?format=Vue` will return the same route rules above, but formatted for `Vue` (e.g.: `blog/:slug`): 77 | 78 | ``` 79 | { 80 | "notFound": { 81 | "handle": "notFound", 82 | "type": "single", 83 | "url": "\/404", 84 | "template": "404" 85 | }, 86 | "blog": { 87 | "handle": "blog", 88 | "type": "channel", 89 | "url": "\/blog\/:slug", 90 | "template": "blog\/_entry" 91 | }, 92 | "blogIndex": { 93 | "handle": "blogIndex", 94 | "type": "single", 95 | "url": "\/blog", 96 | "template": "blog\/index" 97 | }, 98 | "homepage": { 99 | "handle": "homepage", 100 | "type": "single", 101 | "url": "\/", 102 | "template": "index" 103 | } 104 | } 105 | ``` 106 | 107 | Note that `blog\/{slug}` was changed to `blog\/:slug`. This allows you to easily map both static and dynamic Craft CMS routes to your router of choice. 108 | 109 | If you want just the route rules for a particular section, you can use the controller API endpoint `/admin/actions/routeMap/getSectionRouteRules?section=blog` (note the required `section` parameter that specifies the Section handle you want): 110 | 111 | ``` 112 | { 113 | "handle": "blog", 114 | "type": "channel", 115 | "url": "blog\/{slug}", 116 | "template": "blog\/_entry" 117 | } 118 | ``` 119 | 120 | You can also pass in the optional `format` parameter to get route rules from a specific section, in a particular format via the controller API endpoint `/admin/actions/routeMap/getSectionRouteRules?section=blog&format=Vue` 121 | 122 | ``` 123 | { 124 | "handle": "blog", 125 | "type": "channel", 126 | "url": "blog\/:slug", 127 | "template": "blog\/_entry" 128 | } 129 | ``` 130 | 131 | ### Entry URLs 132 | 133 | The controller API endpoint `/admin/actions/routeMap/getAllUrls` will return a list of _all_ of the URLs to all of the Entries on your website: 134 | 135 | ``` 136 | [ 137 | "http:\/\/nystudio107.dev\/404", 138 | "http:\/\/nystudio107.dev\/blog\/a-gulp-workflow-for-frontend-development-automation", 139 | "http:\/\/nystudio107.dev\/blog\/making-websites-accessible-americans-with-disabilities-act-ada", 140 | "http:\/\/nystudio107.dev\/blog\/static-caching-with-craft-cms", 141 | "http:\/\/nystudio107.dev\/blog\/the-case-of-the-missing-php-session", 142 | "http:\/\/nystudio107.dev\/blog\/so-you-wanna-make-a-craft-3-plugin", 143 | "http:\/\/nystudio107.dev\/blog\/a-b-split-testing-with-nginx-craft-cms", 144 | "http:\/\/nystudio107.dev\/blog\/mobile-testing-local-dev-sharing-with-homestead", 145 | "http:\/\/nystudio107.dev\/blog\/simple-static-asset-versioning", 146 | "http:\/\/nystudio107.dev\/blog\/tags-gone-wild", 147 | "http:\/\/nystudio107.dev\/blog\/local-development-with-vagrant-homestead", 148 | "http:\/\/nystudio107.dev\/blog\/mitigating-disaster-via-website-backups", 149 | "http:\/\/nystudio107.dev\/blog\/web-hosting-for-agencies-freelancers", 150 | "http:\/\/nystudio107.dev\/blog\/implementing-critical-css", 151 | "http:\/\/nystudio107.dev\/blog\/autocomplete-search-with-the-element-api-vuejs", 152 | "http:\/\/nystudio107.dev\/blog\/json-ld-structured-data-and-erotica", 153 | "http:\/\/nystudio107.dev\/blog\/craft-3-beta-executive-summary", 154 | "http:\/\/nystudio107.dev\/blog\/prevent-google-from-indexing-staging-sites", 155 | "http:\/\/nystudio107.dev\/blog\/loadjs-as-a-lightweight-javascript-loader", 156 | "http:\/\/nystudio107.dev\/blog\/creating-a-content-builder-in-craft-cms", 157 | "http:\/\/nystudio107.dev\/blog\/service-workers-and-offline-browsing", 158 | "http:\/\/nystudio107.dev\/blog\/using-phpstorm-with-vagrant-homestead", 159 | "http:\/\/nystudio107.dev\/blog\/frontend-dev-best-practices-for-2017", 160 | "http:\/\/nystudio107.dev\/blog\/using-systemjs-as-javascript-loader", 161 | "http:\/\/nystudio107.dev\/blog\/a-better-package-json-for-the-frontend", 162 | "http:\/\/nystudio107.dev\/blog\/modern-seo-snake-oil-vs-substance", 163 | "http:\/\/nystudio107.dev\/blog\/lazy-loading-with-the-element-api-vuejs", 164 | "http:\/\/nystudio107.dev\/blog\/installing-mozjpeg-on-ubuntu-16-04-forge", 165 | "http:\/\/nystudio107.dev\/blog\/a-pretty-website-isnt-enough", 166 | "http:\/\/nystudio107.dev\/blog\/using-vuejs-2-0-with-craft-cms", 167 | "http:\/\/nystudio107.dev\/blog\/image-optimization-project-results", 168 | "http:\/\/nystudio107.dev\/blog\/database-asset-syncing-between-environments-in-craft-cms", 169 | "http:\/\/nystudio107.dev\/blog\/hardening-craft-cms-permissions", 170 | "http:\/\/nystudio107.dev\/blog\/multi-environment-config-for-craft-cms", 171 | "http:\/\/nystudio107.dev\/blog\/google-amp-should-you-care", 172 | "http:\/\/nystudio107.dev\/blog\/creating-optimized-images-in-craft-cms", 173 | "http:\/\/nystudio107.dev\/blog\/the-craft-cache-tag-in-depth", 174 | "http:\/\/nystudio107.dev\/blog\/twig-processing-order-and-scope", 175 | "http:\/\/nystudio107.dev\/blog\/stop-using-htaccess-files-no-really", 176 | "http:\/\/nystudio107.dev\/blog", 177 | "http:\/\/nystudio107.dev\/" 178 | ] 179 | ``` 180 | 181 | You can retrieve just the entries for a particular section via the controller API endpoint `/admin/actions/routeMap/getSectionUrls?section=blog` (note the required `section` parameter that specifies the Section handle you want): 182 | 183 | ``` 184 | [ 185 | "http:\/\/nystudio107.dev\/blog\/a-gulp-workflow-for-frontend-development-automation", 186 | "http:\/\/nystudio107.dev\/blog\/making-websites-accessible-americans-with-disabilities-act-ada", 187 | "http:\/\/nystudio107.dev\/blog\/static-caching-with-craft-cms", 188 | "http:\/\/nystudio107.dev\/blog\/the-case-of-the-missing-php-session", 189 | "http:\/\/nystudio107.dev\/blog\/so-you-wanna-make-a-craft-3-plugin", 190 | "http:\/\/nystudio107.dev\/blog\/a-b-split-testing-with-nginx-craft-cms", 191 | "http:\/\/nystudio107.dev\/blog\/mobile-testing-local-dev-sharing-with-homestead", 192 | "http:\/\/nystudio107.dev\/blog\/simple-static-asset-versioning", 193 | "http:\/\/nystudio107.dev\/blog\/tags-gone-wild", 194 | "http:\/\/nystudio107.dev\/blog\/local-development-with-vagrant-homestead", 195 | "http:\/\/nystudio107.dev\/blog\/mitigating-disaster-via-website-backups", 196 | "http:\/\/nystudio107.dev\/blog\/web-hosting-for-agencies-freelancers", 197 | "http:\/\/nystudio107.dev\/blog\/implementing-critical-css", 198 | "http:\/\/nystudio107.dev\/blog\/autocomplete-search-with-the-element-api-vuejs", 199 | "http:\/\/nystudio107.dev\/blog\/json-ld-structured-data-and-erotica", 200 | "http:\/\/nystudio107.dev\/blog\/craft-3-beta-executive-summary", 201 | "http:\/\/nystudio107.dev\/blog\/prevent-google-from-indexing-staging-sites", 202 | "http:\/\/nystudio107.dev\/blog\/loadjs-as-a-lightweight-javascript-loader", 203 | "http:\/\/nystudio107.dev\/blog\/creating-a-content-builder-in-craft-cms", 204 | "http:\/\/nystudio107.dev\/blog\/service-workers-and-offline-browsing", 205 | "http:\/\/nystudio107.dev\/blog\/using-phpstorm-with-vagrant-homestead", 206 | "http:\/\/nystudio107.dev\/blog\/frontend-dev-best-practices-for-2017", 207 | "http:\/\/nystudio107.dev\/blog\/using-systemjs-as-javascript-loader", 208 | "http:\/\/nystudio107.dev\/blog\/a-better-package-json-for-the-frontend", 209 | "http:\/\/nystudio107.dev\/blog\/modern-seo-snake-oil-vs-substance", 210 | "http:\/\/nystudio107.dev\/blog\/lazy-loading-with-the-element-api-vuejs", 211 | "http:\/\/nystudio107.dev\/blog\/installing-mozjpeg-on-ubuntu-16-04-forge", 212 | "http:\/\/nystudio107.dev\/blog\/a-pretty-website-isnt-enough", 213 | "http:\/\/nystudio107.dev\/blog\/using-vuejs-2-0-with-craft-cms", 214 | "http:\/\/nystudio107.dev\/blog\/image-optimization-project-results", 215 | "http:\/\/nystudio107.dev\/blog\/database-asset-syncing-between-environments-in-craft-cms", 216 | "http:\/\/nystudio107.dev\/blog\/hardening-craft-cms-permissions", 217 | "http:\/\/nystudio107.dev\/blog\/multi-environment-config-for-craft-cms", 218 | "http:\/\/nystudio107.dev\/blog\/google-amp-should-you-care", 219 | "http:\/\/nystudio107.dev\/blog\/creating-optimized-images-in-craft-cms", 220 | "http:\/\/nystudio107.dev\/blog\/the-craft-cache-tag-in-depth", 221 | "http:\/\/nystudio107.dev\/blog\/twig-processing-order-and-scope", 222 | "http:\/\/nystudio107.dev\/blog\/stop-using-htaccess-files-no-really" 223 | ] 224 | ``` 225 | 226 | Both of the above controller API endpoints support an optional `attributes` parameter that lets you pass in an array of `ElementCriteriaModel` attribute key/value pairs to be used to refine the Entries selected. 227 | 228 | For instance, if you wanted just the most recent 5 Entries from the `blog` section, you'd use the controller API endpoint `/admin/actions/routeMap/getSectionUrls?section=blog&attributes[limit]=5`: 229 | 230 | ``` 231 | [ 232 | "http:\/\/nystudio107.dev\/blog\/a-gulp-workflow-for-frontend-development-automation", 233 | "http:\/\/nystudio107.dev\/blog\/making-websites-accessible-americans-with-disabilities-act-ada", 234 | "http:\/\/nystudio107.dev\/blog\/static-caching-with-craft-cms", 235 | "http:\/\/nystudio107.dev\/blog\/the-case-of-the-missing-php-session", 236 | "http:\/\/nystudio107.dev\/blog\/so-you-wanna-make-a-craft-3-plugin" 237 | ] 238 | ``` 239 | 240 | Or if you wanted the 5 oldest Entries from the `blog` section, you'd use the controller API endpoint `/admin/actions/routeMap/getSectionUrls?section=blog&attributes[limit]=5&attributes[order]=postDate asc`: 241 | 242 | ``` 243 | [ 244 | "http:\/\/nystudio107.dev\/blog\/stop-using-htaccess-files-no-really", 245 | "http:\/\/nystudio107.dev\/blog\/twig-processing-order-and-scope", 246 | "http:\/\/nystudio107.dev\/blog\/the-craft-cache-tag-in-depth", 247 | "http:\/\/nystudio107.dev\/blog\/creating-optimized-images-in-craft-cms", 248 | "http:\/\/nystudio107.dev\/blog\/google-amp-should-you-care" 249 | ] 250 | ``` 251 | 252 | ### Entry URL Assets 253 | 254 | The controller API endpoint `/admin/actions/routeMap/getUrlAssetUrls?url=/blog/tags-gone-wild` will return all of the image Assets from the Entry with the URI of `/blog/tags-gone-wild`, whether in Assets fields, or embedded in Matrix/Neo blocks (note the required `url` parameter that specifies the URL to the entry you want): 255 | 256 | ``` 257 | [ 258 | "http:\/\/nystudio107.dev\/img\/blog\/buried-in-tag-manager-tags.jpg", 259 | "http:\/\/nystudio107.dev\/img\/blog\/they-told-two-friends.png", 260 | "http:\/\/nystudio107.dev\/img\/blog\/tag-manager-tags-gone-wild.png", 261 | "http:\/\/nystudio107.dev\/img\/blog\/google-chrome-activity-indicator.png", 262 | "http:\/\/nystudio107.dev\/img\/blog\/tag-javascript-executing.png", 263 | "http:\/\/nystudio107.dev\/img\/blog\/tags-are-prescription-drugs.jpg", 264 | "http:\/\/nystudio107.dev\/img\/blog\/taming-tags-whip.jpg" 265 | ] 266 | ``` 267 | 268 | Either a full URL or a partial URI can be passed in via the `url` parameter. 269 | 270 | By default, it only returns Assets of the type `image` but using the optional parameter `assetTypes` you can pass in an array of the types of Assets you want returned. For instance, if we wanted `image`, `video`, and `pdf` Assets returned, we'd use the controller API endpoint `/admin/actions/routeMap/getUrlAssetUrls?url=/blog/tags-gone-wild&assetTypes[0]=image&assetTypes[1]=video&assetTypes[2]=pdf'`. 271 | 272 | ## Using Route Map in your Twig Templates 273 | 274 | You can also access any of the aforementioned functionality from within Craft CMS Twig templates. 275 | 276 | ### Route Rules 277 | 278 | To get all of your website's route rules: 279 | 280 | ``` 281 | {% set routeRules = craft.routeMap.getAllRouteRules() %} 282 | ``` 283 | 284 | To specify the format that the route rules should be returned in, pass in either `Craft` | `React` | `Vue`: 285 | 286 | ``` 287 | {% set routeRules = craft.routeMap.getAllRouteRules('Vue') %} 288 | ``` 289 | 290 | To get route rules from only a specific section (such as `blog`, in this case), pass in the Section handle: 291 | 292 | ``` 293 | {% set routeRules = craft.routeMap.getSectionRouteRules('blog') %} 294 | ``` 295 | 296 | You can also pass in the optional `format` parameter to get route rules from a specific section, in a particular format: 297 | 298 | ``` 299 | {% set routeRules = craft.routeMap.getSectionRouteRules('blog', 'Vue') %} 300 | ``` 301 | 302 | ### Entry URLs 303 | 304 | To get all of your website's public Entry URLs: 305 | 306 | ``` 307 | {% set urls = craft.routeMap.getAllUrls() %} 308 | ``` 309 | 310 | To refine the URLs returned, you can pass in optional `ElementCriteriaModel` attributes via key/value pairs: 311 | 312 | ``` 313 | {% set urls = craft.routeMap.getAllUrls({'limit': 5}) %} 314 | ``` 315 | 316 | or 317 | 318 | ``` 319 | {% set urls = craft.routeMap.getAllUrls({'limit': 5, 'order': 'postDate asc'}) %} 320 | ``` 321 | 322 | To get URLs from just a specific Section: 323 | 324 | ``` 325 | {% set urls = craft.routeMap.getSectionUrls('blog') %} 326 | ``` 327 | 328 | To refine the URLs returned, you can pass in optional `ElementCriteriaModel` attributes via key/value pairs: 329 | 330 | ``` 331 | {% set urls = craft.routeMap.getSectionUrls('blog', {'limit': 5}) %} 332 | ``` 333 | 334 | or 335 | 336 | ``` 337 | {% set urls = craft.routeMap.getSectionUrls('blog', {'limit': 5, 'order': 'postDate asc'}) %} 338 | ``` 339 | 340 | ### Entry URL Assets 341 | 342 | To get all of the Asset URLs in a particular Entry (whether in Assets fields or embedded in Matrix/Neo blocks) by passing in a URL or URI to the entry: 343 | 344 | ``` 345 | {% set urls = craft.routeMap.getUrlAssetUrls('/blog/tags-gone-wild') %} 346 | ``` 347 | 348 | By default, it returns only Assets of the type `image`. You can pass in an optional array of Asset types you want returned: 349 | 350 | ``` 351 | {% set urls = craft.routeMap.getUrlAssetUrls('/blog/tags-gone-wild', ['image', 'video', 'pdf']) %} 352 | ``` 353 | 354 | ## Using Route Map from your Plugins 355 | 356 | The `craft()->routeMap` service gives you access to all of the functions mentioned above via your plugins. 357 | 358 | ### Route Rules 359 | 360 | To get all of your website's route rules: 361 | 362 | ``` 363 | $routeRules = craft()->routeMap->getAllRouteRules(); 364 | ``` 365 | 366 | To specify the format that the route rules should be returned in, pass in either `Craft` | `React` | `Vue`: 367 | 368 | ``` 369 | $routeRules = craft()->routeMap->getAllRouteRules('Vue'); 370 | ``` 371 | 372 | To get route rules from only a specific section (such as `blog`, in this case), pass in the Section handle: 373 | 374 | ``` 375 | $routeRules = craft()->routeMap->getSectionRouteRules('blog'); 376 | ``` 377 | 378 | You can also pass in the optional `format` parameter to get route rules from a specific section, in a particular format: 379 | 380 | ``` 381 | $routeRules = craft()->routeMap->getSectionRouteRules('blog', 'Vue'); 382 | ``` 383 | 384 | ### Entry URLs 385 | 386 | To get all of your website's public Entry URLs: 387 | 388 | ``` 389 | $urls = craft()->routeMap->getAllUrls(); 390 | ``` 391 | 392 | To refine the URLs returned, you can pass in optional `ElementCriteriaModel` attributes via key/value pairs: 393 | 394 | ``` 395 | $urls = craft()->routeMap->getAllUrls(array('limit' => 5)); 396 | ``` 397 | 398 | or 399 | 400 | ``` 401 | $urls = craft()->routeMap->getAllUrls(array('limit' => 5, 'order' => 'postDate asc')); 402 | ``` 403 | 404 | To get URLs from just a specific Section: 405 | 406 | ``` 407 | $urls = craft()->routeMap->getSectionUrls('blog'); 408 | ``` 409 | 410 | To refine the URLs returned, you can pass in optional `ElementCriteriaModel` attributes via key/value pairs: 411 | 412 | ``` 413 | $urls = craft()->routeMap->getSectionUrls('blog', array('limit' => 5)); 414 | ``` 415 | 416 | or 417 | 418 | ``` 419 | $urls = craft()->routeMap->getSectionUrls('blog', array('limit' => 5, 'order' => 'postDate asc')); 420 | ``` 421 | 422 | ### Entry URL Assets 423 | 424 | To get all of the Asset URLs in a particular Entry (whether in Assets fields or embedded in Matrix/Neo blocks) by passing in a URL or URI to the entry: 425 | 426 | ``` 427 | $urls = craft()->routeMap->getUrlAssetUrls('/blog/tags-gone-wild'); 428 | ``` 429 | 430 | By default, it returns only Assets of the type `image`. You can pass in an optional array of Asset types you want returned: 431 | 432 | ``` 433 | $urls = craft()->routeMap->getUrlAssetUrls('/blog/tags-gone-wild', array('image', 'video', 'pdf')); 434 | ``` 435 | 436 | ## Route Map Roadmap 437 | 438 | Some things to do, and ideas for potential features: 439 | 440 | * Add support for Category Groups / Category URLs 441 | * Add support for Commerce Products / Variant URLs 442 | * Add support for multiple locales 443 | 444 | Brought to you by [nystudio107](https://nystudio107.com) 445 | --------------------------------------------------------------------------------