71 if($this->_sharedCacheFile)
try{
72 \Rsi\File::export($temp = $this->_sharedCacheFile . \
Rsi\Str::random() .
'.tmp',$this->_cache);
73 if(!rename($temp,$this->_sharedCacheFile))
throw new \Exception(
"Could not rename('$temp','{$this->_sharedCacheFile}')");
74 chmod($this->_sharedCacheFile,0666);
78 \Rsi\File::unlink($temp);
86 array_walk_recursive($value,
function(&$item){
87 if(is_object($item) && ($item instanceof \Closure)) $item =
true;
97 protected function cache($route,$key){
98 if($this->_cache ===
null){
100 $empty = [self::CACHE_ROUTES => [],self::CACHE_HASHED => []];
101 if($this->_sharedCacheFile)
try{
102 $this->_cache = include($this->_sharedCacheFile);
104 (($this->_cache[self::CACHE_HASH] ??
null) != ($hash = crc32(
'v4' . print_r($this->
stripClosures($this->routes),
true)))) ||
105 (($time = $this->_cache[self::CACHE_TIME] ??
null) && ($time < time() + rand(0,1000) / 1000 * $this->_sharedCacheTtlSpread))
107 $this->_cache = [self::CACHE_HASH => $hash] + $empty;
108 $this->session->lastPath = $this->session->lastRoute =
null;
111 catch(\Throwable $e){
112 \Rsi\File::unlink($this->_sharedCacheFile);
114 $this->_cache = $empty;
117 else $this->_cache = $this->session->cache ?: $empty;
119 if(!array_key_exists($route,$this->_cache[self::CACHE_ROUTES])) $this->_cache[
self::CACHE_ROUTES][$route] = [];
128 protected function save($route,$key,$value){
135 return $this->
component(
'trans')->langs();
143 protected function mask($route,&$mask,&$slots){
144 if($cache = $this->
cache($route,
'mask')) extract($cache);
146 preg_match_all(
'/\\[(.+?)(|\\*.+?)(|:[^\\[\\]]*(\\[.+?\\])*[^\\[\\]]*)\\]/',$mask =
'/^' . str_replace(
'/',
'\\/',$route) .
'$/i',$matches,PREG_SET_ORDER);
148 foreach($matches as list($full,$key,$enum,$regex)){
149 $slot =
'slot' . count($slots);
152 switch(substr($enum,1,1)){
155 $str_id = substr($enum,2);
157 foreach($trans->langs() as $lang_id => $descr) $enum[$lang_id] = $trans->str(
"[@$str_id.$lang_id]");
161 if(method_exists($this,$method =
'enum' . ucfirst(substr($enum,2)))) $enum = $method;
164 $enum = \Rsi\Record::explode(substr($enum,1),
';',
'=');
168 if($regex && array_key_exists($regex = substr($regex,1),$this->regexShortcuts)) $regex = $this->regexShortcuts[$regex];
169 if($component = preg_match(
'/^@(\\w+)\\.(\\w+)$/',$key,$match)) $key =
'component' . count($slots);
171 compact(
'full',
'regex',
'slot',
'enum') +
172 ($component ? [
'component' => $match[1],
'property' => $match[2]] : []);
173 $mask = str_replace($full,
"(?<$slot>.*?)",$mask);
175 $mask = strtr($mask,[
'{' =>
'(?:',
'}' =>
')?']);
176 foreach($slots as $slot)
if($slot[
'regex'] && extract($slot))
177 $mask = str_replace(
"<$slot>.*?",
"<$slot>$regex",$mask);
178 $this->
save($route,
'mask',compact(
'mask',
'slots'));
186 protected function data($route){
187 if($data = $this->
cache($route,
'data'))
return $data;
188 if(!is_array($name = $data = $this->routes[$route])){
189 parse_str(\
Rsi\Str::pop($name,
'?'),$extra);
190 $action = \Rsi\Str::pop($name,
'#');
191 $data = compact(
'name',
'action',
'extra');
193 $this->
save($route,
'data',$data);
203 protected function enum($enum,$params,$data){
204 return is_string($enum) ? call_user_func([$this,$enum],$params,$data) : $enum;
212 protected function hash($hash,$params){
213 unset($params[$hash[
'key'] ?? $this->hashKey]);
216 $hash[
'algo'] ?? $this->hashAlgo,
217 ($hash[
'salt'] ?? $this->
component(
'encrypt')->key) .
218 print_r(array_key_exists(
'keys',$hash) ? \
Rsi\Record::select($params,$hash[
'keys']) : $params,
true)
228 protected function encrypt($encrypt,$params,&$keys){
230 serialize(\
Rsi\Record::select($params,$keys = $encrypt[
'keys'] ?? array_keys($params))),
231 $encrypt[
'salt'] ??
null
240 if($this->_cache ===
null) $this->
cache(
null,
null);
241 return array_key_exists($key = strtolower($this->_controllerName .
'#' . \
Rsi\Str::check($_POST[
'action'] ??
null)),$this->_cache[self::CACHE_HASHED])
251 protected function match($path,$route){
252 if(array_key_exists($route,$this->routes)){
253 $data = $this->
data($route);
254 $this->
mask($route,$mask,$slots);
255 if(preg_match($mask,$path,$values) && (!array_key_exists(
'match',$data) || call_user_func($this->routes[$route][
'match'],$this,$route,$path,$values))){
256 $name = $data[
'name'];
258 foreach($slots as $key => $slot)
if(array_key_exists($slot[
'slot'],$values)){
259 $component = $property =
null;
260 if(extract($slot) && (($value = $values[$slot]) || !$regex || preg_match(
"/^$regex$/",$value))){
261 $value = urldecode($values[$slot]);
262 if($component) $component = $this->
component($component);
264 $enum = $this->
enum($enum,$params,$data);
265 if((!$component || (($enum[$component->get($property)] ??
null) != $value)) && (($value = \Rsi\Record::isearch($enum,$value)) ===
false))
return false;
267 if($component) $component->set($property,$value);
268 else \Rsi\Record::set($params,explode(
'.',$key),$value);
269 if(preg_match(
'/^\\w+$/',$value)) $name = str_replace(
"[$key]",$value,$name);
272 $this->_controllerName = $name;
273 $_GET = $params + $_GET;
274 if($action = $data[
'action'] ??
null) $_POST[
'action'] = $action;
275 if(array_key_exists(
'encrypt',$data) && ($value = \
Rsi\Str::check($_GET[($encrypt = $data[
'encrypt'])[
'key'] ?? $this->encryptKey] ??
null)))
try{
276 if($this->
hashed($cache,
'encrypt') ===
false) $this->
save(
false,$cache,$encrypt[
'keys'] ??
null);
277 $_GET = array_merge($_GET,unserialize($this->
component(
'encrypt')->decrypt(hex2bin($value),$encrypt[
'salt'] ??
null)));
278 $this->_hashed =
true;
283 elseif(array_key_exists(
'hash',$data)){
284 $hash = $data[
'hash'];
285 $keys = $hash[
'keys'] ??
null;
286 if($this->
hashed($cache) ===
false) $this->
save(
false,$cache,$keys);
287 if(hash_equals($this->
hash($hash,$_GET),strval(\
Rsi\Str::check($_GET[$hash[
'key'] ?? $this->hashKey] ??
null)))) $this->_hashed =
true;
288 else foreach(($keys ?: array_keys($_GET)) as $key) $_GET[$key] = $_POST[$key] =
null;
290 $_POST = array_merge($_POST,$data[
'extra'] ?? []);
304 $this->_viewType = $this->_controllerName = $this->_hashed =
null;
305 if($path ===
null) $path = $this->pathInfo;
306 if($path && ($path = trim(preg_replace(
'/^' . preg_quote($this->prefix,
'/') .
'/i',
'',$path),
'/'))){
307 if($this->_viewType = strtolower(pathinfo($path,PATHINFO_EXTENSION)))
308 $path = substr($path,0,-(1 + strlen($this->_viewType)));
309 if($path != $this->session->lastPath){
310 $this->session->lastPath = $path;
311 $this->session->lastRoute =
null;
312 foreach($this->routes as $route => $data)
if($this->
match($path,$route)){
313 $this->session->lastRoute = $route;
317 elseif($route = $this->session->lastRoute) $this->
match($path,$route);
318 if(!$this->_controllerName) $this->_controllerName = ucwords(strtolower($path),
'/');
320 if(!$this->_hashed && (($keys = $this->
hashed()) !==
false)){
321 if($keys)
foreach($keys as $key) $_GET[$key] = $_POST[$key] =
null;
322 else $_GET = $_POST = [];
332 return strtolower($route);
342 public function reverse($controller_name =
null,$type =
null,$params =
null,$strict =
true){
343 if(!$controller_name) $controller_name = $this->controllerName;
344 $link = $this->
format($controller_name);
345 $params = ($params ?: []) + $this->defaultParams;
347 foreach($this->routes as $route => $data){
348 $data = $this->
data($route);
349 if($data[
'name'] == $controller_name){
351 if(array_key_exists(
'extra',$data))
foreach($data[
'extra'] as $key => $value){
352 if(($query[$key] ??
null) != $value)
continue 2;
355 if(array_key_exists(
'reverse',$data)) $data[
'reverse'] = $this->routes[$route][
'reverse'];
356 if(array_key_exists(
'encrypt',$data)){
357 $value = $this->
encrypt($data[
'encrypt'],$query,$keys);
358 foreach($keys as $key) unset($query[$key]);
361 elseif(array_key_exists(
'hash',$data)) $query[$data[
'hash'][
'key'] ??
$this->hashKey] = $this->
hash($data[
'hash'],$params);
362 $this->
mask($route,$mask,$slots);
363 foreach($slots as $key => $slot)
if($slot[
'component'] ??
null)
364 $query[$key] = $this->
component($slot[
'component'])->get($slot[
'property']);
365 foreach($query as $key => $value)
if(array_key_exists($key,$slots) && is_scalar($value) && extract($slots[$key])){
367 $enum = $this->
enum($enum,$params,$data);
368 $value = $enum[$value] ?? $enum[
null] ??
null;
370 if(!$strict || !$regex || (($value !==
null) && preg_match(
"/^$regex$/",$value))){
371 $route = str_replace($full,rawurlencode($value ??
''),$route);
375 do($route = preg_replace(
'/{[^{}]*?\\[[^\\[]*?\\][^{}]*?}/',
'',$route,-1,$count));
377 if($match = strpos($route,
'[') ===
false){
378 $link = str_replace([
'{',
'}'],
'',$route);
379 if(array_key_exists(
'reverse',$data)) call_user_func_array($data[
'reverse'],[$this,&$link,&$type,&$query]);
384 if($type) $link .=
'.' . $type;
385 if($query = http_build_query($match ? $query : $params)) $link .= (strpos($link,
'?') ===
false ?
'?' :
'&') . $query;
386 return $this->prefix . $link;
390 if($this->_controllerName ===
null) $this->
execute();
395 return parse_url($_SERVER[
'REQUEST_URI'] ??
null,PHP_URL_PATH);
399 if($this->_controllerName ===
null) $this->
execute();