├── LICENSE ├── README.markdown └── system └── expressionengine └── third_party └── resource_router ├── addon.setup.php ├── ext.resource_router.php ├── libraries └── ResourceRouter │ ├── Router.php │ └── Wildcard.php ├── pi.resource_router.php └── views └── index.html /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Rob Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Resource Router 2 | 3 | Control your URLs by remapping URI routes to a specific HTTP response, using [CodeIgniter-style](http://ellislab.com/codeigniter/user-guide/general/routing.html) routing rules. 4 | 5 | ## Why? 6 | 7 | * You need to break out of the `template_group/template_name` paradigm of traditional EE routing 8 | * You need to nest urls on a Structure/Pages URI (pagination, categories, etc.) 9 | * You need to point multiple url patterns to a single template 10 | * You need custom JSON/HTML endpoints that do not warrant a full EE template 11 | * You want to remove excess conditional logic in your templates 12 | 13 | Replace this: 14 | 15 | {if segment 2 == "category"} 16 | {embed="blog/.category"} 17 | {if:elseif segment_2 == "view"} 18 | {embed="blog/.single"} 19 | {if:else} 20 | {embed="blog/.listing"} 21 | {/if} 22 | 23 | With this: 24 | 25 | 'blog/:any' => function ($router, $wildcard) { 26 | if ($wildcard->is('category')) { 27 | $router->setTemplate('blog/.category'); 28 | } elseif ($wildcard->is('view')) { 29 | $router->setTemplate('blog/.single'); 30 | } else { 31 | $router->setTemplate('blog/.listing'); 32 | } 33 | }, 34 | 35 | ## Installation 36 | 37 | *NOTE:* ExpressionEngine 2.6+ or ExpressionEngine 3.0+ and PHP 5.3+ are required 38 | 39 | * Copy the /system/expressionengine/third_party/resource_router/ folder to your /system/expressionengine/third_party/ folder 40 | * Install the extension 41 | 42 | ## Updating from Template Routes 43 | 44 | This add-on was formerly known as Template Routes. If you are updating from Template Routes, you should: 45 | 46 | * Uninstall Template Routes 47 | * Install Resource Router 48 | * Change your `$config['template_routes']` to `$config['resource_router']` 49 | 50 | ## Basic Usage 51 | 52 | ### Setting up your routing rules 53 | 54 | Your routing rules must be set in your system/expressionengine/config/config.php file. 55 | 56 | $config['resource_router'] = array( 57 | 'blog/:category' => 'site/blog-category', 58 | 'blog/:year/:pagination' => 'site/blog-yearly-archive', 59 | ); 60 | 61 | On the left is the URI pattern you wish to match, and on the right is one of two things: a) a string representing a `template_group/template_name` pair, or b) a callback receiving Router and Wilcard objects where you can craft a response for the URL. The callback functionality is covered in the [Advanced Usage](#advanced-usage) section. 62 | 63 | NOTE: Pages module or Structure module URLs take precendence over Resource Router. 64 | 65 | ### Wildcards 66 | 67 | #### :any 68 | 69 | Matches any non-backslash character(s). The equivalent regular expression is `([^/]+)`; 70 | 71 | #### :num 72 | 73 | Matches a numeric value. The equivalent regular expression is `(\d+)`; 74 | 75 | #### :year 76 | 77 | Matches 4 digits in a row. The equivalent regular expression is `(\d{4})`; 78 | 79 | #### :month 80 | 81 | Matches 2 digits in a row. The equivalent regular expression is `(\d{2})`; 82 | 83 | #### :day 84 | 85 | Matches 2 digits in a row. The equivalent regular expression is `(\d{2})`; 86 | 87 | #### :pagination 88 | 89 | Matches a P:num segment. The equivalent regular expression is `((?:/P\d+)?)`. This is an *optional* segment. If not present in the URI, the URI will still be considered a match. If so, the Wildcard object will have a `null` value. 90 | 91 | #### :category 92 | 93 | Matches `/`. The Category URL Indicator is set in Admin > Channel Administration > Global Preferences. The second segment value depends on the "Use Category URL Titles In Links?" setting. 94 | 95 | #### :page:XX 96 | 97 | Matches a Pages/Structure URI for the specified entry_id, where XX is the entry_id 98 | 99 | $config['resource_router'] = array( 100 | ':page:123/:pagination' => 'site/page', 101 | ); 102 | 103 | #### :all 104 | 105 | Matches all possible segments. The equivalent regular expression is `((?:/.*)?)`. This is an *optional* segment. If not present in the URI, the URI will still be considered a match. If so, the Wildcard object will have a `null` value. When multiple segments are detected, the callback will receive many Wildcard objects. 106 | 107 | #### :before 108 | 109 | Register a callback to be run **before** ALL routes. For instance, this is a good place to set global variables that you need on all pages. 110 | 111 | NOTE: This is a special wildcard. Your route must be exactly `':before'` with no other segments. 112 | 113 | ### Validating Wildcards 114 | 115 | These wildcards will perform a database operation to ensure that the wildcard value exists in the database. To validate on additional columns (ex. `status` or `channel`) you should use [Callbacks](#callbacks)). If you provide a callback, as opposed to a `template_group/template_name` string, the wildcard will *not* be validated. You must validate it yourself in your callback. 116 | 117 | Validating wildcards will have corresponding meta data attached to the wildcard object, and will set `{route_X_foo}` variables, where `foo` is a column from the database, such as cat_id. (Think Low Seg2cat). 118 | 119 | #### :entry_id 120 | 121 | Matches an entry id. Does not match if the entry id is not found in the database. To validate on additional columns, you should use a [Callback](#callbacks) and [`$wildcard->isValidEntryId()`](#wildcard-isvalidentryidwhere--array). Adds the following variables: `{route_X_entry_id}`, `{route_X_title}`, `{route_X_url_title}`, `{route_X_channel_id}`, where X is the specified URL segment. 122 | 123 | #### :url_title 124 | 125 | Matches a url title. Does not match if the url title is not found in the database. To validate on additional columns, you should use a [Callback](#callbacks) and [`$wildcard->isValidUrlTitle()`](#wildcard-isvalidurltitlewhere--array). Adds the following variables: `{route_X_entry_id}`, `{route_X_title}`, `{route_X_url_title}`, `{route_X_channel_id}`, where X is the specified URL segment. 126 | 127 | #### :category_id 128 | 129 | Matches a category id. Does not match if the category id is not found in the database. To validate on additional columns (ex. `group_id` or `channel`), you should use a [Callback](#callbacks) and [`$wildcard->isValidCategoryId()`](#wildcard-isvalidcategoryidwhere--array). Adds the following variables: `{route_X_cat_id}`, `{route_X_site_id}`, `{route_X_group_id}`, `{route_X_parent_id}`, `{route_X_cat_name}`, `{route_X_cat_url_title}`, `{route_X_cat_description}`, `{route_X_cat_image}`, `{route_X_cat_order'}`, where X is the specified URL segment. 130 | 131 | #### :category_url_title 132 | 133 | Matches a category url title. Does not match if the category url title is not found in the database. To validate on additional columns (ex. `group_id` or `channel`), you should use a [Callback](#callbacks) and [`$wildcard->isValidCategoryUrlTitle()`](#wildcard-isvalidcategoryurltitlewhere--array). Adds the following variables: `{route_X_cat_id}`, `{route_X_site_id}`, `{route_X_group_id}`, `{route_X_parent_id}`, `{route_X_cat_name}`, `{route_X_cat_url_title}`, `{route_X_cat_description}`, `{route_X_cat_image}`, `{route_X_cat_order'}`, where X is the specified URL segment. 134 | 135 | #### :member_id 136 | 137 | Matches a member id. Does not match if the member id is not found in the database. To validate on additional columns (ex. `group_id` or `channel`), you should use a [Callback](#callbacks) and [`$wildcard->isValidMemberId()`](#wildcard-isvalidmemberidwhere--array). Adds the following variables: `{route_X_member_id}`, `{route_X_group_id}`, `{route_X_email}`, `{route_X_username}`, `{route_X_screen_name}`, where X is the specified URL segment. 138 | 139 | #### :username 140 | 141 | Matches a username. Does not match if the username is not found in the database. To validate on additional columns (ex. `group_id` or `channel`), you should use a [Callback](#callbacks) and [`$wildcard->isValidUsername()`](#wildcard-isvalidusernamewhere--array). Adds the following variables: `{route_X_member_id}`, `{route_X_group_id}`, `{route_X_email}`, `{route_X_username}`, `{route_X_screen_name}`, where X is the specified URL segment. 142 | 143 | ### Matches 144 | 145 | All wildcards and any parenthesized regular expression patterns will be available within your template as a tag variable: 146 | 147 | {route_1} - the first wildcard/parenthesized match 148 | {route_2} - the 2nd, and so forth 149 | 150 | These matches are also available in your template definition, using `$1`, `$2` and so forth: 151 | 152 | $config['resource_router'] = array( 153 | 'blog/:any/:any' => 'site/$1_$2', 154 | ); 155 | 156 | ### Regular Expressions 157 | 158 | Like standard CodeIgniter routing, you may also use regular expressions in your routing rules: 159 | 160 | $config['resource_router'] = array( 161 | 'blog/([A-Z])/:any' => 'blog/alphabetized', 162 | ); 163 | 164 | Don't forget to wrap in parentheses if you would like your regular expression to become a Wildcard and `{route_X}` variable. 165 | 166 | ## Advanced Usage 167 | 168 | ### Callbacks 169 | 170 | You can use callbacks in your routes: 171 | 172 | $config['resource_router'] = array( 173 | 'blog/:any/:any' => function($router, $wildcard_1, $wildcard_2) { 174 | $router->setTemplate('blog/single'); 175 | } 176 | ); 177 | 178 | If you wish to output an EE template at the specified url pattern, your callback should set a valid `template_group/template` string using the `$router->setTemplate()` method. 179 | 180 | Or you can avoid setting a template to signify that this url does *not* match the route: 181 | 182 | 'blog/:any' => function($router, $wildcard) { 183 | if ($wildcard->is('foo')) { 184 | return; 185 | } 186 | $router->setTemplate('blog/single'); 187 | } 188 | 189 | Return a string to immediately output that string and avoid the template engine: 190 | 191 | 'blog/:any' => function($router, $wildcard) { 192 | return 'You found: '.$wildcard; 193 | } 194 | 195 | #### $router 196 | 197 | The first argument in the callback is a `rsanchez\ResourceRouter\Router` object. It has a few methods you can use. 198 | 199 | ##### $router->setTemplate(string $template) 200 | 201 | Set the `template_group/template_name` to use for this URI 202 | 203 | 'blog/:any' => function($router) { 204 | $router->setTemplate('template_group/template_name'); 205 | } 206 | 207 | ##### $router->set404() 208 | 209 | Trigger your EE 404 template. You must set a 404 Page template in your EE Global Template Preferences. 210 | 211 | 'blog/:any' => function($router) { 212 | $router->set404(); 213 | } 214 | 215 | ##### $router->setGlobal(string $key, string|int|bool $value) 216 | 217 | Set a global variable to use in your template. 218 | 219 | 'blog/:any' => function($router) { 220 | // {foo} -> bar 221 | $router->setGlobal('foo', 'bar'); 222 | } 223 | 224 | ##### $router->setVariable(string $key, mixed $value) 225 | 226 | Set tag pair arrays to use as variables in your template. These variables are accessible using the `{exp:resource_router:your_var_name}` template tags. 227 | 228 | 'blog/:any' => function($router) { 229 | // {exp:resource_router:foo} -> bar 230 | $router->setVariable('foo', 'bar'); 231 | 232 | // {exp:resource_router:foo}{bar}-{baz}{/exp:resource_router:foo} -> abc-def 233 | $router->setVariable('foo', array('bar' => 'abc', 'baz' => 'def')); 234 | 235 | // {exp:resource_router:foo}{bar}-{baz},{/exp:resource_router:foo} -> abc-def,ghi-jkl, 236 | $router->setVariable('foo', array( 237 | array('bar' => 'abc', 'baz' => 'def'), 238 | array('bar' => 'ghi', 'baz' => 'jkl'), 239 | )); 240 | } 241 | 242 | ##### $router->setWildcard(int $which, string|int|bool $value) 243 | 244 | Change the value of a wildcard at the specified index. 245 | 246 | 'blog/:any' => function($router) { 247 | // change a wildcard global variable {route_1} -> bar 248 | $router->setWildcard(1, 'bar'); 249 | } 250 | 251 | ##### $router->setContentType(string $content_type) 252 | 253 | Change the Content-Type HTTP response header. 254 | 255 | 'blog/:any' => function($router) { 256 | $router->setContentType('application/json'); 257 | return '{"foo":"bar"}'; 258 | } 259 | 260 | ##### $router->setHeader(string $name, string $value) 261 | 262 | Set an HTTP response header. 263 | 264 | 'blog/:any' => function($router) { 265 | $router->setHeader('Content-Type', 'application/json'); 266 | return '{"foo":"bar"}'; 267 | } 268 | 269 | ##### $router->setHttpStatus(int $code) 270 | 271 | Set the HTTP response status code. 272 | 273 | 'blog/:any' => function($router) { 274 | $router->setHttpStatus(401); 275 | } 276 | 277 | ##### $router->view(string $view, array $variables) 278 | 279 | Render a CodeIgniter view file 280 | 281 | 'blog/:any' => function($router) { 282 | $router->view('myview', array('foo' => 'bar')); 283 | } 284 | 285 | By default, view files are read from `system/expressionengine/third_party/resource_router/views/`. If you wish to change this, you can do so in your config file: 286 | 287 | $config['resource_router:package_path'] = '/path/to/directory/'; 288 | 289 | Your defined package path must have a `views` folder within it. You should not add `views/` to your defined package path. In other words, if your views are in `/var/www/assets/views/`, your package path should be set to `/var/www/assets/`. 290 | 291 | ##### $router->json(mixed $data) 292 | 293 | Send a JSON response of the data wwith `Content-Type: application/json` headers. 294 | 295 | 'blog/:any' => function($router) { 296 | $router->json(array('foo' => 'bar')); 297 | } 298 | 299 | ##### $router->redirect(string $url, int $statusCode = 301) 300 | 301 | Redirect to the specified URL or `template_group/template_name`. 302 | 303 | 'blog/:any' => function($router) { 304 | $router->redirect('foo/bar'); 305 | } 306 | 307 | ##### $router->setUri($uriString) 308 | 309 | Override the current uri being used by the system. 310 | 311 | 'blog/:any' => function($router, $wildcard) { 312 | $router->setUri('blog/view/'.$wildcard); 313 | } 314 | 315 | ##### $router->stopRouting() 316 | 317 | Stop any following routes that also match the url from being processed. 318 | 319 | 'blog/:any' => function($router) { 320 | $router->stopRouting(); 321 | } 322 | 323 | #### $wildcard 324 | 325 | The second and subsequent callback arguments are `rsanchez\ResourceRouter\Wildcard` objects. 326 | 327 | ##### $wildcard->value 328 | 329 | Get the value of the wildcard match. 330 | 331 | 'blog/:any/:any' => function($router, $wildcard_1, $wildcard_2) { 332 | $last_segment = $wildcard_2->value; 333 | 334 | // you may also simply cast to string 335 | $last_segment = (string) $wildcard_2; 336 | } 337 | 338 | ##### $wildcard->is($value) 339 | 340 | Check if the wildcard equals the specified value. This uses the PHP `==` operator internally. 341 | 342 | ``` 343 | 'blog/:url_title' => function($router, $wildcard) { 344 | if ($wildcard->is('some_special_post')) { 345 | $router->setTemplate('site/_blog_special'); 346 | } else { 347 | $router->setTemplate('site/_blog_detail'); 348 | } 349 | } 350 | ``` 351 | 352 | ##### $wildcard->in(array $where) 353 | 354 | Check if the wildcard exists in a given array. This uses the PHP in_array method. 355 | 356 | 357 | ``` 358 | ':any' => function($router, $wildcard) { 359 | $sections = array( 360 | 'member', 361 | 'profile', 362 | ); 363 | 364 | if ($wildcard->in($sections)) { 365 | $router->setTemplate('customer/index'); 366 | } 367 | } 368 | ``` 369 | 370 | ##### $wildcard->isNot($value) 371 | 372 | Check if the wildcard is not equal to the specified value. This uses the PHP `!=` operator. 373 | 374 | ##### $wildcard->isExactly($value) 375 | 376 | Check if the wildcard is exactly equal to the specified value. This uses the PHP `===` operator. 377 | 378 | ##### $wildcard->isNotExactly($value) 379 | 380 | Check if the wildcard is not exactly equal to the specified value. This uses the PHP `!==` operator. 381 | 382 | ##### $wildcard->isGreaterThan($value) 383 | 384 | Check if the wildcard is greater than the specified value. This uses the PHP `>` operator. 385 | 386 | ##### $wildcard->isGreaterThanOrEquals($value) 387 | 388 | Check if the wildcard is greater than or equal to the specified value. This uses the PHP `>=` operator. 389 | 390 | ##### $wildcard->isLessThan($value) 391 | 392 | Check if the wildcard is less than the specified value. This uses the PHP `<` operator. 393 | 394 | ##### $wildcard->isLessThanOrEquals($value) 395 | 396 | Check if the wildcard is less than or equal to the specified value. This uses the PHP `<=` operator. 397 | 398 | ##### $wildcard->isValidEntryId($where = array()) 399 | 400 | Check if the specified entry_id exists. Adds the following variables: `{route_X_entry_id}`, `{route_X_title}`, `{route_X_url_title}`, `{route_X_channel_id}`, where X is the specified URL segment. 401 | 402 | 'blog/:num' => function($router, $wildcard) { 403 | if ($wildcard->isValidEntryId()) { 404 | $router->setTemplate('site/_blog_detail'); 405 | } else { 406 | $router->set404(); 407 | } 408 | } 409 | 410 | In the second parameter, you can specify other columns/values to use in the query WHERE statement. 411 | 412 | 'blog/:num' => function($router, $wildcard) { 413 | $where = array( 414 | 'status' => 'open', 415 | 'channel' => 'blog', 416 | ); 417 | 418 | if ($wildcard->isValidEntryId($where)) { 419 | $router->setTemplate('site/_blog_detail'); 420 | } else { 421 | $router->set404(); 422 | } 423 | } 424 | 425 | ##### $wildcard->isValidUrlTitle($where = array()) 426 | 427 | Check if the specified url_title exists. Adds the following variables: `{route_X_entry_id}`, `{route_X_title}`, `{route_X_url_title}`, `{route_X_channel_id}`, where X is the specified URL segment. 428 | 429 | 'blog/:any' => function($router, $wildcard) { 430 | if ($wildcard->isValidUrlTitle()) { 431 | $router->setTemplate('site/_blog_detail'); 432 | } else { 433 | $router->set404(); 434 | } 435 | } 436 | 437 | ##### $wildcard->isValidEntry($where = array()) 438 | 439 | Check if the specified entry exists. Adds the following variables: `{route_X_entry_id}`, `{route_X_title}`, `{route_X_url_title}`, `{route_X_channel_id}`, where X is the specified URL segment. 440 | 441 | 'blog/:any' => function($router, $wildcard) { 442 | if ($wildcard->isValidEntry(array('url_title' => $wildcard, 'status' => 'open'))) { 443 | $router->setTemplate('site/_blog_detail'); 444 | } else { 445 | $router->set404(); 446 | } 447 | } 448 | 449 | ##### $wildcard->isValidCategoryId($where = array()) 450 | 451 | Check if the specified cat_id exists. Adds the following variables: `{route_X_cat_id}`, `{route_X_site_id}`, `{route_X_group_id}`, `{route_X_parent_id}`, `{route_X_cat_name}`, `{route_X_cat_url_title}`, `{route_X_cat_description}`, `{route_X_cat_image}`, `{route_X_cat_order'}`, where X is the specified URL segment. 452 | 453 | 'blog/:any' => function($router, $wildcard) { 454 | if ($wildcard->isValidCategoryId()) { 455 | $router->setTemplate('site/_blog_category'); 456 | } else { 457 | $router->set404(); 458 | } 459 | } 460 | 461 | ##### $wildcard->isValidCategoryUrlTitle($where = array()) 462 | 463 | Check if the specified category url_title exists. Adds the following variables: `{route_X_cat_id}`, `{route_X_site_id}`, `{route_X_group_id}`, `{route_X_parent_id}`, `{route_X_cat_name}`, `{route_X_cat_url_title}`, `{route_X_cat_description}`, `{route_X_cat_image}`, `{route_X_cat_order'}`, where X is the specified URL segment. 464 | 465 | 'blog/:any' => function($router, $wildcard) { 466 | if ($wildcard->isValidCategoryUrlTitle()) { 467 | $router->setTemplate('site/_blog_category'); 468 | } else { 469 | $router->set404(); 470 | } 471 | } 472 | 473 | ##### $wildcard->isValidCategory(array $where) 474 | 475 | Check if the specified category exists. Adds the following variables: `{route_X_cat_id}`, `{route_X_site_id}`, `{route_X_group_id}`, `{route_X_parent_id}`, `{route_X_cat_name}`, `{route_X_cat_url_title}`, `{route_X_cat_description}`, `{route_X_cat_image}`, `{route_X_cat_order'}`, where X is the specified URL segment. 476 | 477 | 'blog/:any' => function($router, $wildcard) { 478 | // use the second parameter to specify a column to retrieve data from 479 | $valid = $wildcard->isValidCategory(array( 480 | 'cat_url_title' => $wildcard, 481 | 'channel' => 'blog', 482 | )); 483 | 484 | if ($valid) { 485 | $router->setTemplate('site/_blog_category'); 486 | } else { 487 | $router->set404(); 488 | } 489 | } 490 | 491 | ##### $wildcard->isValidMemberId($where = array()) 492 | 493 | Check if the specified member_id exists. Adds the following variables: `{route_X_member_id}`, `{route_X_group_id}`, `{route_X_email}`, `{route_X_username}`, `{route_X_screen_name}`, where X is the specified URL segment. 494 | 495 | 'users/:num' => function($router, $wildcard) { 496 | if ($wildcard->isValidMemberId()) { 497 | $router->setTemplate('site/_user_detail'); 498 | } else { 499 | $router->set404(); 500 | } 501 | } 502 | 503 | ##### $wildcard->isValidUsername($where = array()) 504 | 505 | Check if the specified username exists. Adds the following variables: `{route_X_member_id}`, `{route_X_group_id}`, `{route_X_email}`, `{route_X_username}`, `{route_X_screen_name}`, where X is the specified URL segment. 506 | 507 | 'users/:any' => function($router, $wildcard) { 508 | if ($wildcard->isValidUsername()) { 509 | $router->setTemplate('site/_user_detail'); 510 | } else { 511 | $router->set404(); 512 | } 513 | } 514 | 515 | ##### $wildcard->isValidMember(array $where) 516 | 517 | Check if the specified member exists. Adds the following variables: `{route_X_member_id}`, `{route_X_group_id}`, `{route_X_email}`, `{route_X_username}`, `{route_X_screen_name}`, where X is the specified URL segment. 518 | 519 | 'users/:any' => function($router, $wildcard) { 520 | // use the second parameter to specify a column to retrieve data from 521 | $where = array( 522 | 'username' => $wildcard, 523 | 'group_id' => 5, 524 | ); 525 | 526 | if ($wildcard->isValidMember($where)) { 527 | $router->setTemplate('site/_user_detail'); 528 | } else { 529 | $router->set404(); 530 | } 531 | } 532 | 533 | #### $wildcard->getMeta(string $column) 534 | 535 | Get the specified meta data from the wildcard. This is only applicable to validating wildcards. 536 | 537 | 'posts/:url_title' => function($router, $wildcard) { 538 | if ($wildcard->isValidUrlTitle()) { 539 | // do something custom with this entry id 540 | $entry_id = $wildcard->getMeta('entry_id'); 541 | 542 | $router->setTemplate('posts/_detail'); 543 | } else { 544 | $router->set404(); 545 | } 546 | } 547 | 548 | ### Examples 549 | 550 | Add pagination, category, and yearly/monthly/daily archives to a Pages/Structure page: 551 | 552 | $config['resource_router'] = array( 553 | ':page:123/:pagination' => 'site/_blog-index', 554 | ':page:123/:category' => 'site/_blog-category', 555 | ':page:123/:year' => 'site/_blog-yearly', 556 | ':page:123/:year/:month' => 'site/_blog-monthly', 557 | ':page:123/:year/:month/:day' => 'site/_blog-daily', 558 | ); 559 | 560 | Suppose you wanted this scheme for blog urls: 561 | 562 | * `/blog` 563 | * `/blog/` 564 | * `/blog/` 565 | * `/blog/` 566 | 567 | ``` 568 | $config['resource_router'] = array( 569 | 'blog/:any' => function($router, $wildcard) { 570 | // is it a category url title? 571 | if ($wildcard->isValidCategoryUrlTitle()) { 572 | $router->setTemplate('site/_blog_category'); 573 | // is it a username? 574 | } elseif ($wildcard->isValidUsername()) { 575 | $router->setTemplate('site/_blog_author'); 576 | // is it a url title? 577 | } elseif ($wildcard->isValidUrlTitle()) { 578 | $router->setTemplate('site/_blog_detail'); 579 | } else { 580 | $router->set404(); 581 | } 582 | } 583 | ); 584 | ```` 585 | 586 | A member group dependent endpoint: 587 | 588 | $config['resource_router'] = array( 589 | 'restricted-area' => function($router) { 590 | if (ee()->session->userdata('group_id') != 1) { 591 | $router->set404(); 592 | } 593 | $router->setTemplate('site/.restricted-area'); 594 | } 595 | ); 596 | 597 | 598 | A custom JSON endpoint: 599 | 600 | $config['resource_router'] = array( 601 | 'api/latest-news' => function($router) { 602 | $query = ee()->db->select('title, url_title') 603 | ->where('status', 'open') 604 | ->where('channel_id', 1) 605 | ->order_by('entry_date', 'ASC') 606 | ->limit(10) 607 | ->get('channel_titles'); 608 | 609 | $result = $query->result(); 610 | 611 | $router->json($result); 612 | } 613 | ); 614 | 615 | 616 | A callback used before all routes: 617 | ``` 618 | $config['resource_router'] = array( 619 | ':before' => function($router) { 620 | $admin_groups = array(1, 6, 7); 621 | $group_id = ee()->session->userdata('group_id'); 622 | $is_admin = in_array($group_id, $admin_groups); 623 | $router->setGlobal('is_admin', $is_admin); 624 | }, 625 | ); 626 | ``` -------------------------------------------------------------------------------- /system/expressionengine/third_party/resource_router/addon.setup.php: -------------------------------------------------------------------------------- 1 | 'Rob Sanchez', 5 | 'author_url' => 'https://github.com/rsanchez/resource_router', 6 | 'name' => 'Resource Router', 7 | 'description' => 'Control your URLs by remapping URI routes to a specific HTTP response, using CodeIgniter-style routing rules.', 8 | 'version' => '1.2.0', 9 | 'namespace' => '\\', 10 | ); 11 | -------------------------------------------------------------------------------- /system/expressionengine/third_party/resource_router/ext.resource_router.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 30 | } 31 | 32 | // ---------------------------------------------------------------------- 33 | 34 | /** 35 | * Activate Extension 36 | * 37 | * This function enters the extension into the exp_extensions table 38 | * 39 | * @see http://codeigniter.com/user_guide/database/index.html for 40 | * more information on the db class. 41 | * 42 | * @return void 43 | */ 44 | public function activate_extension() 45 | { 46 | // Setup custom settings in this array. 47 | $this->settings = array(); 48 | 49 | $data = array( 50 | 'class' => __CLASS__, 51 | 'method' => 'core_template_route', 52 | 'hook' => 'core_template_route', 53 | 'settings' => serialize($this->settings), 54 | 'version' => $this->version, 55 | 'enabled' => 'y', 56 | 'priority' => 1, 57 | ); 58 | 59 | ee()->db->insert('extensions', $data); 60 | 61 | $data = array( 62 | 'class' => __CLASS__, 63 | 'method' => 'publish_live_preview_route', 64 | 'hook' => 'publish_live_preview_route', 65 | 'settings' => serialize($this->settings), 66 | 'version' => $this->version, 67 | 'enabled' => 'y', 68 | 'priority' => 1, 69 | ); 70 | 71 | ee()->db->insert('extensions', $data); 72 | 73 | } 74 | 75 | // ---------------------------------------------------------------------- 76 | 77 | /** 78 | * core_template_route 79 | * 80 | * @param string $uri_string 81 | * @return array 82 | */ 83 | public function core_template_route($uri_string) 84 | { 85 | //since EE2 doesn't have an autoloader 86 | require_once PATH_THIRD.'resource_router/libraries/ResourceRouter/Router.php'; 87 | require_once PATH_THIRD.'resource_router/libraries/ResourceRouter/Wildcard.php'; 88 | 89 | $router = new \rsanchez\ResourceRouter\Router($uri_string); 90 | 91 | if ($router->isRoutable()) 92 | { 93 | // set the route as array from the template string 94 | return $router->template(); 95 | } 96 | 97 | // set the default route to any other extension calling this hook 98 | return ee()->extensions->last_call; 99 | } 100 | 101 | // ---------------------------------------------------------------------- 102 | 103 | /** 104 | * publish_live_preview_route 105 | * 106 | * @param array $post_data 107 | * @param string $uri 108 | * @param int $template_id 109 | * @return array 110 | */ 111 | public function publish_live_preview_route(array $post_data, $uri, $template_id) 112 | { 113 | //since EE2 doesn't have an autoloader 114 | require_once PATH_THIRD.'resource_router/libraries/ResourceRouter/Router.php'; 115 | require_once PATH_THIRD.'resource_router/libraries/ResourceRouter/Wildcard.php'; 116 | 117 | $router = new \rsanchez\ResourceRouter\Router($uri); 118 | 119 | if ($router->isRoutable()) 120 | { 121 | // get the route as array from the template string 122 | $route = $router->template(); 123 | 124 | $template = ee('Model')->get('Template') 125 | ->with('TemplateGroup') 126 | ->filter('TemplateGroup.group_name', $route[0]) 127 | ->filter('template_name', $route[1]) 128 | ->first(); 129 | 130 | if ($template) { 131 | return array( 132 | 'uri' => $uri, 133 | 'template_id' => $template->template_id, 134 | ); 135 | } 136 | } 137 | 138 | // set the default route to any other extension calling this hook 139 | return ee()->extensions->last_call; 140 | } 141 | 142 | // ---------------------------------------------------------------------- 143 | 144 | /** 145 | * Disable Extension 146 | * 147 | * This method removes information from the exp_extensions table 148 | * 149 | * @return void 150 | */ 151 | function disable_extension() 152 | { 153 | ee()->db->where('class', __CLASS__); 154 | ee()->db->delete('extensions'); 155 | } 156 | 157 | // ---------------------------------------------------------------------- 158 | 159 | /** 160 | * Update Extension 161 | * 162 | * This function performs any necessary db updates when the extension 163 | * page is visited 164 | * 165 | * @return mixed void on update / false if none 166 | */ 167 | function update_extension($current = '') 168 | { 169 | if ($current == '' OR $current == $this->version) 170 | { 171 | return FALSE; 172 | } 173 | 174 | if (version_compare($current, '1.2.0', '<')) { 175 | $data = array( 176 | 'class' => __CLASS__, 177 | 'method' => 'publish_live_preview_route', 178 | 'hook' => 'publish_live_preview_route', 179 | 'settings' => serialize($this->settings), 180 | 'version' => $this->version, 181 | 'enabled' => 'y', 182 | 'priority' => 1, 183 | ); 184 | 185 | ee()->db->insert('extensions', $data); 186 | } 187 | } 188 | 189 | // ---------------------------------------------------------------------- 190 | } 191 | 192 | /* End of file ext.resource_router.php */ 193 | /* Location: /system/expressionengine/third_party/resource_router/ext.resource_router.php */ 194 | -------------------------------------------------------------------------------- /system/expressionengine/third_party/resource_router/libraries/ResourceRouter/Router.php: -------------------------------------------------------------------------------- 1 | 'template_group/template_name', 11 | * 12 | * @var array 13 | */ 14 | protected $routes = array(); 15 | 16 | /** 17 | * List of site Page URIs 18 | * @var array 19 | */ 20 | protected $pageUris = array(); 21 | 22 | /** 23 | * The request URI 24 | * @var string 25 | */ 26 | protected $uriString; 27 | 28 | /** 29 | * Is the matched URI a page URI? 30 | * @var boolean 31 | */ 32 | protected $isPage = FALSE; 33 | 34 | /** 35 | * Should we continue to process routes? 36 | * @var boolean 37 | */ 38 | protected $processRoutes = TRUE; 39 | 40 | /** 41 | * The template group 42 | * @var null|string 43 | */ 44 | protected $templateGroup; 45 | 46 | /** 47 | * The template name 48 | * @var null|string 49 | */ 50 | protected $templateName; 51 | 52 | /** 53 | * List of matched wildcards 54 | * @var array 55 | */ 56 | protected $wildcards = array(); 57 | 58 | /** 59 | * Content-Type header 60 | * @var string 61 | */ 62 | protected $contentType; 63 | 64 | /** 65 | * Constructor 66 | * 67 | * @param string $uri_string 68 | * @return void 69 | */ 70 | public function __construct($uri_string) 71 | { 72 | if ($package_path = ee()->config->item('resource_router:package_path')) 73 | { 74 | ee()->load->add_package_path($package_path); 75 | } 76 | 77 | // get the routes array from the config file 78 | $routes = ee()->config->item('resource_router'); 79 | 80 | // in case anyone tries to serialize ee()->config 81 | unset(ee()->config->config['resource_router'], ee()->config->default_ini['resource_router']); 82 | 83 | if (is_array($routes)) 84 | { 85 | $this->routes = $routes; 86 | } 87 | 88 | // get all the Pages/Structure URIs 89 | $site_pages = ee()->config->item('site_pages'); 90 | 91 | $site_id = ee()->config->item('site_id'); 92 | 93 | if (isset($site_pages[$site_id]['uris'])) 94 | { 95 | $this->pageUris = $site_pages[$site_id]['uris']; 96 | } 97 | 98 | // set all the {route_X} variables to blank by default 99 | for ($i = 0; $i <= 10; $i++) 100 | { 101 | $this->setGlobal('route_'.$i); 102 | } 103 | 104 | // normalize the uri_string 105 | $this->uriString = rtrim($uri_string, '/'); 106 | 107 | // start with an empty query_string 108 | $query_string = ''; 109 | 110 | // check if this URI is a Pages URI 111 | $this->isPage = in_array('/'.$this->uriString, $this->pageUris); 112 | 113 | $found_match = FALSE; 114 | 115 | if (isset($this->routes[':before'])) 116 | { 117 | if (is_callable($this->routes[':before'])) 118 | { 119 | call_user_func($this->routes[':before'], $this); 120 | } 121 | 122 | unset($this->routes[':before']); 123 | } 124 | 125 | // loop through all the defined routes and check if the uri_string is a match 126 | foreach($this->routes as $rule => $template) 127 | { 128 | // should we continue to process routes? 129 | if ($this->processRoutes === FALSE) 130 | { 131 | break; 132 | } 133 | 134 | // normalize the rule 135 | $rule = rtrim($rule, '/'); 136 | 137 | // does the rule have any wildcards? 138 | $wildcard = strpos($rule, ':'); 139 | 140 | // build the regex from the rule wildcard(s) 141 | if ($wildcard !== FALSE) 142 | { 143 | // check for a :page:XX wildcard 144 | if (preg_match('/\(?:page:(\d+)\)?/', $rule, $match) && isset($this->pageUris[$match[1]])) 145 | { 146 | $rule = str_replace($match[0], '('.trim($this->pageUris[$match[1]], '/').')', $rule); 147 | 148 | // don't count a page uri as wildcard 149 | $wildcard = strpos($rule, ':'); 150 | } 151 | 152 | // find all the wildcards and parenthesized regexes 153 | $wildcardsByType = array(); 154 | 155 | if (preg_match_all('/(:(any|num|year|month|day|category_url_title|category_id|category|pagination|all|url_title|entry_id|member_id|username)|\(.*?\))/', $rule, $matches)) 156 | { 157 | // store the type of each wildcard so we can specify it later 158 | foreach ($matches[0] as $i => $match) 159 | { 160 | $wildcardsByType[$i + 1] = $match[0] === ':' ? substr($match, 1) : NULL; 161 | } 162 | } 163 | 164 | $regex = str_replace( 165 | array( 166 | ':any', 167 | ':num', 168 | ':year', 169 | ':month', 170 | ':day', 171 | '/:pagination', 172 | ':pagination', 173 | '/:all', 174 | ':entry_id', 175 | ':url_title', 176 | ':category_id', 177 | ':category_url_title', 178 | ':member_id', 179 | ':username', 180 | ':category', 181 | ), 182 | array( 183 | '([^/]+)', 184 | '(\d+)', 185 | '(\d{4})', 186 | '(\d{2})', 187 | '(\d{2})', 188 | '((?:/P\d+)?)', 189 | '((?:/P\d+)?)', 190 | '((?:/.*)?)', 191 | '(\d+)', 192 | '([^/]+)', 193 | '(\d+)', 194 | '([^/]+)', 195 | '(\d+)', 196 | '([^/]+)', 197 | preg_quote(ee()->config->item('reserved_category_word')).'/'.(ee()->config->item('use_category_name') === 'y' ? '([^/]+)' : '(\d+)'), 198 | ), 199 | $rule 200 | ); 201 | } 202 | else 203 | { 204 | $regex = $rule; 205 | } 206 | 207 | $regex = '#^'.trim($regex, '/').'$#'; 208 | 209 | // check if the uri_string matches this route 210 | if (preg_match($regex, $this->uriString, $match)) 211 | { 212 | array_shift($match); 213 | 214 | foreach ($match as $i => $segment) 215 | { 216 | $index = $i + 1; 217 | 218 | $type = isset($wildcardsByType[$index]) ? $wildcardsByType[$index] : NULL; 219 | 220 | $segment = trim($segment, '/'); 221 | 222 | if ($segment === '') 223 | { 224 | $segment = NULL; 225 | } 226 | 227 | if ($type === 'all') 228 | { 229 | $segs = explode('/', rtrim($segment, '/')); 230 | 231 | $index--; 232 | 233 | foreach ($segs as $j => $seg) 234 | { 235 | $index++; 236 | 237 | $this->wildcards[$index] = new Wildcard($this, $index, $seg, 'any'); 238 | } 239 | } 240 | else 241 | { 242 | $this->wildcards[$index] = new Wildcard($this, $index, $segment, $type); 243 | } 244 | 245 | //if it wasn't a callback (where it's assumed you ran your own validation), 246 | //validate all the wildcards and bail if it fails 247 | if ( ! is_callable($template) && ! $this->wildcards[$index]->isValid()) 248 | { 249 | $template = null; 250 | 251 | continue; 252 | } 253 | } 254 | 255 | if (is_string($template)) 256 | { 257 | $this->setTemplate($template); 258 | } 259 | 260 | if (is_callable($template)) 261 | { 262 | $args = $this->wildcards; 263 | 264 | array_unshift($args, $this); 265 | 266 | $output = call_user_func_array($template, $args); 267 | 268 | if ($output && is_string($output)) 269 | { 270 | return $this->output($output); 271 | } 272 | } 273 | 274 | if ($this->hasTemplate()) 275 | { 276 | // check if it has wildcards 277 | if ($wildcard !== FALSE) 278 | { 279 | // the channel module uses this query_string property to do its dynamic stuff 280 | // normally gets set in Template::parse_template_uri(), but we are overriding that function here 281 | // let's grab the bits of the uri that are dynamic and set that as the query_string 282 | // e.g. blog/nested/here/:any => _blog/_view will yield a query_string of that final segment 283 | $query_string = preg_replace('#^'.preg_quote(str_replace(array('(', ')'), '', substr($rule, 0, $wildcard))).'#', '', $this->uriString); 284 | } 285 | 286 | break; 287 | } 288 | } 289 | } 290 | 291 | if ($this->hasTemplate()) 292 | { 293 | if ($query_string) 294 | { 295 | ee()->uri->query_string = $query_string; 296 | } 297 | 298 | // I want Structure's global variables set on urls that start with a pages URI 299 | // so we tell structure that the uri_string is the first match in the regex 300 | if ( ! $this->isPage && isset($this->wildcards[1]) && isset(ee()->extensions->OBJ['Structure_ext']) && in_array('/'.$this->wildcards[1], $this->pageUris)) 301 | { 302 | $temp_uri_string = ee()->uri->uri_string; 303 | 304 | ee()->uri->uri_string = $this->wildcards[1]; 305 | 306 | ee()->extensions->OBJ['Structure_ext']->sessions_start(ee()->session); 307 | 308 | ee()->uri->uri_string = $temp_uri_string; 309 | } 310 | 311 | // loop through the matched sub-strings 312 | foreach ($this->wildcards as $i => $wildcard) 313 | { 314 | // set each sub-string as a global template variable 315 | $this->setGlobal('route_'.$i, (string) $wildcard); 316 | 317 | // replace any sub-string matches in the template definition 318 | $this->templateName = str_replace('$'.$i, $wildcard, $this->templateName); 319 | $this->templateGroup = str_replace('$'.$i, $wildcard, $this->templateGroup); 320 | } 321 | } 322 | } 323 | 324 | /** 325 | * Set uriString property 326 | * 327 | * @param string $uriString the new URI 328 | * @return void 329 | */ 330 | public function setUri($uriString) 331 | { 332 | $this->uriString = rtrim($uriString, '/'); 333 | } 334 | 335 | /** 336 | * Get uriString property 337 | * 338 | * @return string 339 | */ 340 | public function getUri() 341 | { 342 | return $this->uriString; 343 | } 344 | 345 | /** 346 | * @return array 347 | */ 348 | public function getSegments() 349 | { 350 | return explode('/', trim($this->uriString, '/')); 351 | } 352 | 353 | /** 354 | * @param int $num 355 | * 356 | * @return string 357 | */ 358 | public function getSegment($num = 1) 359 | { 360 | $segments = $this->getSegments(); 361 | 362 | // The array is 0 indexed, so decrement 363 | $num--; 364 | 365 | if (isset($segments[$num]) ? $segments[$num] : '') { 366 | return $segments[$num]; 367 | } 368 | 369 | return ''; 370 | } 371 | 372 | /** 373 | * Does this route match a page URI? 374 | * 375 | * @return boolean 376 | */ 377 | public function isPage() 378 | { 379 | return $this->isPage; 380 | } 381 | 382 | /** 383 | * Redirect to the specified url or path 384 | * @param string $url 385 | * @param int $statusCode 386 | * @return void 387 | */ 388 | public function redirect($url, $statusCode = 301) 389 | { 390 | // if it doesn't start with: 391 | // a) a slash 392 | // b) a dot 393 | // c) a valid protocol (eg. http://) 394 | // 395 | // assume it's a EE template url 396 | if ( ! preg_match('#^(/|\.|[a-z]+://)#', $url)) 397 | { 398 | $url = ee()->functions->create_url($url); 399 | } 400 | 401 | ee()->functions->redirect($url, false, $statusCode); 402 | } 403 | 404 | /** 405 | * Load a CI view 406 | * @param string $view name of the view file (sans file extension) 407 | * @param array $variables 408 | * @return string 409 | */ 410 | public function view($view, $variables = array()) 411 | { 412 | return ee()->load->view($view, $variables, TRUE); 413 | } 414 | 415 | /** 416 | * Send this data as a JSON response 417 | * 418 | * return $router->json(array('foo' => 'bar')); 419 | * 420 | * @param mixed $data 421 | * @return void 422 | */ 423 | public function json($data, $options = 0) 424 | { 425 | if (is_object($data) && ! $data instanceof \JsonSerializable) 426 | { 427 | if (method_exists($data, 'toJson')) 428 | { 429 | $output = $data->toJson($options); 430 | } 431 | elseif (method_exists($data, 'toArray')) 432 | { 433 | $output = json_encode($data->toArray(), $options); 434 | } 435 | else 436 | { 437 | $output = json_encode($data, $options); 438 | } 439 | } 440 | else 441 | { 442 | $output = json_encode($data, $options); 443 | } 444 | 445 | return $this->quit(200, $output, array( 446 | 'Content-Type' => 'application/json', 447 | )); 448 | } 449 | 450 | /** 451 | * Get the matched wildcards 452 | * 453 | * @return array 454 | */ 455 | public function wildcards() 456 | { 457 | return $this->wildcards; 458 | } 459 | 460 | /** 461 | * Get the specified matched wildcard 462 | * 463 | * @param int $which the wildcard index 464 | * @return mixed|null null if doesn't exist 465 | */ 466 | public function wildcard($which) 467 | { 468 | return array_key_exists($which, $this->wildcards) ? $this->wildcards[$which] : NULL; 469 | } 470 | 471 | /** 472 | * Output a string that will be output as the body content for this router endpoint 473 | * 474 | * $router->output('Hello world!'); 475 | * 476 | * @param string $output 477 | */ 478 | public function output($output = '') 479 | { 480 | ee()->output->final_output = $output; 481 | 482 | $output_type = $this->contentType ? 'custom' : ee()->output->out_type; 483 | 484 | $override_types = array('webpage', 'css', 'js', 'xml', 'json', 'custom'); 485 | 486 | // dont send those weird pragma no-cache headers 487 | if (in_array($output_type, $override_types)) 488 | { 489 | switch ($output_type) 490 | { 491 | case 'json': 492 | $this->setContentType('application/json'); 493 | ee()->output->enable_profiler = FALSE; 494 | break; 495 | case 'webpage': 496 | $this->setContentType('text/html; charset='.ee()->config->item('charset')); 497 | break; 498 | case 'css': 499 | $this->setContentType('text/css'); 500 | break; 501 | case 'js': 502 | $this->setContentType('text/javascript'); 503 | ee()->output->enable_profiler = FALSE; 504 | break; 505 | case 'xml': 506 | $this->setContentType('text/xml'); 507 | ee()->output->final_output = trim(ee()->output->final_output); 508 | break; 509 | } 510 | 511 | ee()->output->out_type = 'cp_asset'; 512 | } 513 | 514 | if (version_compare(APP_VER, '3', '<')) 515 | { 516 | return $this->legacyDisplay(); 517 | } 518 | 519 | ee()->output->_display(); 520 | 521 | exit; 522 | } 523 | 524 | /** 525 | * Stop processing routes 526 | * @return void 527 | */ 528 | public function stopRouting() 529 | { 530 | $this->processRoutes = FALSE; 531 | } 532 | 533 | /** 534 | * Display output in EE2 535 | * @return void 536 | */ 537 | protected function legacyDisplay() 538 | { 539 | // Start from CodeIgniter.php 540 | ee()->benchmark->mark('controller_execution_time_( EE / index )_end'); 541 | 542 | ee()->hooks->_call_hook('post_controller'); 543 | 544 | if (ee()->hooks->_call_hook('display_override') === FALSE) 545 | { 546 | ee()->output->_display(); 547 | } 548 | 549 | ee()->hooks->_call_hook('post_system'); 550 | 551 | if (class_exists('CI_DB') AND isset(ee()->db)) 552 | { 553 | ee()->db->close(); 554 | } 555 | // End from CodeIgniter.php 556 | 557 | exit; 558 | } 559 | 560 | /** 561 | * Trigger a 404 using the built-in EE 404 template 562 | * 563 | * @return $this 564 | */ 565 | public function set404() 566 | { 567 | if (version_compare(APP_VER, '3', '<')) 568 | { 569 | return $this->legacy404(); 570 | } 571 | 572 | ee()->load->library('template', NULL, 'TMPL'); 573 | 574 | ee()->TMPL->show_404(); 575 | 576 | return $this; 577 | } 578 | 579 | /** 580 | * Trigger a 404 in EE2 581 | * @return $this 582 | */ 583 | public function legacy404() 584 | { 585 | //all the conditions to trigger a 404 in the TMPL class 586 | $hidden_template_indicator = ee()->config->item('hidden_template_indicator') ?: '.'; 587 | 588 | ee()->uri->page_query_string = ''; 589 | 590 | ee()->config->set_item('hidden_template_404', 'y'); 591 | 592 | $this->templateGroup = $hidden_template_indicator; 593 | $this->templateName = $hidden_template_indicator; 594 | 595 | return $this; 596 | } 597 | 598 | /** 599 | * Set the HTTP Content-Type header 600 | * 601 | * $router->setContentType('application/json'); 602 | * 603 | * @param string $content_type 604 | * @return $this 605 | */ 606 | public function setContentType($content_type) 607 | { 608 | $this->setHeader('Content-Type', $content_type); 609 | 610 | return $this; 611 | } 612 | 613 | /** 614 | * Set a global template variable 615 | * 616 | * $router->setGlobal 617 | * 618 | * @param string $key the variable name 619 | * @param string|bool|int $value 620 | * @return $this 621 | */ 622 | public function setGlobal($key, $value = '') 623 | { 624 | if ($value instanceof Wildcard) { 625 | $value = (string) $value; 626 | } 627 | 628 | ee()->config->_global_vars[$key] = $value; 629 | 630 | return $this; 631 | } 632 | 633 | /** 634 | * Set an HTTP header 635 | * 636 | * $router->setHeader('Content-Type: application/json'); 637 | * $router->setHeader('Content-Type', 'application/json'); 638 | * 639 | * @param string $header the full header string if using one parameter, the header name if using two parameters 640 | * @param string $content [optional] the header content if using two parameters 641 | * @return $this 642 | */ 643 | public function setHeader($header, $content = NULL) 644 | { 645 | if (func_num_args() === 1) 646 | { 647 | ee()->output->set_header($header); 648 | 649 | if (strncmp($header, 'Content-Type: ', 14) === 0) 650 | { 651 | $this->contentType = substr($header, 14); 652 | } 653 | } 654 | else 655 | { 656 | ee()->output->set_header(sprintf('%s: %s', $header, $content)); 657 | 658 | if ($header === 'Content-Type') 659 | { 660 | $this->contentType = $content; 661 | } 662 | } 663 | 664 | return $this; 665 | } 666 | 667 | /** 668 | * Set an HTTP status code 669 | * 670 | * $router->setHttpStatus(401); 671 | * 672 | * @param int $code a valid HTTP status code 673 | * @return $this 674 | */ 675 | public function setHttpStatus($code) 676 | { 677 | // if you don't do this, EE_Output will override with a 200 status 678 | ee()->config->set_item('send_headers', FALSE); 679 | 680 | ee()->output->set_status_header($code); 681 | 682 | return $this; 683 | } 684 | 685 | /** 686 | * Set the EE output class type 687 | * 688 | * $router->setOutputType('css'); 689 | * 690 | * @param string $type one of the following: webpage, css, js, json, xml, feed, 404 691 | * @return $this 692 | */ 693 | public function setOutputType($type) 694 | { 695 | ee()->output->out_type = $type; 696 | 697 | return $this; 698 | } 699 | 700 | /** 701 | * Set the template to use for this router endpoint 702 | * 703 | * $router->setTemplate('foo/bar'); 704 | * 705 | * @param string $template a template_group/template_name pair 706 | * @return $this 707 | */ 708 | public function setTemplate($template) 709 | { 710 | if ( ! is_array($template)) 711 | { 712 | $template = explode('/', trim($template, '/')); 713 | } 714 | 715 | $this->templateGroup = $template[0]; 716 | $this->templateName = isset($template[1]) ? $template[1] : 'index'; 717 | 718 | $default_hidden_template_indicator = version_compare(APP_VER, '2.9.0', '<') ? '.' : '_'; 719 | 720 | $hidden_template_indicator = ee()->config->item('hidden_template_indicator') ?: $default_hidden_template_indicator; 721 | 722 | //allow you to set hidden templates 723 | if (isset($this->templateName[0]) && $this->templateName[0] === $hidden_template_indicator) 724 | { 725 | ee()->config->set_item('hidden_template_indicator', ''); 726 | } 727 | 728 | return $this; 729 | } 730 | 731 | /** 732 | * Set a template single or pair variable for use with the {exp:resource_router} plugin 733 | * 734 | * $router->setVariable('foo', 'bar'); 735 | * 736 | * {exp:resource_router:foo} -> bar 737 | * 738 | * $router->setVariable('foo', array('bar' => 1, 'baz' => 2)); 739 | * 740 | * {exp:resource_router:foo}{bar}-{baz}{/exp:resource_router:foo} -> 1-2 741 | * 742 | * $router->setVariable('foo', array(array('bar' => 1, 'baz' => 2), array('bar' => 3, 'baz' => 4))); 743 | * 744 | * {exp:resource_router:foo}{bar}-{baz}|{/exp:resource_router:foo} -> 1-2|3-4 745 | * 746 | * @param string $name the key or identifier of the variable 747 | * @param string|array $data an array for a tag pair or a single value 748 | * @return $this 749 | */ 750 | public function setVariable($name, $data) 751 | { 752 | ee()->session->set_cache('resource_router', $name, $data); 753 | 754 | return $this; 755 | } 756 | 757 | /** 758 | * Set a wildcard variable 759 | * 760 | * $router->setWildcard(1, 'foo'); 761 | * 762 | * @param int $which from 1 -> 10 763 | * @param string $value the wildcard variable value 764 | * @return $this 765 | */ 766 | public function setWildcard($which, $value) 767 | { 768 | if (isset($this->wildcards[$which])) 769 | { 770 | $this->wildcards[$which]->value = $value; 771 | } 772 | 773 | return $this; 774 | } 775 | 776 | /** 777 | * Get the set template 778 | * 779 | * @return mixed 780 | */ 781 | public function template() 782 | { 783 | return array($this->templateGroup, $this->templateName); 784 | } 785 | 786 | /** 787 | * Check if a template has been set 788 | * @return bool 789 | */ 790 | public function hasTemplate() 791 | { 792 | return $this->templateName && $this->templateGroup; 793 | } 794 | 795 | /** 796 | * Check if the current uri is routable 797 | * 798 | * @return bool is there a valid template set, and does it not match a page uri exactly 799 | */ 800 | public function isRoutable() 801 | { 802 | return $this->hasTemplate() && ! $this->isPage; 803 | } 804 | 805 | /** 806 | * Get the specified set variable 807 | * 808 | * @param string $which the variable key 809 | * @return mixed|false false if doesn't exist 810 | */ 811 | public function variable($which) 812 | { 813 | return ee()->session->cache('resource_router', $which); 814 | } 815 | 816 | /** 817 | * Exit the application immediately 818 | * @param int $statusCode 819 | * @param string $message 820 | * @param array $headers 821 | * @return void 822 | */ 823 | protected function quit($statusCode, $message = '', $headers = array()) 824 | { 825 | set_status_header($statusCode); 826 | 827 | $hasContentLength = false; 828 | 829 | foreach ($headers as $key => $value) 830 | { 831 | header(sprintf('%s: %s', $key, $value)); 832 | } 833 | 834 | exit($message); 835 | } 836 | } 837 | -------------------------------------------------------------------------------- /system/expressionengine/third_party/resource_router/libraries/ResourceRouter/Wildcard.php: -------------------------------------------------------------------------------- 1 | router = $router; 42 | $this->index = $index; 43 | $this->value = $value; 44 | $this->type = $type; 45 | } 46 | 47 | /** 48 | * Return the XSS-cleaned value if it exists, otherwise create 49 | * and return it. 50 | * @param string $name Property name 51 | * @return string XSS-cleaned property value 52 | */ 53 | public function __get($name) 54 | { 55 | if ($name == 'clean_value') { 56 | if (isset($this->clean_value)) { 57 | return $this->clean_value; 58 | } else { 59 | $this->clean_value = ee()->security->xss_clean($this->value); 60 | return $this->clean_value; 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Validate according to type 67 | * 68 | * @param array $where additional columns to match in the query 69 | * @return bool 70 | */ 71 | public function isValid($where = array()) 72 | { 73 | switch($this->type) 74 | { 75 | case 'entry_id': 76 | $where['entry_id'] = $this->value; 77 | return $this->isValidEntry($where); 78 | case 'url_title': 79 | $where['url_title'] = $this->value; 80 | return $this->isValidEntry($where); 81 | case 'category_id': 82 | $where['cat_id'] = $this->value; 83 | return $this->isValidCategory($where); 84 | case 'category_url_title': 85 | $where['cat_url_title'] = $this->value; 86 | return $this->isValidCategory($where); 87 | case 'member_id': 88 | $where['member_id'] = $this->value; 89 | return $this->isValidMember($where); 90 | case 'username': 91 | $where['username'] = $this->value; 92 | return $this->isValidMember($where); 93 | } 94 | 95 | return true; 96 | } 97 | 98 | /** 99 | * Check if the given category is valid 100 | * 101 | * if ($wildcard->isValidCategory(array( 102 | * 'cat_url_title' => $wildcard, 103 | * 'channel' => 'blog', 104 | * )) 105 | * { 106 | * $router->setTemplate('blog/category'); 107 | * } 108 | * 109 | * @param array $where where / where_in provided to CodeIgniter Active Record class 110 | * @return boolean|mixed is this a valid category|the value of the $return column, if specified 111 | */ 112 | public function isValidCategory(array $where) 113 | { 114 | $joined = FALSE; 115 | 116 | if (isset($where['channel']) || isset($where['channel_id'])) 117 | { 118 | if (isset($where['channel'])) 119 | { 120 | $channel = is_array($where['channel']) ? $where['channel'] : array($where['channel']); 121 | 122 | ee()->db->where_in('channel_name', $channel); 123 | } 124 | 125 | if (isset($where['channel_id'])) 126 | { 127 | $channel_id = is_array($where['channel_id']) ? $where['channel_id'] : array($where['channel_id']); 128 | 129 | ee()->db->where_in('channel_id', $channel_id); 130 | } 131 | 132 | ee()->db->select('cat_group'); 133 | 134 | $query = ee()->db->get('channels'); 135 | 136 | if ($query->num_rows() > 0 && ! isset($where['group_id'])) 137 | { 138 | $where['group_id'] = array(); 139 | } 140 | 141 | foreach ($query->result() as $row) 142 | { 143 | foreach (explode('|', $row->cat_group) as $group_id) 144 | { 145 | $where['group_id'][] = $group_id; 146 | } 147 | } 148 | 149 | unset($where['channel'], $where['channel_id']); 150 | } 151 | 152 | foreach ($where as $key => $value) 153 | { 154 | if ($joined === FALSE && strncmp($key, 'field_id_', 9) === 0) 155 | { 156 | ee()->db->join('category_field_data', 'category_field_data.cat_id = categories.cat_id'); 157 | 158 | $joined = TRUE; 159 | } 160 | 161 | if ($key === 'cat_id') 162 | { 163 | $key = 'categories.cat_id'; 164 | } 165 | 166 | if (is_array($value)) 167 | { 168 | ee()->db->where_in($key, $value); 169 | } 170 | else 171 | { 172 | ee()->db->where($key, (string) $value); 173 | } 174 | } 175 | 176 | ee()->db->where('categories.site_id', ee()->config->item('site_id')); 177 | 178 | $select = array( 179 | 'cat_id', 180 | 'site_id', 181 | 'group_id', 182 | 'parent_id', 183 | 'cat_name', 184 | 'cat_url_title', 185 | 'cat_description', 186 | 'cat_image', 187 | 'cat_order', 188 | ); 189 | 190 | foreach ($select as $column) 191 | { 192 | ee()->db->select('categories.'.$column); 193 | } 194 | 195 | $query = ee()->db->get('categories'); 196 | 197 | $isValid = $query->num_rows() > 0; 198 | 199 | $meta = $isValid ? $query->row_array() : array_fill_keys($select, null); 200 | 201 | $this->setMeta($meta); 202 | 203 | $query->free_result(); 204 | 205 | return $isValid; 206 | } 207 | 208 | /** 209 | * Check if the given entry is valid 210 | * 211 | * if ($wildcard->isValidEntry(array( 212 | * 'url_title' => $wildcard, 213 | * 'channel' => 'blog', 214 | * 'status' => 'open', 215 | * )) 216 | * { 217 | * $router->setTemplate('blog/detail'); 218 | * } 219 | * 220 | * @param array $where where / where_in provided to CodeIgniter Active Record class 221 | * @return boolean is this a valid entry 222 | */ 223 | public function isValidEntry(array $where) 224 | { 225 | $joined_data = FALSE; 226 | $joined_channel = FALSE; 227 | 228 | foreach ($where as $key => $value) 229 | { 230 | if ($joined_data === FALSE && strncmp($key, 'field_id_', 9) === 0) 231 | { 232 | ee()->db->join('channel_data', 'channel_data.entry_id = channel_titles.entry_id'); 233 | 234 | $joined_data = TRUE; 235 | } 236 | 237 | if ($key === 'channel' || $key === 'channel_name') 238 | { 239 | if ($joined_channel === FALSE) 240 | { 241 | ee()->db->join('channels', 'channels.channel_id = channel_titles.channel_id'); 242 | 243 | $joined_channel = TRUE; 244 | } 245 | 246 | $key = 'channels.channel_name'; 247 | } 248 | 249 | if ($key === 'channel_id') 250 | { 251 | $key = 'channel_titles.channel_id'; 252 | } 253 | 254 | if ($key === 'entry_id') 255 | { 256 | $key = 'channel_titles.entry_id'; 257 | } 258 | 259 | if (is_array($value)) 260 | { 261 | ee()->db->where_in($key, $value); 262 | } 263 | else 264 | { 265 | ee()->db->where($key, (string) $value); 266 | } 267 | } 268 | 269 | ee()->db->where('channel_titles.site_id', ee()->config->item('site_id')); 270 | 271 | $select = array( 272 | 'entry_id', 273 | 'title', 274 | 'url_title', 275 | 'channel_id', 276 | ); 277 | 278 | foreach ($select as $column) 279 | { 280 | ee()->db->select('channel_titles.'.$column); 281 | } 282 | 283 | $query = ee()->db->get('channel_titles'); 284 | 285 | $isValid = $query->num_rows() > 0; 286 | 287 | $meta = $isValid ? $query->row_array() : array_fill_keys($select, null); 288 | 289 | $this->setMeta($meta); 290 | 291 | $query->free_result(); 292 | 293 | return $isValid; 294 | } 295 | 296 | /** 297 | * Check if the given member is valid 298 | * 299 | * if ($wo;dcard->isValidMember(array( 300 | * 'username' => $wildcard, 301 | * 'group_id' => 6, 302 | * )) 303 | * { 304 | * $router->setTemplate('users/detail'); 305 | * } 306 | * 307 | * @param array $where where / where_in provided to CodeIgniter Active Record class 308 | * @return boolean is this a valid member 309 | */ 310 | public function isValidMember(array $where) 311 | { 312 | foreach ($where as $key => $value) 313 | { 314 | if (is_array($value)) 315 | { 316 | ee()->db->where_in($key, $value); 317 | } 318 | else 319 | { 320 | ee()->db->where($key, (string) $value); 321 | } 322 | } 323 | 324 | $select = array( 325 | 'member_id', 326 | 'group_id', 327 | 'email', 328 | 'username', 329 | 'screen_name', 330 | ); 331 | 332 | ee()->db->select($select); 333 | 334 | $query = ee()->db->get('members'); 335 | 336 | $isValid = $query->num_rows() > 0; 337 | 338 | $meta = $isValid ? $query->row_array() : array_fill_keys($select, null); 339 | 340 | $this->setMeta($meta); 341 | 342 | $query->free_result(); 343 | 344 | return $isValid; 345 | } 346 | 347 | /** 348 | * Check if the given category ID is valid 349 | * 350 | * if ($wildcard->isValidCategoryId()) 351 | * { 352 | * $router->setTemplate('blog/category'); 353 | * } 354 | * 355 | * @param array $where additional columns to add to the sql query 356 | * @return boolean is this a valid category 357 | */ 358 | public function isValidCategoryId($where = array()) 359 | { 360 | $where['cat_id'] = $this->value; 361 | 362 | return $this->isValidCategory($where); 363 | } 364 | 365 | /** 366 | * Check if the given category url title is valid 367 | * 368 | * if ($wildcard->isValidCategoryUrlTitle()) 369 | * { 370 | * $router->setTemplate('blog/category'); 371 | * } 372 | * 373 | * @param array $where additional columns to add to the sql query 374 | * @return boolean is this a valid category 375 | */ 376 | public function isValidCategoryUrlTitle($where = array()) 377 | { 378 | $where['cat_url_title'] = $this->value; 379 | 380 | return $this->isValidCategory($where); 381 | } 382 | 383 | /** 384 | * Check if the given entry id is valid 385 | * 386 | * if ($wildcard->isValidEntryId()) 387 | * { 388 | * $router->setTemplate('blog/detail'); 389 | * } 390 | * 391 | * @param array $where additional columns to add to the sql query 392 | * @return boolean is this a valid entry 393 | */ 394 | public function isValidEntryId($where = array()) 395 | { 396 | $where['entry_id'] = $this->value; 397 | 398 | return $this->isValidEntry($where); 399 | } 400 | 401 | /** 402 | * Check if the given member_id is valid 403 | * 404 | * if ($wildcard->isValidMemberId()) 405 | * { 406 | * $router->setTemplate('users/detail'); 407 | * } 408 | * 409 | * @param array $where additional columns to add to the sql query 410 | * @return boolean is this a valid member 411 | */ 412 | public function isValidMemberId($where = array()) 413 | { 414 | $where['member_id'] = $this->value; 415 | 416 | return $this->isValidMember($where); 417 | } 418 | 419 | /** 420 | * Check if the given url title is valid 421 | * 422 | * if ($wildcard->isValidUrlTitle()) 423 | * { 424 | * $router->setTemplate('blog/detail'); 425 | * } 426 | * 427 | * @param array $where additional columns to add to the sql query 428 | * @return boolean is this a valid entry 429 | */ 430 | public function isValidUrlTitle($where = array()) 431 | { 432 | $where['url_title'] = $this->value; 433 | 434 | return $this->isValidEntry($where); 435 | } 436 | 437 | /** 438 | * Check if the given username is valid 439 | * 440 | * if ($wildcard->isValidUsername()) 441 | * { 442 | * $router->setTemplate('users/detail'); 443 | * } 444 | * 445 | * @param array $where additional columns to add to the sql query 446 | * @return boolean is this a valid member 447 | */ 448 | public function isValidUsername($where = array()) 449 | { 450 | $where['username'] = $this->value; 451 | 452 | return $this->isValidMember($where); 453 | } 454 | 455 | /** 456 | * Get data from this wildcard's DB table (if applicable) 457 | * @param string $column ex. entry_id 458 | * @return string|null 459 | */ 460 | public function getMeta($column) 461 | { 462 | return isset($this->meta[$column]) ? $this->meta[$column] : null; 463 | } 464 | 465 | /** 466 | * Set the meta for this validating wildcard (if applicable) 467 | * @param array $meta 468 | * @return void 469 | */ 470 | protected function setMeta(array $meta) 471 | { 472 | $this->meta = $meta; 473 | 474 | foreach ($this->meta as $key => $value) 475 | { 476 | $this->router->setGlobal(sprintf('route_%d_%s', $this->index, $key), $value); 477 | } 478 | } 479 | 480 | public function compare($operator, $value) 481 | { 482 | switch ($operator) { 483 | case '==': 484 | return $this->value == $value; 485 | case '===': 486 | return $this->value === $value; 487 | case '!=': 488 | return $this->value != $value; 489 | case '<>': 490 | return $this->value <> $value; 491 | case '!==': 492 | return $this->value !== $value; 493 | case '>': 494 | return $this->value > $value; 495 | case '>=': 496 | return $this->value >= $value; 497 | case '<': 498 | return $this->value < $value; 499 | case '<=': 500 | return $this->value <= $value; 501 | } 502 | 503 | throw new \Exception('Invalid comparison operator.'); 504 | } 505 | 506 | public function is($value) 507 | { 508 | return $this->compare('==', $value); 509 | } 510 | 511 | public function in(array $where) 512 | { 513 | return in_array($this->value, $where); 514 | } 515 | 516 | public function isExactly($value) 517 | { 518 | return $this->compare('===', $value); 519 | } 520 | 521 | public function isNot($value) 522 | { 523 | return $this->compare('!=', $value); 524 | } 525 | 526 | public function isNotExactly($value) 527 | { 528 | return $this->compare('!==', $value); 529 | } 530 | 531 | public function isGreaterThan($value) 532 | { 533 | return $this->compare('>', $value); 534 | } 535 | 536 | public function isGreaterThanOrEqual($value) 537 | { 538 | return $this->compare('>=', $value); 539 | } 540 | 541 | public function isLessThan($value) 542 | { 543 | return $this->compare('<', $value); 544 | } 545 | 546 | public function isLessThanOrEqual($value) 547 | { 548 | return $this->compare('<=', $value); 549 | } 550 | 551 | public function isEmpty() 552 | { 553 | return ! $this->value && $this->value !== '0'; 554 | } 555 | 556 | public function __toString() 557 | { 558 | return (string) $this->value; 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /system/expressionengine/third_party/resource_router/pi.resource_router.php: -------------------------------------------------------------------------------- 1 | 'resource_router', 15 | 'pi_version' => '1.1.3', 16 | 'pi_author' => 'Rob Sanchez', 17 | 'pi_author_url' => 'https://github.com/rsanchez', 18 | 'pi_description'=> 'Tags for Resource Router variables', 19 | 'pi_usage' => '{exp:resource_router:foo}{bar}-{baz}{/exp:resource_router:foo}' 20 | ); 21 | 22 | class Resource_router 23 | { 24 | public function __call($name, $args) 25 | { 26 | $data = ee()->session->cache('resource_router', $name); 27 | 28 | if ( ! $data) 29 | { 30 | return ee()->TMPL->no_results(); 31 | } 32 | 33 | if (is_array($data)) 34 | { 35 | if ( ! $data) 36 | { 37 | return ee()->TMPL->no_results(); 38 | } 39 | 40 | $method = 'parse_variables'; 41 | 42 | //is it associative? 43 | foreach (array_keys($data) as $key => $val) 44 | { 45 | if ($key !== $val) 46 | { 47 | $method = 'parse_variables_row'; 48 | break; 49 | } 50 | } 51 | 52 | return ee()->TMPL->$method(ee()->TMPL->tagdata, $data); 53 | } 54 | 55 | return $data; 56 | } 57 | } 58 | 59 | /* End of file pi.resource_router.php */ 60 | /* Location: /system/expressionengine/third_party/resource_router/pi.resource_router.php */ -------------------------------------------------------------------------------- /system/expressionengine/third_party/resource_router/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 403 Forbidden 4 | 5 | 6 | 7 |

Directory access is forbidden.

8 | 9 | 10 | --------------------------------------------------------------------------------