FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Widget.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi\Fred\Controller;
4 
5 use Rsi\Fred\User;
6 
7 class Widget extends \Rsi\Thing{
8 
9  const CHILD_ID_SEPARATOR = '__';
10 
11  //configuration options
12  const CAPTION = 'caption';
13  const DEFAULT_VALUE = 'defaultValue';
14  const DISPLAY = 'display'; //!< Display type (see DISPLAY_* constants).
15  const HELP = 'help'; //!< Help string (translated).
16  const HINT = 'hint'; //!< Hint string (translated).
17  const PARAMS = 'params'; //!< Extra parameters (prefix the key with a '@' to point to the value of another widget).
18  const RIGHT = 'right'; //!< Optional different right with respect to the controller. If the user has the right, then (s)he
19  // may view the widget. If the level is also met, the widget is also writeable.
20  const TRAILER = 'trailer'; //!< Trailer string (translated).
21  const TAGS = 'tags'; //!< Extra tags for help, hint, and trailer.
22  const WIDGET_HELP = 'widgetHelp'; //!< General widget help (translated).
23  const CLASS_NAME = 'className';
24 
25  //checks
26  const MIN = 'min'; //!< Minimum value.
27  const MAX = 'max'; //!< Maximum value.
28  const REQUIRED = 'required'; //!< True for required. Set to self::VALID_FUNC to let the validation function handle this. Use
29  // an array with [reference,operator,value] to make this dependable on the value of another widget.
30  const VALID_FUNC = 'validFunc'; //!< Specific validation function (called with value and index; returns true if OK).
31  const WIDGETS = 'widgets'; //!< Optional child widgets (key = name, value = widget object).
32  const SECURITY_CHECKS_IGNORE = 'securityChecksIgnore'; //!< Security checks to ignore (array with checks to ignore, true =
33  // all).
34 
35  //display options (note: alphabetical order)
36  const DISPLAY_HIDDEN = 'h'; //!< Do not show (only hidden)
37  const DISPLAY_MINIMAL = 'm'; //!< Show only value (without input element(s)).
38  const DISPLAY_READABLE = 'r'; //!< Show input element(s), but read-only.
39  const DISPLAY_WRITEABLE = 'w'; //!< Show editable input element(s).
40 
41  const EVENT_ACTION_PREFIX = 'controller:widget:action:';
42 
43  public $defaultValue = null;
44  public $help = null;
45  public $min = null;
46  public $max = null;
47  public $required = null;
48  public $trailer = null;
49  public $widgetHelp = null;
50  public $validFunc = null;
51 
52  public $parent = null; //!< Optional parent widget.
53 
54  protected $_controller = null;
55  protected $_id = null;
56  protected $_widgets = null; //!< Optional child widgets.
57  protected $_securityChecksIgnore = []; //!< Security checks to ignore.
58  protected $_config = []; //!< Widget configuration (see constants).
59  protected $_display = null;
60  protected $_params = null;
61  protected $_right = null;
62  protected $_tags = null;
63 
64  public function __construct($config = null){
65  $this->_config = $config ?: [];
66  // Note: At this moment we do not perform the initialization, because the widget is not yet connected to a controller. When
67  // the controller gets set, the widget will be initialized.
68  }
69 
70  protected function init(){
71  $this->configure($this->_config = array_merge(
72  $this->_controller->config(['widget',lcfirst(\Rsi\File::basename(get_called_class()))],[]),
74  ));
75  $this->publish(['controller','securityChecksIgnore']);
76  $this->publish('parent',self::READWRITE);
77  }
78  /**
79  * Update the widget configuration.
80  * @param array $config New configuration (key-value pairs). Old values, which are not in the new configuration, are
81  * preserved.
82  */
83  public function update($config){
84  $this->_config = array_merge($this->_config,$config);
85  $this->configure($config);
86  }
87  /**
88  * Get single configuration value.
89  * @param string $key Key.
90  * @param mixed $default Default value.
91  * @return mixed Value.
92  */
93  protected function config($key,$default = null){
94  return \Rsi\Record::get($this->_config,$key,$default);
95  }
96  /**
97  * Public configuration.
98  * @return array Public configuration for this widget in key => value pairs.
99  */
100  public function clientConfig(){
101  $config = array_merge(
102  [self::CLASS_NAME => get_called_class()],
103  $this->get([self::DEFAULT_VALUE,self::DISPLAY,self::MIN,self::MAX,self::REQUIRED])
104  );
105  if($params = $this->config(self::PARAMS)){
106  foreach($params as $key => $value) if(substr($key,0,1) != '@') unset($params[$key]);
107  if($params) $config[self::PARAMS] = array_values($params);
108  }
109  if($this->validFunc) $config[self::VALID_FUNC] = is_array($this->validFunc)
110  ? (is_object($this->validFunc[0]) ? get_class($this->validFunc[0]) : $this->validFunc[0]) . '::' . $this->validFunc[1]
111  : $this->validFunc;
112  if($this->trailer) $config[self::TRAILER] = $this->trans->str($this->trailer,$this->tags);
113  if($this->help) $config[self::HELP] = true;
114  if($this->widgetHelp) $config[self::WIDGET_HELP] = true;
115  if($this->widgets){
116  $widgets = [];
117  foreach($this->widgets as $id => $widget) $widgets[$id] = array_filter($widget->clientConfig());
118  $config[self::WIDGETS] = $widgets;
119  }
120  return $config;
121  }
122 
123  public function caption($id = null){
124  $caption = $this->config(self::CAPTION);
125  return $this->trans->str($caption === null ? $this->_controller->caption($id ?: $this->id) : $caption);
126  }
127 
128  public function hint($id = null){
129  $hint = $this->config(self::HINT);
130  return $hint === null
131  ? $this->trans->str($this->_controller->hint($id ?: $this->id),$this->tags) ?:
132  $this->trans->str($this->_controller->hint(\Rsi\File::basename(get_called_class())),$this->tags)
133  : $this->trans->str($hint,$this->tags);
134  }
135  /**
136  * Convert a value from user format to standard, internal format.
137  * @param mixed $value Value in user format.
138  * @return mixed Value in standard, internal format.
139  */
140  public function convert($value){
141  return $value;
142  }
143  /**
144  * Convert a value from client format to standard, internal format.
145  * @param mixed $value Value in client format.
146  * @return mixed Value in standard, internal format.
147  */
148  public function clientConvert($value){
149  return $value;
150  }
151  /**
152  * Convert an internal value before it is stored in the request data.
153  * @param mixed $value Value in internal format.
154  * @return mixed Value in request data format.
155  */
156  public function dataConvert($value){
157  return $value;
158  }
159  /**
160  * Format a value from standard, internal format to user format.
161  * @param mixed $value Value in standard, internal format.
162  * @return mixed Value in user format.
163  */
164  public function format($value){
165  return $value;
166  }
167  /**
168  * Format a value from standard, internal format to client format.
169  * @param mixed $value Value in standard, internal format.
170  * @return mixed Value in client format.
171  */
172  public function clientFormat($value){
173  return $value;
174  }
175  /**
176  * Format a tag value.
177  * @param mixed $value Value in standard, internal format.
178  * @return mixed Value in user format.
179  */
180  public function formatTag($value){
181  return $this->format($value);
182  }
183  /**
184  * Purge function that will be called first.
185  * @param mixed $value Raw value.
186  * @return string Purged value.
187  */
188  protected function purgeBase($value){
189  return $value;
190  }
191  /**
192  * Purge a value.
193  * @param mixed $value Raw value.
194  * @return string Purged value.
195  */
196  public function purge($value){
197  $value = $this->purgeBase($value);
198  foreach(get_class_methods($this) as $method)
199  if((strlen($method) > 5) && (substr($method,0,5) == 'purge') && ($method != 'purgeBase')) $value = call_user_func([$this,$method],$value);
200  return $value;
201  }
202  /**
203  * Note if an action is expected.
204  * @param string $action
205  */
206  public function expect($action){
207  $this->_controller->expect($this->id,$action);
208  }
209  /**
210  * Execute an action on a child widget.
211  * The widget must determine the child from the ID. E.g. for a list the ID could be [list,5,name] (name of the 5th record on
212  * the list). The 'name' child widget will be called then (with 5 as the index).
213  * @param array $id Complete compound ID, including the ID of this parent.
214  */
215  protected function executeChild($id){
216  $this->fred->externalError('Unknown child widget',['class' => get_called_class(),'id' => $id]);
217  }
218  /**
219  * Execute an action on this widget.
220  * Security checks are executed before. All input and output is handled through the request component. An action can only
221  * trust the values from the data array. An action must check if the widget is writeable for actions that perform 'write
222  * like' behaviour.
223  */
224  public function execute(){
225  if(!$this->security->check($this->securityChecksIgnore,$this->_controller->expected($this->id,$action = $this->_controller->action)))
226  $this->fred->externalError('Suspicious widget action call',['class' => get_called_class()]);
227  elseif(count($id = explode(self::CHILD_ID_SEPARATOR,$this->request->widgetId)) > 1) $this->executeChild($id);
228  elseif(!method_exists($this,$method = 'action' . ucfirst($action)))
229  $this->fred->externalError('Unknown widget action',['class' => get_called_class(),'action' => $action]);
230  else{ //action on this widget
231  call_user_func([$this,$method]);
232  $this->event->trigger(self::EVENT_ACTION_PREFIX . lcfirst(\Rsi\File::basename(get_called_class())) . ':' . $action,$this,array_pop($id));
233  }
234  }
235  /**
236  * Check if a value is empty.
237  * @param mixed $value
238  * @return bool True when empty.
239  */
240  public function nothing($value){
241  return \Rsi::nothing($value);
242  }
243 
244  protected function checkMin($value,$index = null){
245  return $this->nothing($this->min) || ($value >= $this->min);
246  }
247 
248  protected function checkMax($value,$index = null){
249  return $this->nothing($this->max) || ($value <= $this->max);
250  }
251  /**
252  * Required check.
253  * This check will only be called when the value is empty.
254  * If the required parameter has the value self::VALID_FUNC, then the checkValidFunc() function will be used to determine
255  * whether the value is correct (allowed empty).
256  * If the required parameter is an array, the first entry is seen as an reference to another widget (e.g. 'List__*__Name' -
257  * where the asterisk will be replaced with the current index). Its value will then be used for an comparison with an
258  * operator (2nd entry) and a value (3rd entry). If this comparison returns true, the value is required.
259  */
260  protected function checkRequired($value,$index = null){
261  if($this->required === self::VALID_FUNC) return $this->checkValidFunc($value,$index);
262  if(is_array($required = $this->required)){
263  $id = explode(self::CHILD_ID_SEPARATOR,str_replace('*',$index,array_shift($required)));
264  $widget = $this->_controller->widgets[$widget_id = array_shift($id)];
265  $ref = $widget->purge($this->request->complex(['widget',$widget_id]));
266  $ref = $this->_controller->view->convert ? $widget->convert($ref) : $widget->clientConvert($ref);
267  $required = \Rsi\Str::operator($id ? \Rsi\Record::get($ref,$id) : $ref,array_shift($required),array_shift($required),true);
268  }
269  return !$required;
270  }
271 
272  protected function checkValidFunc($value,$index = null){
273  return $this->validFunc ? call_user_func($this->validFunc,$value,$index) : true;
274  }
275  /**
276  * Check a value.
277  * @param mixed $value Value in standard, internal format.
278  * @param int $index Index for mutual children.
279  * @return string Error code (empty = OK).
280  */
281  public function check($value,$index = null){
282  if($this->nothing($value)) return $this->checkRequired($value,$index) ? null : 'required';
283  foreach(get_class_methods($this) as $method)
284  if(($method != 'checkRequired') && (strlen($method) > 5) && (substr($method,0,5) == 'check') && !call_user_func([$this,$method],$value,$index))
285  return lcfirst(substr($method,5));
286  return null; //OK
287  }
288 
289  protected function actionHelp(){
290  $type = self::HELP;
291  switch($this->request->type){
292  case 'widget': $type = self::WIDGET_HELP; break;
293  }
294  $this->request->result = [
295  'caption' => $this->trans->str($this->_controller->helpCaption,$this->tags),
296  'help' => $this->trans->str(($this->widgets[$this->request->id] ?? $this)->$type,$this->tags)
297  ];
298  }
299 
300  protected function setController($value){
301  $this->_controller = $value;
302  $this->init();
303  }
304 
305  protected function getDisplay(){
306  if($this->_display === null){
307  if(!$this->user->authorized($this->right,User::RIGHT_LEVEL_READ)) $this->_display = false;
308  else{
309  $this->_display = $this->config(self::DISPLAY,$this->_controller->display);
310  if(($this->_display > self::DISPLAY_READABLE) && !$this->user->authorized($this->right,User::RIGHT_LEVEL_WRITE))
311  $this->_display = self::DISPLAY_READABLE;
312  if($this->parent) $this->_display = min($this->_display,$this->parent->display);
313  }
314  }
315  return $this->_display;
316  }
317 
318  protected function getFred(){
319  return $this->_controller->fred;
320  }
321 
322  protected function getId(){
323  if($this->_id === null){
324  $this->_id = false;
325  if($this->parent){
326  foreach($this->parent->widgets as $id => $widget) if($this === $widget){
327  $this->_id = $this->parent->id . self::CHILD_ID_SEPARATOR . $id;
328  break;
329  }
330  }
331  elseif($this->_controller) foreach($this->_controller->widgets as $id => $widget) if($this === $widget){
332  $this->_id = $id;
333  break;
334  }
335  }
336  return $this->_id;
337  }
338 
339  protected function getParams(){
340  if($this->_params === null){
341  $this->_params = [];
342  if($params = $this->config(self::PARAMS)) foreach($params as $key => $value){
343  if(substr($key,0,1) == '@'){
344  $key = substr($key,1);
345  if(array_key_exists($value,$this->request->data)) $value = $this->request->data[$value];
346  else{
347  $widget = $this->_controller->widgets[$value];
348  $value = $widget->clientConvert($this->request->complex(['widget',$value]));
349  if($widget->check($value)) $value = false;
350  }
351  }
352  $this->_params[$key] = $value;
353  }
354  }
355  return $this->_params;
356  }
357 
358  protected function getReadable(){
359  return $this->display >= self::DISPLAY_READABLE;
360  }
361 
362  protected function getRight(){
363  if($right = $this->config(self::RIGHT)) return $right;
364  if($right !== false) return $this->_controller->right;
365  return null;
366  }
367 
368  protected function getTags(){
369  if($this->_tags === null){
370  $this->_tags = $this->config(self::TAGS,[]);
371  if($this->_config) foreach($this->_config as $key => $value)
372  $this->_tags[$key] = is_array($value) || is_object($value) ? (bool)$value : $value;
373  if(!$this->nothing($this->min)) $this->_tags[self::MIN] = $this->formatTag($this->min);
374  if(!$this->nothing($this->max)) $this->_tags[self::MAX] = $this->formatTag($this->max);
375  }
376  return $this->_tags;
377  }
378 
379  protected function getWidgets(){
380  if($this->_widgets === null){
381  $this->_widgets = $this->config(self::WIDGETS,[]);
382  foreach($this->_widgets as $widget){
383  $widget->parent = $this;
384  $widget->controller = $this->_controller;
385  }
386  }
387  return $this->_widgets;
388  }
389 
390  protected function getWriteable(){
391  return $this->user->authorized($this->right,User::RIGHT_LEVEL_WRITE);
392  }
393 
394  protected function _get($key){
395  return array_key_exists($key,$this->_config) ? $this->_config[$key] : $this->_controller->component($key);
396  }
397 
398 }
execute()
Execute an action on this widget.
Definition: Widget.php:224
__construct($config=null)
Definition: Widget.php:64
clientConvert($value)
Convert a value from client format to standard, internal format.
Definition: Widget.php:148
checkMin($value, $index=null)
Definition: Widget.php:244
checkValidFunc($value, $index=null)
Definition: Widget.php:272
dataConvert($value)
Convert an internal value before it is stored in the request data.
Definition: Widget.php:156
clientConfig()
Public configuration.
Definition: Widget.php:100
const DISPLAY_WRITEABLE
Show editable input element(s).
Definition: Widget.php:39
const DISPLAY_READABLE
Show input element(s), but read-only.
Definition: Widget.php:38
const VALID_FUNC
Specific validation function (called with value and index; returns true if OK).
Definition: Widget.php:30
convert($value)
Convert a value from user format to standard, internal format.
Definition: Widget.php:140
Basic object.
Definition: Thing.php:13
$_widgets
Optional child widgets.
Definition: Widget.php:56
expect($action)
Note if an action is expected.
Definition: Widget.php:206
configure($config)
Configure the object.
Definition: Thing.php:45
formatTag($value)
Format a tag value.
Definition: Widget.php:180
executeChild($id)
Execute an action on a child widget.
Definition: Widget.php:215
checkMax($value, $index=null)
Definition: Widget.php:248
const WIDGETS
Optional child widgets (key = name, value = widget object).
Definition: Widget.php:31
const HELP
Help string (translated).
Definition: Widget.php:15
clientFormat($value)
Format a value from standard, internal format to client format.
Definition: Widget.php:172
const TAGS
Extra tags for help, hint, and trailer.
Definition: Widget.php:21
const WIDGET_HELP
General widget help (translated).
Definition: Widget.php:22
const MIN
Minimum value.
Definition: Widget.php:26
$_config
Widget configuration (see constants).
Definition: Widget.php:58
const SECURITY_CHECKS_IGNORE
Security checks to ignore (array with checks to ignore, true =.
Definition: Widget.php:32
const RIGHT_LEVEL_WRITE
Definition: User.php:8
check($value, $index=null)
Check a value.
Definition: Widget.php:281
$_securityChecksIgnore
Security checks to ignore.
Definition: Widget.php:57
const PARAMS
Extra parameters (prefix the key with a &#39;@&#39; to point to the value of another widget).
Definition: Widget.php:17
nothing($value)
Check if a value is empty.
Definition: Widget.php:240
const RIGHT
Optional different right with respect to the controller. If the user has the right, then (s)he.
Definition: Widget.php:18
const HINT
Hint string (translated).
Definition: Widget.php:16
const DISPLAY_HIDDEN
Do not show (only hidden)
Definition: Widget.php:36
const RIGHT_LEVEL_READ
Definition: User.php:7
format($value)
Format a value from standard, internal format to user format.
Definition: Widget.php:164
publish($property, $visibility=self::READABLE)
Publish a property (or hide it again).
Definition: Thing.php:27
purgeBase($value)
Purge function that will be called first.
Definition: Widget.php:188
const REQUIRED
True for required. Set to self::VALID_FUNC to let the validation function handle this. Use.
Definition: Widget.php:28
config($key, $default=null)
Get single configuration value.
Definition: Widget.php:93
purge($value)
Purge a value.
Definition: Widget.php:196
checkRequired($value, $index=null)
Required check.
Definition: Widget.php:260
update($config)
Update the widget configuration.
Definition: Widget.php:83
const DISPLAY_MINIMAL
Show only value (without input element(s)).
Definition: Widget.php:37
const TRAILER
Trailer string (translated).
Definition: Widget.php:20
const MAX
Maximum value.
Definition: Widget.php:27
$parent
Optional parent widget.
Definition: Widget.php:52
const DISPLAY
Display type (see DISPLAY_* constants).
Definition: Widget.php:14