View source for URI Router

yaur -- yet another uri router
++written and distributed under the ((http://www.opensource.org/licenses/BSD-3-Clause BSD License))++

All the routing is now done via the settings in the **router.conf**, the idea was "to decoupling of [the] serve logic from Apache - to be server-agnostic".

{{toc numerate=1}}


The whole idea is to take URI path, ##_GET/_PUT/_SERVER## data and other meta-data, then process rules in this file sequentially, which results in dispatched handler to process query,  and all arguments extracted from URI for further usage by those handlers (e.g. you can extract parts of URI to ##_GET## vars, etc.).

class
  * ((source:master/src/class/urirouter.php urirouter.php))

config
  *  ((source:master/src/config/router.conf router.conf))

==='Language' guide===
Every line of code consists of regex which matched against URI, and actions, all separated by whitespace.There are no possibility to include whitespace in regex or action. Regex will be matched against URI, on success all actions executed, on failure - we go for next regex. Lines without regex is continuations for same regex, and will be executed sequentially if regex matched.

Every single action can succeed or fail. If any one fails - all variable assignments made by THIS line (even before failed action) is discarded, and we go to next line.

Two control actions exist (all action line must succeed for them to act!)
  ##_next!##		-- jump to next regex (skip next action lines with empty regex)
  ##_ok!##		-- search terminated with success

If no ##_ok!## is executed ever - search fails and ##404## is emitted.

That's all on control flow.

====Main regex====
((http://php.net/manual/en/reference.pcre.pattern.syntax.php PCRE regex syntax)) -- usual php ##preg_*## regex syntax apply, including delimiters and options after trailing delimiter. Convenience macros (defined by '##define##' line, or supplied by WackoWiki) expanded before matching. Used as ##{macro}## - to be referenced as ##$1-vars## then, or ##{var=macro}## - for inline assignment. Macros cannot be used in ##~~-regexes##.

VARIABLES:
  ##$0..$9##	- fields matched by main regex. ##$0## is complete match, ##$1## and later - corresponding (...) parts
  ##$a..$j##	- as ##$0..$9## but set by sub-matched (by ##~~## operator) patterns 
  ##Gname##	- ##_GET[name]##
  ##Pname##	- ##_PUT[name]##
  ##Sname##	- ##_SERVER[name]##
  ##others##	- local variables

Predefined vars:
  ##_tls##		- ##1## or ##0##, tls session
  ##_uri##		- parsed URI (it is matched against main regex, but can be changed by assignment)
  ##_method##		- ##_SERVER[REQUEST_METHOD]##
  ##_rewrite##	- ##1## or ##0##, ##mod_rewrite## active

Usage of undefined variables considered a failure (if not masked by ##@## in VALUE expansion, see later)

ACTIONS:
Similar format used for all actions (not all fields apply for every action, and just ignored):
  ##VARIABLE[:FUNCTION]operatorVALUE##

Value is a string, with expanded variables. expansions:
  ##$0 .. $9 / $a .. $j## -- see above
  ##${name}##
  ##@$...## format can be used to mask undefined variable error
  ##$$## - replaced by ##$##
  ##$@## - replaced by ##@##

Assignments:
  FUNCTIONs can be used: ##tolower | toupper | int##
  ##var=$1##
  ##var:tolower=$1##
  ##var?=$2##				-- assign if not set
  ##var!##				~== var=1
  ##dbg=$1,$2,$3##		-- ##Ut::dbg## printer ;)

Pattern matching:
  ##var~/regex/i##			-- sets ##$a..$j## on success
  ##var!~/regex/i##
  ##var~hashid:[1-9]##		-- hashid expansion, into ##$a...##
  ##var!~hashid:[1-9]##

Comparisons:
  FUNCTIONS can be used: ##int## -- both args converted to int before comparison
  ##var==12##    ##var!=12##    ##var:int<12##    ##var>12##    ##var<=12##    ##var>=12##

Others:
  ##var?##					-- isset
  ##var-##					-- unset


===router.conf===
%%
//define	{method}	name|name|name		// predefined by wackowiki
define		{hashid}	[0-9a-zA-Z]+
define		{i}		[0-9]+
define		{h}		[0-9a-fA-F]+
define		{a}		[0-9a-zA-Z]+
define		{w}		[\w]+
define		{}		[^/]*
define		{*}		.*?
define		{**}		.*%% 

%%
//`^{hashid}$`			$1~hashid:2 Gone=$a Gtwo=$b all=$0+${Gone} _ok! _tls!=0 _method~/g(e)t/i BIN:tolower=$b Pln=${_line}
//				desc=$0 term:tolower=MyMethod term!= Gmethod=show _ok!

//`^{hashid}/{Op=*}/{Mode=**}$`
//				Op!~/^diff$/i &next!
//				$1~hashid:2 Gone=$a Gtwo=$b Gmethod:tolower=${Op} _ok! // test

// ...

//`^{i}rev{i}$`
//				page=$1x$2 method=hashid redirect=301 _ok!

//`^{page=}$`
//				method=show _ok!
%% 
====Example====
config/router.conf
%%
//define	{method}	name|name|name		// predefined by wackowiki
define		{hashid}	[0-9a-zA-Z]+
define		{i}		[0-9]+
define		{h}		[0-9a-fA-F]+
define		{a}		[0-9a-zA-Z]+
define		{w}		[\w]+
define		{}		[^/]*
define		{*}		.*?
define		{**}		.*

`^`
	SPATH_INFO!= _uri=${SPATH_INFO} _next!		// if PATH_INFO available - use it
	_rewrite==0 _uri=@${Gpage} Gpage-		// when rewrite mode is off - replace _uri by page _GET variable

`^/*{_uri=*}/*$`					// trim _uri of beginning & trailing slashes
`^index\.(php|html)$`					_uri=

'^'							route=static age=30 static=${_uri}

`^robots\.txt$`						_ok!
`^sitemap\.xml$`					_ok! age=0

`^(theme/{}/css|theme/_common|admin/style)/{}$`		_ok!		// css
`^image/(wikiedit/)?{}$`				_ok!		// icons
`^theme/{}/icon/{}$`					_ok!		// icons
`^js/(lang/)?{}$`					_ok!		// js
`^file/(global/){}$`					_ok!			// global uploads
`^file/thumb/{}$`					_ok!		// global thumbs
`^setup/(image|css)/{}$`				_ok! unlock=1	// setup inlines
`^xml/{}$`						_ok! age=0	// feeds

'^'							_ok! _install!=0 route=install unlock=1
							session=1 age- static-

`^\.freecap$`						_ok! route=freecap session=2

'^'							engine=1 route=wacko

`^admin\.php$`						_ok! route=admin

`^{}(/.*)?$`
	$1~hashid:2 page=$ax$b method=hashid _ok!

`^(|{page=**}/){method}(/.*?)?$`ii			method:tolower=$3
							method==file session=2
							_ok!

// catch-all
`{page=**}`						_ok! method=show

%% 

_cache/config/router.conf
%%
[2,
	["`^`",[],96],
	["",[["_SERVER","PATH_INFO","","!=",""],
		["vars","_uri","","=","${SPATH_INFO}"],
		["vars","_next","","!",1]],97],
	["",
		[
			["vars","_rewrite","","==","0"],
			["vars","_uri","","=","@${Gpage}"],
			["_GET","page","","-",""]
		],98],
	["`^/*(?P<_uri>.*?)/*$`",[],100],
	["`^index\\.(php|html)$`",
		[["vars","_uri","","=",""]],101],
	["'^'",
		[
			["vars","route","","=","static"],
			["vars","age","","=","30"],
			["vars","static","","=","${_uri}"]],103],
	["`^robots\\.txt$`",
		[["vars","_ok","","!",1]],105],
	["`^sitemap\\.xml$`",
		[
			["vars","_ok","","!",1],
			["vars","age","","=","0"]
		],106],
	["`^(theme/([^/]*)/css|theme/_common|admin/style)/([^/]*)$`",
		[["vars","_ok","","!",1]],108],
	["`^image/(wikiedit/)?([^/]*)$`",
		[["vars","_ok","","!",1]],109],
	["`^theme/([^/]*)/icon/([^/]*)$`",
		[["vars","_ok","","!",1]],110],
	["`^js/(lang/)?([^/]*)$`",
		[["vars","_ok","","!",1]],111],
	["`^file/(global/)([^/]*)$`",
		[["vars","_ok","","!",1]],112],
	["`^file/thumb/([^/]*)$`",
		[["vars","_ok","","!",1]],113],
	["`^setup/(image|css)/([^/]*)$`",
		[["vars","_ok","","!",1],
		["vars","unlock","","=","1"]],114],
	["`^xml/([^/]*)$`",
		[
			["vars","_ok","","!",1],
			["vars","age","","=","0"]
		],115],
	["'^'",
		[
			["vars","_ok","","!",1],
			["vars","_install","","!=","0"],
			["vars","route","","=","install"],
			["vars","unlock","","=","1"]
		],117],["",
		[
			["vars","session","","=","1"],
			["vars","age","","-",""],
			["vars","static","","-",""]
		],118],
	["`^\\.freecap$`",
		[
			["vars","_ok","","!",1],
			["vars","route","","=","freecap"],
			["vars","session","","=","2"]
		],120],
	["'^'",
		[
			["vars","engine","","=","1"],
			["vars","route","","=","wacko"]
		],122],
	["`^admin\\.php$`",
		[
			["vars","_ok","","!",1],
			["vars","route","","=","admin"]
		],124],
	["`^([^/]*)(/.*)?$`",[],126],
	["",[["match",1,"","~","hashid:2"],
		["vars","page","","=","$ax$b"],
		["vars","method","","=","hashid"],
		["vars","_ok","","!",1]],127],
	["`^(|(?P<page>.*)/)(file|categories|permissions|purge|watch|revisions|referrers_out|filemeta|remove|attachments|addcomment|rate|show|print|languages|watchers|latex|diff|rename|review|moderate|export.xml|referrers|upload|wordprocessor|restore|referrers_sites|edit|properties|new|claim|clone|revisions.xml|source)(/.*?)?$`ii",
		[["vars","method","tolower","=","$3"]],136],["",
		[["vars","method","","==","file"],
		["vars","session","","=","2"]],137],["",
		[["vars","_ok","","!",1]],138],
	["`(?P<page>.*)`",
		[
			["vars","_ok","","!",1],
			["vars","method","","=","show"]
		],141]
]
%%


===Rewrite===
Mode rewrite is now set in the Settings class at runtime, regardless what you set in the config, this follows again the server-agnostic idea.

%%(php)
<?php

// if .htaccess tell us actual info on mod_rewrite status - use it
if (getenv('HTTP_MOD_ENV') === 'on' && AUTO_REWRITE)
{
	$this->rewrite_mode = (getenv('HTTP_MOD_REWRITE') === 'on');
}
%% 

For debugging purposes, the auto-rewrite setting can be turned off via constants.php.
  * ##const AUTO_REWRITE = 0;##
  * rename ##.htaccess## to ##dist.htaccess##

====The .htaccess file====

%%
# No user serviceable parts inside
# If you want to fix anything by tuning htaccess - you're possibly on the wrong path

<IfModule mod_env.c>
	SetEnv HTTP_MOD_ENV on
</IfModule>

<IfModule mod_rewrite.c>
	<IfModule mod_env.c>
		SetEnv HTTP_MOD_REWRITE on
	</IfModule>
	RewriteEngine on
	RewriteRule ^ index.php [QSA,L]
</IfModule>

<IfModule !mod_rewrite.c>
<FilesMatch \.php$>
	# Apache 2.4
	<IfModule mod_authz_core.c>
		Require all denied
	</IfModule>
	# Apache 2.2
	<IfModule !mod_authz_core.c>
		Order Allow,Deny
		Deny from all
	</IfModule>
</FilesMatch>

<FilesMatch "^(admin|index)\.php$">
	# Apache 2.4
	<IfModule mod_authz_core.c>
		Require all granted
	</IfModule>
	# Apache 2.2
	<IfModule !mod_authz_core.c>
		Order Allow,Deny
		Allow from All
		Deny from None
	</IfModule>
</FilesMatch>
</IfModule>
%%



====Example====
  * httpd.conf ##LoadModule rewrite_module modules/mod_rewrite.so##
  * config,php ##'base_url' => '~http://localhost/wiki/',##
  * page tag: ##RecentlyCommented##

=====ON=====
%% LoadModule rewrite_module modules/mod_rewrite.so %%
  result: ##~http://localhost/wiki/RecentlyCommented##

=====OFF=====
%% #LoadModule rewrite_module modules/mod_rewrite.so %%
  result: ##~http://localhost/wiki/?page=RecentlyCommented##

It works without ##index.php/## in ##base_url##, the URI router appends ##?page=## if mode_rewrite is OFF.


===Known Bugs and Limitations===

===Copyrights===
© ((user:RideSnowNow Serge Terekhov)), 2016-2019

===Contacts===
If you found any bugs in the URI Router, please inform us — ((https://wackowiki.org/bugs/ Bugtracker)).