29 public $errorStr =
'[caption]{[!caption]{[id]}}: [error]';
44 protected $_checkWidgets = [
'default' =>
false,
'fragment' =>
false,
'pingAlive' =>
false,
'clientError' =>
false,
'featureHint' =>
false,
'requestSocket' =>
false];
54 $this->
publish(
'authSets',self::READWRITE);
55 if($this->action ==
'default') $this->
domainRedir();
60 foreach($this->
domainRedir as $mask => $redir)
if(preg_match($mask,$domain)){
61 \Rsi\Http::redirHeader($redir . \
Rsi\Record::get($_SERVER,
'REQUEST_URI'),$this->domainRedirPermanent);
67 $widgets = $constraints = [];
68 foreach($this->widgets as $id => $widget) $widgets[$id] = array_filter($widget->clientConfig(),
function($value){
69 return $value !== null;
71 foreach($this->constraints as $constraint) $constraints[] = array_filter($constraint);
72 return array_merge(parent::clientConfig(),[
73 'route' => $this->
route(
'*'),
74 'widgets' => $widgets,
75 'checkWidgets' => $this->_checkWidgets,
76 'constraints' => $constraints
84 if(!$this->_authSets) $this->_authSets[] = [$name];
85 else foreach($this->_authSets as &$set)
if(!in_array($name,$set)) $set[] = $name;
94 foreach($this->_authSets as $key => $set) $sets[$key] = array_diff($set,[$name]);
95 $this->_authSets = array_filter($sets);
103 $widget->controller = $this;
104 $this->_widgets[$id] = $widget;
111 foreach($widgets as $id => $widget) $this->
addWidget($id,$widget);
120 $class_name = (substr($type,0,1) ==
'\\' ?
'' : $this->defaultWidgetNamespace .
'\\') . ucfirst($type);
121 return new $class_name($config);
140 $def = $this->
component(
'def')->column($table,$column,$extra);
150 protected function addFromDef($table,$columns =
null,$extra =
null,$prefix =
null){
151 if(!$columns) $columns = array_keys($this->
component(
'def')->table($table));
152 elseif(!is_array($columns)) $columns = [$columns];
153 foreach($columns as $column) $this->
addWidget($prefix . $column,$this->
widgetFromDef($table,$column,$extra));
161 foreach($record->columns as $column => $config) $this->
addByType($prefix . $column,$config[
'type'],$config);
170 foreach($record->columns as $column => $config) $request->data[$prefix . $column] = $record->get($column);
179 foreach($record->columns as $column => $config) $record->set($column,$request->data[$prefix . $column]);
187 foreach($ids as $id) unset($this->_widgets[$id]);
197 protected function addConstraint($ids,$operator,$group =
null,$total =
null){
198 $this->_constraints[] = compact(
'ids',
'operator',
'group',
'total');
212 $this->_action =
null;
220 $expected = $this->session->expected ?: [];
221 \Rsi\Record::set($expected,[$id,$action],
true);
222 $this->session->expected = $expected;
231 if($result = \
Rsi\Record::get($expected = $this->session->expected,[$id,$action = $this->action])){
232 unset($expected[$id][$action]);
233 $this->session->expected = $expected;
250 public function route($type =
null,$params =
null){
252 if(($type ===
null) && (($type = $router->viewType) == $this->defaultViewType)) $type =
null;
253 return $router->reverse($this->name,$type,($params ?: []) + $this->
routeParams());
261 public function redir($levels = 1,$suffix =
null,$params =
null){
263 $name = \Rsi\File::dirname($this->name,$levels) . $suffix;
264 if($params ===
false) $request->viewControllerName = $name;
265 else $request->redir = $this->
component(
'router')->reverse($name,
null,$params);
273 return str_replace(
'*',$id,$this->defaultCaption);
281 return str_replace(
'*',$id,$this->defaultHint);
287 $this->_fred->externalError(
'Unknown controller action',[
'class' => get_called_class(),
'action' => $this->action]);
298 $this->fragmentId = $this->_fred->request->fragmentId;
308 $message->warning($this->inactiveMessage,[
309 'maxSessionTime' => $alive->maxSessionTime ? $local->formatNumber($alive->maxSessionTime,1,0) :
null,
310 'maxInactiveTime' => $alive->maxInactiveTime ? $local->formatNumber($alive->maxInactiveTime,1,0) :
null,
311 'sessionStart' => $local->formatDateTime($alive->session->start),
312 'sessionAlive' => $local->formatDateTime($alive->session->alive)
320 if($this->clientErrorPrio){
321 $security = $this->
component(
'security');
324 (count($errors = $security->session->clientErrors ?: []) < $this->clientErrorMax) &&
325 !in_array($hash = md5($request->message . $request->url . $request->lineNo),$errors)
328 $this->clientErrorPrio,
329 'Client error: ' . $request->message,
332 array_filter([
'column' => $request->column,
'trace' => $request->trace,
'journal' => $request->journal])
334 if(!$errors) $security->addBan(
'clientError',$this->clientErrorBanDelay);
336 $security->session->clientErrors = $errors;
345 $request->result = $this->
component(
'hint')->trans($request->id,$request->count);
353 'host' => $socket->clientHost,
354 'port' => $socket->clientPort,
355 'prefix' => $socket->clientPrefix,
356 'token' => $socket->token()
365 $this->
component(
'request')->errors[$id] = $this->
component(
'trans')->str($this->errorStr,[
368 'caption' => strip_tags($this->widgets[$id]->
caption($id))
378 if($values)
switch($type){
396 protected function checkConstraint($widget_ids,$ids,$operator,$group =
null,$total =
null){
400 foreach($ids as $id){
403 in_array($widget_id = array_shift($id),$widget_ids) &&
404 !$this->widgets[$widget_id]->nothing($value = $request->data[$widget_id])
406 $sub_ids[] = [$widget_id,$id];
407 if($indexes ===
null)
while($id)
switch($sub = array_shift($id)){
409 $indexes = $value ? array_keys($value) : [];
412 $value = \Rsi\Record::get($value,$sub);
416 if($indexes ===
null) $indexes = [
null];
417 foreach($indexes as $i => $index){
418 $init = $prev =
false;
420 foreach($sub_ids as list($widget_id,$id)){
421 $value = $request->data[$widget_id];
423 switch($sub = array_shift($id)){
429 $sub = $indexes[$i - 1];
432 if($i + 1 >= count($indexes))
break 3;
433 $sub = $indexes[$i + 1];
436 $value = \Rsi\Record::get($value,$sub);
437 if(!is_numeric($sub) && $this->widgets[$widget_id]->widgets[$sub]->nothing($value))
continue 2;
439 if($group) $values[] = $value;
440 elseif(!$init) $init =
true;
441 elseif(!\
Rsi\Str::operator($prev,$operator,$value)) $this->
addError($widget_id,
'constraint');
444 if($group && $values && !\
Rsi\Str::operator(
447 $total ===
true ? count($values) : $total
448 ))
foreach($sub_ids as list($widget_id,$id)) $this->
addError($widget_id,
'group');
459 if(!$ids) $ids = array_keys($this->widgets);
460 foreach($ids as $id){
461 $widget = $this->widgets[$id];
462 if($widget->writeable && ($error = $widget->check($request->data[$id] ??
null))) $this->
addError($id,$error);
464 if(!$request->errors)
foreach($this->constraints as $constraint)
465 $this->
checkConstraint($ids,$constraint[
'ids'],$constraint[
'operator'],$constraint[
'group'],$constraint[
'total']);
466 return !$request->errors;
477 if(!array_key_exists($widget_id,$this->widgets))
478 $this->_fred->externalError(
'Unknown widget',[
'class' => get_called_class(),
'id' => $widget_id]);
480 $widget = $this->widgets[$widget_id];
481 if(!$widget->readable) $this->_fred->externalError(
482 'User not authorized for widget',
483 [
'class' => get_called_class(),
'id' => $widget_id,
'action' => $this->action]
485 else $widget->execute($id);
488 elseif(!method_exists($this,$method =
'action' . ucfirst($this->action))) $this->
unkownAction();
490 if(!$this->
component(
'security')->check($this->securityChecksIgnore))
491 $this->_fred->externalError(
'Suspicious controller call',[
'class' => get_called_class()]);
492 $convert = $this->view->convert;
493 if(($widgets = $this->widgets) && ($ids = array_key_exists($this->action,$this->_checkWidgets) && ($this->_checkWidgets[$this->action] !==
null)
494 ? $this->_checkWidgets[$this->action]
495 : (array_key_exists(
null,$this->_checkWidgets) ? $this->_checkWidgets[
null] : array_keys($widgets))
497 foreach($ids as $id){
498 $widget = $widgets[$id];
499 if($widget->writeable){
500 $value = $widget->purge($request->complex([
'widget',$id]));
501 $value = $convert ? $widget->convert($value) : $widget->clientConvert($value);
502 if($error = $widget->check($value)){
503 $request->data[$id] =
null;
506 else $request->data[$id] = $widget->dataConvert($value);
509 if(!$request->errors)
foreach($this->constraints as $constraint)
510 $this->
checkConstraint($ids,$constraint[
'ids'],$constraint[
'operator'],$constraint[
'group'],$constraint[
'total']);
512 foreach($widgets as $id => $widget)
if(!$ids || !in_array($id,$ids)) $request->data[$id] = $widget->defaultValue;
513 if(!$request->errors) call_user_func([$this,$method]);
523 if(array_key_exists($action ?:
null,$this->_rights)){
524 $right = $this->_rights[$action];
528 elseif(array_key_exists(
null,$this->_rights)) $right = $this->_rights[
null];
538 $prefix =
'action' . ucfirst($prefix);
539 foreach(get_class_methods($this) as $method)
if(
540 \
Rsi\Str::startsWith($method,$prefix) &&
542 ) $actions[] = $action;
547 if($this->_action ===
null) $this->_action = $this->
component(
'request')->action ?:
'default';
552 foreach($check_widgets as $action => $ids) $this->_checkWidgets[$action] = $ids ? array_values($ids) : $ids;
556 if($this->_constraints ===
null) $this->
getWidgets();
561 foreach($this->
component(
'front')->controllerNamespaces + [
null =>
null] as $namespace => $prefix)
562 if(\
Rsi\Str::startsWith(get_called_class(),$namespace))
break;
563 return str_replace(
'\\',
'/',$prefix . substr(get_called_class(),strlen($namespace) + ($prefix ? 0 : 1)));
571 $this->_rights = array_merge($this->_rights,$rights);
575 if($this->action && array_key_exists($this->action,$this->_securityChecksIgnore))
return $this->_securityChecksIgnore[$this->action];
576 if(array_key_exists(
null,$this->_securityChecksIgnore))
return $this->_securityChecksIgnore[
null];
581 $this->_securityChecksIgnore = array_merge($this->_securityChecksIgnore,$security_checks_ignore);
585 if($this->_view ===
null){
588 !class_exists($class_name = str_replace(
'Controller',
'View',$this->viewClassName ?: get_called_class()) .
'\\' . ucfirst($type)) &&
589 !class_exists($class_name = $this->defaultViewNamespace .
'\\' . ucfirst($type)) &&
590 !class_exists($class_name = str_replace(
'Controller',
'View',$this->viewClassName ?: get_called_class()) .
'\\' . ucfirst($type = $this->defaultViewType))
591 ) $class_name = $this->defaultViewNamespace .
'\\' . ucfirst($type);
592 $this->_view =
new $class_name($this->_fred,array_replace_recursive(
594 $this->
config([
'view',$type],[]),
595 [
'controller' => $this]
602 if($this->_widgets ===
null){
603 $this->_widgets = $this->_constraints = [];
610 if($value ===
false) $this->_widgets = [];
615 return $this->widgets[$key];
618 protected function _set($key,$value){