FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Table.php
Go to the documentation of this file.
1 <?php
2 
4 
5 use \Rsi\Fred\Controller;
6 use \Rsi\Fred\Controller\Provider;
7 
8 class Table extends Char{
9 
10  const PROVIDER = 'provider'; //!< Data provider.
11  const KEY = 'key'; //!< System key (hidden from user).
12  const SEQUENCE = 'sequence'; //!< Sequence key (higher = newer).
13  const CODE = 'code'; //!< User code (what the user sees; default same as key).
14  const DESCR = 'descr'; //!< Full description.
15  const PARENT_KEY = 'parentKey'; //!< Parent key column.
16  const COLUMNS = 'columns'; //!< Grid columns (key = column name, value = column config; code and description if empty).
17  const DETAILS = 'details'; //!< Columns to show in details, or one of the DETAILS_* constants.
18  const LINK = 'link'; //!< Link (URL) for a record ("[columnName]" is replaced by the value of that column, for that record).
19  const BATCH_SIZE = 'batchSize'; //!< Number of records to return per call.
20  const CACHE = 'cache'; //!< Allow local cache (limited number of records expected).
21  const DOWNLOAD_FORMATS = 'downloadFormats'; //!< Available download formats (key = description, value = function (use '*' to
22  // refer to this widget); default all downloadXxx functions; set to false for none).
23  const DOWNLOAD_NAME = 'downloadName'; //!< Filename (without extension) used for download.
24 
25  //column options
26  const ORDER = 'order'; //!< True if ordering the result by this column is allowed (default true if omitted).
27  const SEARCH = 'search'; //!< Allowed search methods for this column (see \\Rsi\\Fred\\Controller\\Provider::SEARCH_*
28  // constants).
29  const TOTAL = 'total'; //!< Show total for this column (see \\Rsi\\Fred\\Controller\\Provider::TOTAL_* constants; one or
30  // multiple in an array).
31  const GROUP = 'group'; //!< Show grouped data for this column (array with keys 'group' = column to group by - defaults to
32  // first column; 'column' = column to group the value for - defaults to current column; 'total' = see
33  // \\Rsi\\Fred\\Controller\\Provider::TOTAL_* constants; 'limit' = number of results: positive = top-x, negative = bottom-x,
34  // 0 = all, sorted by 'group' column).
35  const FORMAT = 'format'; //!< Formatting method.
36  const FORMAT_PARAMS = 'formatParams'; //!< Extra parameters for the formatting method.
37  const VISIBLE = 'visible'; //!< Default visibility (see VISIBLE_* and DOWNLOAD_* constants).
38 
39  const VISIBLE_DEFAULT = 1; //!< Show by deault (may be unselected by the user).
40  const VISIBLE_OPTIONAL = 2; //!< Dont't show by default, but make it selectable for the user.
41  const VISIBLE_NEVER = 4; //!< Do not show (also not optional).
42  const DOWNLOAD_DEFAULT = 16; //!< Download by default (may be unselected by the user).
43  const DOWNLOAD_VISIBLE = 32; //!< Download when visible.
44  const DOWNLOAD_OPTIONAL = 64; //!< Do not download by default, but make it selectable for the user.
45 
46  const DETAILS_ALL = 'all'; //!< Show all columns in details.
47  const DETAILS_EXTRA = 'extra'; //!< Show all columns not currently visible.
48 
49  public $provider = null;
50  public $sequence = null;
51  public $sequenceMessage = '{[count<0]!}{[count>0]{[count|number()]}}'; //!< Message for new records (available tags: count,
52  //id, and caption; empty result = no message / no refresh; '!' = auto-refresh). Note: count = -1 means: latest record gone.
53  public $details = null;
54  public $link = null;
55  public $batchSize = 100;
56  public $cache = false;
57  public $pdfWriter = null;
58  public $downloadName = 'data';
59  public $downloadCookieKey = 'fred-controller-table-downloading';
60  public $downloadBusyMessage = null;
61  public $downloadTimeout = null;
62 
63  protected $_key = null;
64  protected $_code = null;
65  protected $_descr = null;
66  protected $_parentKey = null;
67  protected $_search = null;
68  protected $_columns = null;
69  protected $_downloadFormats = null;
70 
71  protected function init(){
72  parent::init();
73  $this->publish(['key','descr','parentKey','search']);
74  }
75 
76  public function columnCaption($column){
77  return array_key_exists(self::CAPTION,$config = $this->columns[$column] ?: [])
78  ? $this->trans->str($config[self::CAPTION])
79  : $this->trans->id($column);
80  }
81 
82  public function sessionKey($key){
83  return array_merge(['widget','table',$this->id ?: $this->provider->id],is_array($key) ? $key : [$key]);
84  }
85 
86  public function clientConfig(){
87  $session = $this->_controller->session;
88  $columns = [];
89  foreach($this->columns as $column => $config){
90  if($caption = $this->columnCaption($column)) $config[self::CAPTION] = $caption;
91  $columns[$column] = $config;
92  }
93  $config = array_merge(
94  parent::clientConfig(),
95  $this->get([self::CODE,self::DESCR,self::PARENT_KEY,self::DETAILS,self::LINK,self::SEARCH]),
96  [self::COLUMNS => $columns]
97  );
98  if($search = $session->get($this->sessionKey('search'))) $config[self::SEARCH] = $search;
99  if($order = $session->get($this->sessionKey('order')) ?: $this->provider->order) $config['defaultOrder'] = $order;
100  if($this->cache) $config[self::CACHE] = true;
101  if($this->downloadFormats) $config[self::DOWNLOAD_FORMATS] = array_keys($this->downloadFormats);
102  $config['downloadCookieKey'] = $this->downloadCookieKey;
103  if($this->downloadBusyMessage) $config['downloadBusyMessage'] = $this->trans->str($this->downloadBusyMessage);
104  if($this->downloadTimeout) $config = $this->downloadTimeout;
105  return $config;
106  }
107 
108  public function convert($value){
109  return $this->provider->trans($this->key,$this->code,$value,$this->params);
110  }
111 
112  public function clientConvert($value){
113  return $this->convert($value);
114  }
115 
116  public function format($value){
117  return $this->provider->trans($this->code,$this->key,$value,$this->params);
118  }
119 
120  public function clientFormat($value){
121  return $this->format($value);
122  }
123 
124  protected function checkRange($value){
125  return $this->provider->exists($this->key,$value,$this->params);
126  }
127 
128  protected function download($records,$format,$ext = null,$content_type = null){
129  $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
130  $sheet = $spreadsheet->setActiveSheetIndex(0)->setTitle($this->downloadName);
131  if(!($columns = $this->request->record('columns')))
132  $this->fred->externalError('Invalid columns',['columns' => $columns]);
133  $columnChars = [];
134  foreach(array_values($columns) as $index => $column){
135  if(!array_key_exists($column,$this->columns)) $this->fred->externalError('Unknown column',['column' => $column]);
136  $char = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($index + 1);
137  $sheet->getStyle($cell = $char . 1)->getFont()->setBold(true);
138  if($caption = $this->columnCaption($column)) $sheet->setCellValue($cell,$caption);
139  foreach($records as $index => $record) $sheet->setCellValue($char . ($index + 2),$record[$column] ?? null);
140  $sheet->getColumnDimension($char)->setAutoSize(true);
141  }
142  \Rsi\Http::downloadHeaders($this->downloadName . '.' . ($ext ?: strtolower($format)),$content_type);
143  \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet,$format)->save('php://output');
144  }
145 
146  protected function downloadExcel($records){
147  $this->download($records,'Xls');
148  }
149 
150  protected function downloadXLSX($records){
151  $this->download($records,'Xlsx');
152  }
153 
154  protected function downloadCSV($records){
155  $this->download($records,'Csv');
156  }
157 
158  protected function downloadPDF($records){
159  $this->download($records,$this->pdfWriter,'pdf');
160  }
161 
162  protected function downloadXML($records){
163  $root = new \SimpleXmlElement('<records />');
164  foreach($records as $record){
165  $node = $root->addChild('record');
166  foreach($record as $key => $value) $node->addChild($key,htmlspecialchars($value));
167  }
168  \Rsi\Http::downloadHeaders($this->downloadName . '.xml');
169  print($root->asXML());
170  }
171 
172  protected function downloadJSON($records){
173  \Rsi\Http::downloadHeaders($this->downloadName . '.json');
174  print(json_encode($records));
175  }
176 
177  public function searchParams(&$search,&$order){
178  $search = $order = [];
179  if(is_array($param = $this->request->complex('search'))) foreach($param as $column => $values)
180  if(
181  array_key_exists($column,$this->columns) && is_array($values) &&
182  ($types = \Rsi\Record::get($this->columns[$column],self::SEARCH,[Provider::SEARCH_LIKE]))
183  ){
184  foreach($values as $type => $value) if(!in_array($type,$types))
185  $this->fred->externalError('Invalid search type',['class' => get_called_class()] + compact('column','type'));
186  $search[$column] = $values;
187  }
188  if(is_array($param = $this->request->complex('order'))) foreach($param as $column => $type)
189  if(array_key_exists($column,$this->columns) && \Rsi\Record::get($this->columns[$column],self::ORDER,true))
190  $order[$column] = (int)$type;
191  }
192 
193  protected function actionRecords(){
194  $this->searchParams($search,$order);
195  $session = $this->_controller->session;
196  $session->set($this->sessionKey('search'),$search);
197  $session->set($this->sessionKey('order'),$order);
198  if($this->parentKey) $search[$this->parentKey] = [Provider::SEARCH_EXACT => $this->convert($this->request->parent)];
199  $offset = max(0,(int)$this->request->offset);
200  $limit = min(max(0,(int)$this->request->limit) ?: $this->batchSize,$this->batchSize);
201  $records = $this->provider->search($search,$order,$params = $this->params,$offset,$limit);
202  if($this->parentKey) foreach($records as $index => $record) $records[$index][$this->parentKey] =
203  ($value = $record[$this->key] ?? null) && $this->provider->exists($this->parentKey,$value,$params);
204  $this->request->result = [
205  'records' => $records,
206  'eof' => count($records) < $limit
207  ];
208  }
209 
210  protected function actionTotals(){
211  $this->searchParams($search,$order);
212  $this->request->result = $totals = [];
213  foreach($this->columns as $column => $config)
214  if($total = \Rsi\Record::get($config,self::TOTAL)) $totals[$column] = $total;
215  if($this->sequence) $totals[$this->sequence] =
216  array_unique(array_merge(\Rsi\Record::explode($totals[$this->sequence] ?? null),[Controller::TOTAL_MAX]));
217  if($totals){
218  $this->request->result['totals'] = $totals = $this->provider->totals($search,$totals,$this->params);
219  if($this->sequence) $this->request->result['latest'] = $totals[$this->sequence][Controller::TOTAL_MAX];
220  }
221  }
222 
223  protected function actionGroup(){
224  if($config = \Rsi\Record::get($this->columns,[$column = $this->request->column,self::GROUP])){
225  $this->searchParams($search,$order);
226  $this->request->result = $this->provider->group(
227  $search,
228  $config['group'] ?? \Rsi\Record::key($this->columns),
229  $config['column'] ?? $column,
230  $config['total'],
231  $config['limit'] ?? null
232  );
233  }
234  }
235 
236  protected function actionDownload(){
237  if($method = \Rsi\Record::get($this->downloadFormats,$this->request->format)){
238  $this->request->cookie($this->downloadCookieKey,null,-1,['httponly' => false]);
239  $this->searchParams($search,$order);
240  call_user_func($method,$this->provider->search($search,$order,$this->params));
241  $this->fred->halt();
242  }
243  }
244 
245  protected function actionNew(){
246  if($this->sequence){
247  $this->searchParams($search,$order);
248  if(!array_key_exists($this->sequence,$search)) $search[$this->sequence] = [];
249  if($latest = $this->request->latest) $search[$this->sequence][Provider::SEARCH_FROM] = $latest;
250  $count = \Rsi\Record::get(
251  $this->provider->totals($search,[$this->sequence => Controller::TOTAL_COUNT],$this->params),
252  [$this->sequence,Controller::TOTAL_COUNT]
253  );
254  if($latest) $count--;
255  if($this->request->result['count'] = $count) $this->request->result['message'] = $this->trans->str(
256  $this->sequenceMessage,
257  ['count' => $count,'id' => $this->id,'caption' => $this->caption()]
258  );
259  }
260  }
261 
262  protected function getCode(){
263  return $this->config(self::CODE) ?: $this->_key;
264  }
265 
266  protected function getColumns(){
267  if(!$this->_columns){
268  $this->_columns = $this->config(self::COLUMNS);
269  if(!$this->_columns){
270  $this->_columns = [$this->code => null];
271  if($this->descr) $this->_columns[$this->descr] = null;
272  }
273  }
274  return $this->_columns;
275  }
276 
277  protected function getDownloadFormats(){
278  if($this->_downloadFormats === null){
279  $this->_downloadFormats = $this->config(self::DOWNLOAD_FORMATS);
280  if($this->_downloadFormats === null){
281  $this->_downloadFormats = [];
282  foreach(get_class_methods($this) as $method) if((strlen($method) > 8) && (substr($method,0,8) == 'download'))
283  $this->_downloadFormats[substr($method,8)] = [$this,$method];
284  }
285  elseif($this->_downloadFormats) foreach($this->_downloadFormats as &$function)
286  if(is_array($function) && ($function[0] == '*')) $function[0] = $this;
287  unset($function);
288  }
290  }
291 
292 }
Rsi\Fred\Controller\Widget\Table\VISIBLE_OPTIONAL
const VISIBLE_OPTIONAL
Dont't show by default, but make it selectable for the user.
Definition: Table.php:40
Rsi\Fred\Controller\Provider\SEARCH_EXACT
const SEARCH_EXACT
Search for values identical to the reference value (this may also be an array with.
Definition: Provider.php:9
Rsi\Fred\Controller\Widget\Table\LINK
const LINK
Link (URL) for a record ("[columnName]" is replaced by the value of that column, for that record).
Definition: Table.php:18
Rsi\Fred\Controller\Widget\Table\clientFormat
clientFormat($value)
Format a value from standard, internal format to client format.
Definition: Table.php:120
Rsi\Fred\Controller\Widget\Table\TOTAL
const TOTAL
Show total for this column (see \Rsi\Fred\Controller\Provider::TOTAL_* constants; one or.
Definition: Table.php:29
Rsi
Rsi\Fred\Controller\Widget\Table\BATCH_SIZE
const BATCH_SIZE
Number of records to return per call.
Definition: Table.php:19
Rsi\Fred\Controller\Widget\Table\$_key
$_key
Definition: Table.php:63
Rsi\Fred\Controller\Widget\Table\DOWNLOAD_DEFAULT
const DOWNLOAD_DEFAULT
Download by default (may be unselected by the user).
Definition: Table.php:42
Rsi\Fred\Controller\Widget\caption
caption($id=null)
Definition: Widget.php:133
Rsi\Fred\Controller\Widget\Table\DOWNLOAD_OPTIONAL
const DOWNLOAD_OPTIONAL
Do not download by default, but make it selectable for the user.
Definition: Table.php:44
Rsi\Fred\Controller\Widget\Table
Definition: Table.php:8
Rsi\Fred\Controller\Widget\Table\$batchSize
$batchSize
Definition: Table.php:55
Rsi\Fred\Controller\Widget\Table\FORMAT_PARAMS
const FORMAT_PARAMS
Extra parameters for the formatting method.
Definition: Table.php:36
Rsi\Fred\Controller\Widget\Table\COLUMNS
const COLUMNS
Grid columns (key = column name, value = column config; code and description if empty).
Definition: Table.php:16
Rsi\Fred\Controller\Widget\Table\format
format($value)
Format a value from standard, internal format to user format.
Definition: Table.php:116
Rsi\Fred\Controller\Widget\Table\DOWNLOAD_VISIBLE
const DOWNLOAD_VISIBLE
Download when visible.
Definition: Table.php:43
Rsi\Fred\Controller\Widget\Table\convert
convert($value)
Convert a value from user format to standard, internal format.
Definition: Table.php:108
Rsi\Fred\Controller\Widget\Table\actionRecords
actionRecords()
Definition: Table.php:193
Rsi\Fred\Controller\TOTAL_MAX
const TOTAL_MAX
Definition: Controller.php:11
Rsi\Fred\Controller\Widget\Table\actionGroup
actionGroup()
Definition: Table.php:223
Rsi\Fred\Controller\Widget\Table\KEY
const KEY
System key (hidden from user).
Definition: Table.php:11
Rsi\Fred\Controller\Widget\Table\searchParams
searchParams(&$search, &$order)
Definition: Table.php:177
Rsi\Fred\Controller\Widget\Table\actionDownload
actionDownload()
Definition: Table.php:236
Rsi\Fred\Controller\Widget\CAPTION
const CAPTION
Definition: Widget.php:12
Rsi\Fred\Controller\Widget\Table\downloadCSV
downloadCSV($records)
Definition: Table.php:154
Rsi\Fred\Controller\Widget\Table\download
download($records, $format, $ext=null, $content_type=null)
Definition: Table.php:128
Rsi\Fred\Controller\Widget\Table\downloadXLSX
downloadXLSX($records)
Definition: Table.php:150
Rsi\Fred\Controller\Widget\Table\CODE
const CODE
User code (what the user sees; default same as key).
Definition: Table.php:13
Rsi\Fred\Controller\Widget\Table\getCode
getCode()
Definition: Table.php:262
Rsi\Fred\Controller\Widget\Char
Definition: Char.php:5
Rsi\Fred\Controller\Widget\Table\PARENT_KEY
const PARENT_KEY
Parent key column.
Definition: Table.php:15
Rsi\Fred\Controller\Widget\Table\$_columns
$_columns
Definition: Table.php:68
Rsi\Fred\Controller\Widget\Table\clientConfig
clientConfig()
Public configuration.
Definition: Table.php:86
Rsi\Fred\Controller\Provider\SEARCH_LIKE
const SEARCH_LIKE
Search for values that match reference value with wildcard (? = 1 char, * = multiple chars).
Definition: Provider.php:12
Rsi\Fred\Controller\Widget\Table\columnCaption
columnCaption($column)
Definition: Table.php:76
Rsi\Fred\Controller\Widget\Table\DOWNLOAD_NAME
const DOWNLOAD_NAME
Filename (without extension) used for download.
Definition: Table.php:23
Rsi\Fred\Controller\Widget\Table\DESCR
const DESCR
Full description.
Definition: Table.php:14
Rsi\Fred\Controller\Widget\Table\init
init()
Definition: Table.php:71
Rsi\Fred\Controller\Widget\Table\$_downloadFormats
$_downloadFormats
Definition: Table.php:69
Rsi\Fred\Controller\Widget\Table\downloadExcel
downloadExcel($records)
Definition: Table.php:146
Rsi\Fred\Controller\Widget\Table\$_code
$_code
Definition: Table.php:64
Rsi\Fred\Controller\Provider\SEARCH_FROM
const SEARCH_FROM
Search for values larger than the reference value.
Definition: Provider.php:7
Rsi\Fred\Controller\Widget\Table\CACHE
const CACHE
Allow local cache (limited number of records expected).
Definition: Table.php:20
Rsi\Fred\Controller\Widget\Table\$pdfWriter
$pdfWriter
Definition: Table.php:57
Rsi\Fred\Controller\Widget\Table\$downloadTimeout
$downloadTimeout
Definition: Table.php:61
Rsi\Fred\Controller\Widget\Table\$_descr
$_descr
Definition: Table.php:65
Rsi\Fred\Controller\Widget\Table\actionNew
actionNew()
Definition: Table.php:245
Rsi\Fred\Controller\Widget\Table\VISIBLE_NEVER
const VISIBLE_NEVER
Do not show (also not optional).
Definition: Table.php:41
Rsi\Fred\Controller\Widget\Table\$details
$details
Definition: Table.php:53
Rsi\Fred\Controller\Widget\Table\$downloadName
$downloadName
Definition: Table.php:58
Rsi\Fred\Controller\Widget\Table\actionTotals
actionTotals()
Definition: Table.php:210
Rsi\Fred\Controller\Widget\Table\checkRange
checkRange($value)
Definition: Table.php:124
Rsi\Fred\Controller\Widget\Table\VISIBLE
const VISIBLE
Default visibility (see VISIBLE_* and DOWNLOAD_* constants).
Definition: Table.php:37
Rsi\Fred\Controller\Widget\Table\GROUP
const GROUP
Show grouped data for this column (array with keys 'group' = column to group by - defaults to.
Definition: Table.php:31
Rsi\Fred\Controller\Widget\config
config($key, $default=null)
Get single configuration value.
Definition: Widget.php:98
Rsi\Fred\Controller\Widget\Table\DETAILS_ALL
const DETAILS_ALL
Show all columns in details.
Definition: Table.php:46
Rsi\Fred\Controller\TOTAL_COUNT
const TOTAL_COUNT
Definition: Controller.php:7
Rsi\Fred\Controller\Widget\Table\$sequenceMessage
$sequenceMessage
Message for new records (available tags: count,.
Definition: Table.php:51
Rsi\Fred\Controller\Widget\Table\$cache
$cache
Definition: Table.php:56
Rsi\Fred\Controller\Widget\Table\downloadJSON
downloadJSON($records)
Definition: Table.php:172
Rsi\Fred\Controller\Widget
Definition: Char.php:3
Rsi\Fred\Controller\Widget\Table\getDownloadFormats
getDownloadFormats()
Definition: Table.php:277
Rsi\Thing\publish
publish($property, $visibility=self::READABLE)
Publish a property (or hide it again).
Definition: Thing.php:28
Rsi\Fred\Controller\Widget\Table\$_search
$_search
Definition: Table.php:67
Rsi\Fred\Controller\Widget\Table\downloadPDF
downloadPDF($records)
Definition: Table.php:158
Rsi\Fred\Controller\Widget\Table\FORMAT
const FORMAT
Formatting method.
Definition: Table.php:35
Rsi\Fred\Controller\Widget\Table\SEARCH
const SEARCH
Allowed search methods for this column (see \Rsi\Fred\Controller\Provider::SEARCH_*.
Definition: Table.php:27
Rsi\Fred\Controller\Widget\Table\sessionKey
sessionKey($key)
Definition: Table.php:82
Rsi\Fred\Controller\Widget\Table\$downloadBusyMessage
$downloadBusyMessage
Definition: Table.php:60
Rsi\Fred\Controller\Widget\Table\$link
$link
Definition: Table.php:54
Rsi\Fred\Controller\Widget\Table\$_parentKey
$_parentKey
Definition: Table.php:66
Rsi\Fred\Controller\Widget\Table\clientConvert
clientConvert($value)
Convert a value from client format to standard, internal format.
Definition: Table.php:112
Rsi\Fred\Controller\Widget\Table\PROVIDER
const PROVIDER
Data provider.
Definition: Table.php:10
Rsi\Fred\Controller\Widget\Table\$provider
$provider
Definition: Table.php:49
Rsi\Fred\Controller\Widget\Table\$sequence
$sequence
Definition: Table.php:50
Rsi\Fred\Controller\Widget\Table\SEQUENCE
const SEQUENCE
Sequence key (higher = newer).
Definition: Table.php:12
Rsi\Fred\Controller\Widget\Table\downloadXML
downloadXML($records)
Definition: Table.php:162
Rsi\Fred\Controller\Widget\Table\DOWNLOAD_FORMATS
const DOWNLOAD_FORMATS
Available download formats (key = description, value = function (use '*' to.
Definition: Table.php:21
Rsi\Fred\Controller\Widget\Table\ORDER
const ORDER
True if ordering the result by this column is allowed (default true if omitted).
Definition: Table.php:26
Rsi\Fred\Controller\Widget\Table\getColumns
getColumns()
Definition: Table.php:266
Rsi\Fred\Controller\Widget\Table\$downloadCookieKey
$downloadCookieKey
Definition: Table.php:59
Rsi\Fred\Controller\Widget\Table\VISIBLE_DEFAULT
const VISIBLE_DEFAULT
Show by deault (may be unselected by the user).
Definition: Table.php:39
Rsi\Fred\Controller\Widget\Table\DETAILS
const DETAILS
Columns to show in details, or one of the DETAILS_* constants.
Definition: Table.php:17
Rsi\Fred\Controller\Widget\Table\DETAILS_EXTRA
const DETAILS_EXTRA
Show all columns not currently visible.
Definition: Table.php:47