28 public $errorStr =
'[caption]{[!caption]{[id]}}: [error]';
43 protected $_checkWidgets = [
'default' =>
false,
'fragment' =>
false,
'pingAlive' =>
false,
'clientError' =>
false,
'featureHint' =>
false,
'requestSocket' =>
false];
53 $this->
publish(
'authSets',self::READWRITE);
54 if($this->action ==
'default') $this->
domainRedir();
59 foreach($this->
domainRedir as $mask => $redir)
if(preg_match($mask,$domain)){
60 \Rsi\Http::redirHeader($redir . \
Rsi\Record::get($_SERVER,
'REQUEST_URI'),$this->domainRedirPermanent);
66 $widgets = $constraints = [];
67 foreach($this->widgets as $id => $widget) $widgets[$id] = array_filter($widget->clientConfig());
68 foreach($this->constraints as $constraint) $constraints[] = array_filter($constraint);
69 return array_merge(parent::clientConfig(),[
70 'route' => $this->
route(
'*'),
71 'widgets' => $widgets,
72 'checkWidgets' => $this->_checkWidgets,
73 'constraints' => $constraints
81 if(!$this->_authSets) $this->_authSets[] = [$name];
82 else foreach($this->_authSets as &$set)
if(!in_array($name,$set)) $set[] = $name;
91 foreach($this->_authSets as $key => $set) $sets[$key] = array_diff($set,[$name]);
92 $this->_authSets = array_filter($sets);
100 $widget->controller = $this;
101 $this->_widgets[$id] = $widget;
108 foreach($widgets as $id => $widget) $this->
addWidget($id,$widget);
117 $class_name = (substr($type,0,1) ==
'\\' ?
'' : $this->defaultWidgetNamespace .
'\\') . ucfirst($type);
118 return new $class_name($config);
137 $def = $this->
component(
'def')->column($table,$column,$extra);
147 protected function addFromDef($table,$columns = null,$extra = null,$prefix = null){
148 if(!$columns) $columns = array_keys($this->
component(
'def')->table($table));
149 elseif(!is_array($columns)) $columns = [$columns];
150 foreach($columns as $column) $this->
addWidget($prefix . $column,$this->
widgetFromDef($table,$column,$extra));
158 foreach($record->columns as $column => $config) $this->
addByType($prefix . $column,$config[
'type'],$config);
167 foreach($record->columns as $column => $config) $request->data[$prefix . $column] = $record->get($column);
176 foreach($record->columns as $column => $config) $record->set($column,$request->data[$prefix . $column]);
184 foreach($ids as $id) unset($this->_widgets[$id]);
194 protected function addConstraint($ids,$operator,$group = null,$total = null){
195 $this->_constraints[] = compact(
'ids',
'operator',
'group',
'total');
209 $this->_action = null;
217 $expected = $this->session->expected ?: [];
218 \Rsi\Record::set($expected,[$id,$action],
true);
219 $this->session->expected = $expected;
228 if($result = \
Rsi\Record::get($expected = $this->session->expected,[$id,$action = $this->action])){
229 unset($expected[$id][$action]);
230 $this->session->expected = $expected;
247 public function route($type = null,$params = null){
249 if(($type === null) && (($type = $router->viewType) == $this->defaultViewType)) $type = null;
250 return $router->reverse($this->name,$type,($params ?: []) + $this->
routeParams());
258 public function redir($levels = 1,$suffix = null,$params = null){
260 $name = \Rsi\File::dirname($this->name,$levels) . $suffix;
261 if($params ===
false) $request->viewControllerName = $name;
262 else $request->redir = $this->
component(
'router')->reverse($name,null,$params);
270 return str_replace(
'*',$id,$this->defaultCaption);
278 return str_replace(
'*',$id,$this->defaultHint);
284 $this->_fred->externalError(
'Unknown controller action',[
'class' => get_called_class(),
'action' => $this->action]);
295 $this->fragmentId = $this->_fred->request->fragmentId;
305 $message->warning($this->inactiveMessage,[
306 'maxSessionTime' => $alive->maxSessionTime ? $local->formatNumber($alive->maxSessionTime,1,0) : null,
307 'maxInactiveTime' => $alive->maxInactiveTime ? $local->formatNumber($alive->maxInactiveTime,1,0) : null,
308 'sessionStart' => $local->formatDateTime($alive->session->start),
309 'sessionAlive' => $local->formatDateTime($alive->session->alive)
317 if($this->clientErrorPrio){
318 $security = $this->
component(
'security');
321 (count($errors = $security->session->clientErrors ?: []) < $this->clientErrorMax) &&
322 !in_array($hash = md5($request->message . $request->url . $request->lineNo),$errors)
325 $this->clientErrorPrio,
326 'Client error: ' . $request->message,
329 array_filter([
'column' => $request->column,
'trace' => $request->trace,
'journal' => $request->journal])
331 if(!$errors) $security->addBan(
'clientError',$this->clientErrorBanDelay);
333 $security->session->clientErrors = $errors;
342 $request->result = $this->
component(
'hint')->trans($request->id,$request->count);
350 'host' => $socket->clientHost,
351 'port' => $socket->clientPort,
352 'prefix' => $socket->clientPrefix,
353 'token' => $socket->token()
362 $this->
component(
'request')->errors[$id] = $this->
component(
'trans')->str($this->errorStr,[
365 'caption' => strip_tags($this->widgets[$id]->
caption($id))
375 if($values)
switch($type){
376 case self::TOTAL_SUM:
return array_sum($values);
377 case self::TOTAL_COUNT:
return count($values);
378 case self::TOTAL_AVG:
return array_sum($values) / count($values);
379 case self::TOTAL_MAX:
return max($values);
380 case self::TOTAL_MIN:
return min($values);
381 case self::TOTAL_UNIQUE:
return count(array_unique($values));
393 protected function checkConstraint($widget_ids,$ids,$operator,$group = null,$total = null){
397 foreach($ids as $id){
400 in_array($widget_id = array_shift($id),$widget_ids) &&
401 !$this->widgets[$widget_id]->nothing($value = $request->data[$widget_id])
403 $sub_ids[] = [$widget_id,$id];
404 if($indexes === null)
while($id)
switch($sub = array_shift($id)){
406 $indexes = $value ? array_keys($value) : [];
409 $value = \Rsi\Record::get($value,$sub);
413 if($indexes === null) $indexes = [null];
414 foreach($indexes as $i => $index){
415 $init = $prev =
false;
417 foreach($sub_ids as list($widget_id,$id)){
418 $value = $request->data[$widget_id];
420 switch($sub = array_shift($id)){
426 $sub = $indexes[$i - 1];
429 if($i + 1 >= count($indexes))
break 3;
430 $sub = $indexes[$i + 1];
433 $value = \Rsi\Record::get($value,$sub);
434 if(!is_numeric($sub) && $this->widgets[$widget_id]->widgets[$sub]->nothing($value))
continue 2;
436 if($group) $values[] = $value;
437 elseif(!$init) $init =
true;
438 elseif(!\
Rsi\Str::operator($prev,$operator,$value)) $this->
addError($widget_id,
'constraint');
441 if($group && $values && !\
Rsi\Str::operator(
444 $total ===
true ? count($values) : $total
445 ))
foreach($sub_ids as list($widget_id,$id)) $this->
addError($widget_id,
'group');
456 if(!$ids) $ids = array_keys($this->widgets);
457 foreach($ids as $id){
458 $widget = $this->widgets[$id];
459 if($widget->writeable && ($error = $widget->check($request->data[$id] ?? null))) $this->
addError($id,$error);
461 if(!$request->errors)
foreach($this->constraints as $constraint)
462 $this->
checkConstraint($ids,$constraint[
'ids'],$constraint[
'operator'],$constraint[
'group'],$constraint[
'total']);
463 return !$request->errors;
474 if(!array_key_exists($id,$this->widgets))
475 $this->_fred->externalError(
'Unknown widget',[
'class' => get_called_class(),
'id' => $id]);
477 $widget = $this->widgets[$id];
478 if(!$widget->readable) $this->_fred->externalError(
479 'User not authorized for widget',
480 [
'class' => get_called_class(),
'id' => $id,
'action' => $this->action]
482 else $widget->execute();
485 elseif(!method_exists($this,$method =
'action' . ucfirst($this->action))) $this->
unkownAction();
487 if(!$this->
component(
'security')->check($this->securityChecksIgnore))
488 $this->_fred->externalError(
'Suspicious controller call',[
'class' => get_called_class()]);
489 $convert = $this->view->convert;
490 if(($widgets = $this->widgets) && ($ids = array_key_exists($this->action,$this->_checkWidgets) && ($this->_checkWidgets[$this->action] !== null)
491 ? $this->_checkWidgets[$this->action]
492 : (array_key_exists(null,$this->_checkWidgets) ? $this->_checkWidgets[null] : array_keys($widgets))
494 foreach($ids as $id){
495 $request->data[$id] = null;
496 $widget = $widgets[$id];
497 if($widget->writeable){
498 $value = $widget->purge($request->complex([
'widget',$id]));
499 $value = $convert ? $widget->convert($value) : $widget->clientConvert($value);
500 if($error = $widget->check($value)) $this->
addError($id,$error);
501 else $request->data[$id] = $widget->dataConvert($value);
504 if(!$request->errors)
foreach($this->constraints as $constraint)
505 $this->
checkConstraint($ids,$constraint[
'ids'],$constraint[
'operator'],$constraint[
'group'],$constraint[
'total']);
507 foreach($widgets as $id => $widget)
if(!$ids || !in_array($id,$ids)) $request->data[$id] = $widget->defaultValue;
508 if(!$request->errors) call_user_func([$this,$method]);
518 if(array_key_exists($action ?: null,$this->_rights)){
519 $right = $this->_rights[$action];
523 elseif(array_key_exists(null,$this->_rights)) $right = $this->_rights[null];
533 $prefix =
'action' . ucfirst($prefix);
534 foreach(get_class_methods($this) as $method)
if(
535 \
Rsi\Str::startsWith($method,$prefix) &&
537 ) $actions[] = $action;
542 if($this->_action === null) $this->_action = $this->
component(
'request')->action ?:
'default';
547 foreach($check_widgets as $action => $ids) $this->_checkWidgets[$action] = $ids ? array_values($ids) : $ids;
551 if($this->_constraints === null) $this->
getWidgets();
556 foreach($this->
component(
'front')->controllerNamespaces + [null => null] as $namespace => $prefix)
557 if(\
Rsi\Str::startsWith(get_called_class(),$namespace))
break;
558 return str_replace(
'\\',
'/',$prefix . substr(get_called_class(),strlen($namespace) + ($prefix ? 0 : 1)));
566 $this->_rights = array_merge($this->_rights,$rights);
570 if($this->action && array_key_exists($this->action,$this->_securityChecksIgnore))
return $this->_securityChecksIgnore[$this->action];
571 if(array_key_exists(null,$this->_securityChecksIgnore))
return $this->_securityChecksIgnore[null];
576 $this->_securityChecksIgnore = array_merge($this->_securityChecksIgnore,$security_checks_ignore);
580 if($this->_view === null){
583 !class_exists($class_name = str_replace(
'Controller',
'View',$this->viewClassName ?: get_called_class()) .
'\\' . ucfirst($type)) &&
584 !class_exists($class_name = $this->defaultViewNamespace .
'\\' . ucfirst($type)) &&
585 !class_exists($class_name = str_replace(
'Controller',
'View',$this->viewClassName ?: get_called_class()) .
'\\' . ucfirst($type = $this->defaultViewType))
586 ) $class_name = $this->defaultViewNamespace .
'\\' . ucfirst($type);
587 $this->_view =
new $class_name($this->_fred,array_replace_recursive(
589 $this->
config([
'view',$type],[]),
590 [
'controller' => $this]
597 if($this->_widgets === null){
598 $this->_widgets = $this->_constraints = [];
605 if($value ===
false) $this->_widgets = [];
610 return $this->widgets[$key];
613 protected function _set($key,$value){
route($type=null, $params=null)
Route to this controller.
addAuth($name)
Add an authentication check to all sets, or create a new one if none exists.
setCheckWidgets($check_widgets)
$inactiveMessage
Message for inactive user who is redirected to login controller.
addWidgets($widgets)
Add multiple widgets.
widgetFromDef($table, $column, $extra=null)
Create a widget from a database definition.
setSecurityChecksIgnore($security_checks_ignore)
addByType($id, $type, $config)
Add a widget by type.
removeAuth($name)
Remove an authentication check from all sets.
addFromDef($table, $columns=null, $extra=null, $prefix=null)
Add widgets from a database definition.
addError($id, $error)
Add an error to the request.
$clientErrorBanDelay
Ban delay for the first error in a new session (prevent flooding via multiple.
expect($id, $action)
Note if an action is expected.
redir($levels=1, $suffix=null, $params=null)
Navigate to another controller.
config($key, $default=null)
Retrieve a config value.
$_authSets
Possible sets of necessary authentication (array of arrays of authentication checks).
addWidget($id, $widget)
Add a single widget.
$clientErrorMax
Maximum number of client errors to log in one session.
actionDefault()
Default action (if no action specified).
routeParams()
Parameters for the route to this controller.
$_action
Widgets to check per action (key = action; value = array with ID's of widget to check; null = default...
dataToRecord($record, $prefix=null)
Copy data from the request to a definition record.
checkConstraint($widget_ids, $ids, $operator,$group=null, $total=null)
Check a constraint.
addFromRecord($record, $prefix=null)
Add widgets from a definition record.
actionRight($action)
Necessary right for an action.
actionClientError()
Log client-side errors.
actionFeatureHint()
Return a feature hint translation.
$domainRedirPermanent
True to make a redirection permanent (HTTP status code = 301; defaults to 302).
deleteWidget(... $ids)
Remove one or more widget(s).
$display
Default display mode for widgets.
checkWidgets($ids=null)
Check widget values.
widgetByType($type, $config)
Create a widget by type.
hint($id)
Default hint for a widget.
addConstraint($ids, $operator,$group=null, $total=null)
Add a constraint.
unkownAction()
Unknwon action called.
$defaultCaption
An asterisk will be replaced with widget ID.
$_securityChecksIgnore
Security checks to ignore (key = action, null = default; value = array with.
actionPingAlive()
Ping to keep the session alive.
reset()
Reset the controller.
publish($property, $visibility=self::READABLE)
Publish a property (or hide it again).
initWidgets()
Initialize the widgets.
expected($id, $action)
Was the action expected?
$viewClassName
Fix the view to a certain View or Controller class (empty = same as called class).
const RIGHT_LEVEL_SEPARATOR
Separator between right and level in a single string notation.
actions($prefix=null)
Available actions for current user.
dataFromRecord($record, $prefix=null)
Copy data from a definition record to the request.
arrayTotal($values, $type)
Calculate the total for an array.
actionFragment()
Return a fragment from the view.
execute()
Execute the action (if specified) on the controller.
$clientErrorPrio
Log client errors with this prio (empty = do not log).
actionRequestSocket()
Request a socket.
$domainRedir
Prefered domain notation (key = domain mask regex, value = prefered domain, incl. protocol)...
caption($id)
Default caption for a widget.
component($name)
Get a component (local or default).
$defaultHint
An asterisk will be replaced with the widget ID or type.
getSecurityChecksIgnore()
$_rights
Necessary right needed per action (key = action, null = default; value = right, optionally.