-
Notifications
You must be signed in to change notification settings - Fork 41
/
Copy pathAbstractBaseType.php
527 lines (480 loc) · 16.6 KB
/
AbstractBaseType.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
<?php
namespace dokuwiki\plugin\struct\types;
use dokuwiki\plugin\struct\meta\Column;
use dokuwiki\plugin\struct\meta\QueryBuilder;
use dokuwiki\plugin\struct\meta\QueryBuilderWhere;
use dokuwiki\plugin\struct\meta\StructException;
use dokuwiki\plugin\struct\meta\TranslationUtilities;
use dokuwiki\plugin\struct\meta\ValidationException;
use dokuwiki\plugin\struct\meta\Value;
/**
* Class AbstractBaseType
*
* This class represents a basic type that can be configured to be used in a Schema. It is the main
* part of a column definition as defined in meta\Column
*
* This defines also how the content of the coulmn will be entered and formatted.
*
* @package dokuwiki\plugin\struct\types
* @see Column
*/
abstract class AbstractBaseType {
use TranslationUtilities;
/**
* @var array current config
*/
protected $config = array();
/**
* @var array config keys that should not be cleaned despite not being in $config
*/
protected $keepconfig = array('label', 'hint', 'visibility', 'default');
/**
* @var string label for the field
*/
protected $label = '';
/**
* @var bool is this a multivalue field?
*/
protected $ismulti = false;
/**
* @var int the type ID
*/
protected $tid = 0;
/**
* @var null|Column the column context this type is part of
*/
protected $context = null;
/**
* @var \DokuWiki_Plugin
*/
protected $hlp = null;
/**
* AbstractBaseType constructor.
* @param array|null $config The configuration, might be null if nothing saved, yet
* @param string $label The label for this field (empty for new definitions=
* @param bool $ismulti Should this field accept multiple values?
* @param int $tid The id of this type if it has been saved, yet
*/
public function __construct($config = null, $label = '', $ismulti = false, $tid = 0) {
// general config options
$baseconfig = array(
'visibility' => array(
'inpage' => true,
'ineditor' => true,
),
'default' => '',
);
// use previously saved configuration, ignoring all keys that are not supposed to be here
if(!is_null($config)) {
$this->mergeConfig($config, $this->config);
}
$this->initTransConfig();
$this->config = array_merge($baseconfig, $this->config);
$this->label = $label;
$this->ismulti = (bool) $ismulti;
$this->tid = $tid;
}
/**
* Merge the current config with the base config of the type
*
* Ignores all keys that are not supposed to be there. Recurses into sub keys
*
* @param array $current Current configuration
* @param array $config Base Type configuration
*/
protected function mergeConfig($current, &$config) {
foreach($current as $key => $value) {
if(isset($config[$key]) || in_array($key, $this->keepconfig)) {
if(is_array($config[$key])) {
$this->mergeConfig($value, $config[$key]);
} else {
$config[$key] = $value;
}
}
}
}
/**
* Returns data as associative array
*
* @return array
*/
public function getAsEntry() {
return array(
'config' => json_encode($this->config),
'label' => $this->label,
'ismulti' => $this->ismulti,
'class' => $this->getClass()
);
}
/**
* The class name of this type (no namespace)
* @return string
*/
public function getClass() {
$class = get_class($this);
return substr($class, strrpos($class, "\\") + 1);
}
/**
* Return the current configuration for this type
*
* @return array
*/
public function getConfig() {
return $this->config;
}
/**
* @return boolean
*/
public function isMulti() {
return $this->ismulti;
}
/**
* @return string
*/
public function getLabel() {
return $this->label;
}
/**
* Returns the translated label for this type
*
* Uses the current language as determined by $conf['lang']. Falls back to english
* and then to the type label
*
* @return string
*/
public function getTranslatedLabel() {
return $this->getTranslatedKey('label', $this->label);
}
/**
* Returns the translated hint for this type
*
* Uses the current language as determined by $conf['lang']. Falls back to english.
* Returns empty string if no hint is configured
*
* @return string
*/
public function getTranslatedHint() {
return $this->getTranslatedKey('hint', '');
}
/**
* @return int
*/
public function getTid() {
return $this->tid;
}
/**
* @throws StructException
* @return Column
*/
public function getContext() {
if(is_null($this->context))
throw new StructException('Empty column context requested. Type was probably initialized outside of Schema.');
return $this->context;
}
/**
* @param Column $context
*/
public function setContext($context) {
$this->context = $context;
}
/**
* @return bool
*/
public function isVisibleInEditor() {
return $this->config['visibility']['ineditor'];
}
/**
* @return bool
*/
public function isVisibleInPage() {
return $this->config['visibility']['inpage'];
}
/**
* @return string
*/
public function getDefaultValue() {
return $this->config['default'];
}
/**
* Split a single value into multiple values
*
* This function is called on saving data when only a single value instead of an array
* was submitted.
*
* Types implementing their own @see multiValueEditor() will probably want to override this
*
* @param string $value
* @return array
*/
public function splitValues($value) {
return array_map('trim', explode(',', $value));
}
/**
* Return the editor to edit multiple values
*
* Types can override this to provide a better alternative than multiple entry fields
*
* @param string $name the form base name where this has to be stored
* @param string[] $rawvalues the current values
* @param string $htmlID a unique id to be referenced by the label
* @return string html
*/
public function multiValueEditor($name, $rawvalues, $htmlID) {
$html = '';
foreach($rawvalues as $value) {
$html .= '<div class="multiwrap">';
$html .= $this->valueEditor($name . '[]', $value, '');
$html .= '</div>';
}
// empty field to add
$html .= '<div class="newtemplate">';
$html .= '<div class="multiwrap">';
$html .= $this->valueEditor($name . '[]', '', $htmlID);
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* Return the editor to edit a single value
*
* @param string $name the form name where this has to be stored
* @param string $rawvalue the current value
* @param string $htmlID a unique id to be referenced by the label
*
* @return string html
*/
public function valueEditor($name, $rawvalue, $htmlID) {
$class = 'struct_' . strtolower($this->getClass());
// support the autocomplete configurations out of the box
if(isset($this->config['autocomplete']['maxresult']) && $this->config['autocomplete']['maxresult']) {
$class .= ' struct_autocomplete';
}
$params = array(
'name' => $name,
'value' => $rawvalue,
'class' => $class,
'id' => $htmlID
);
$attributes = buildAttributes($params, true);
return "<input $attributes>";
}
/**
* Output the stored data
*
* @param string|int $value the value stored in the database
* @param \Doku_Renderer $R the renderer currently used to render the data
* @param string $mode The mode the output is rendered in (eg. XHTML)
* @return bool true if $mode could be satisfied
*/
public function renderValue($value, \Doku_Renderer $R, $mode) {
$value = $this->displayValue($value);
$R->cdata($value);
return true;
}
/**
* format and return the data
*
* @param int[]|string[] $values the values stored in the database
* @param \Doku_Renderer $R the renderer currently used to render the data
* @param string $mode The mode the output is rendered in (eg. XHTML)
* @return bool true if $mode could be satisfied
*/
public function renderMultiValue($values, \Doku_Renderer $R, $mode) {
$len = count($values);
for($i = 0; $i < $len; $i++) {
$this->renderValue($values[$i], $R, $mode);
if($i < $len - 1) {
$R->cdata(', ');
}
}
return true;
}
/**
* Render a link in a struct cloud. This should be good for most types, but can be overwritten if necessary.
*
* @param string|int $value the value stored in the database
* @param \Doku_Renderer $R the renderer currently used to render the data
* @param string $mode The mode the output is rendered in (eg. XHTML)
* @param string $page the target to which should be linked
* @param string $filter the filter to apply to the aggregations on $page
* @param int $weight the scaled weight of the item. Will already be implemented as css font-size on the outside container
*/
public function renderTagCloudLink($value, \Doku_Renderer $R, $mode, $page, $filter, $weight) {
$R->internallink("$page?$filter", $this->displayValue($value));
}
/**
* This function is used to modify an aggregation query to add a filter
* for the given column matching the given value. A type should add at
* least a filter here but could do additional things like joining more
* tables needed to handle more complex filters
*
* Important: $value might be an array. If so, the filter should check against
* all provided values ORed together
*
* @param QueryBuilderWhere $add The where clause where statements can be added
* @param string $tablealias The table the currently saved value(s) are stored in
* @param string $colname The column name on above table to use in the SQL
* @param string $comp The SQL comparator (LIKE, NOT LIKE, =, !=, etc)
* @param string|string[] $value this is the user supplied value to compare against. might be multiple
* @param string $op the logical operator this filter should use (AND|OR)
*/
public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) {
/** @var QueryBuilderWhere $add Where additionional queries are added to*/
if(is_array($value)) {
$add = $add->where($op); // sub where group
$op = 'OR';
}
foreach((array) $value as $item) {
$pl = $add->getQB()->addValue($item);
$add->where($op, "$tablealias.$colname $comp $pl");
}
}
/**
* Add the proper selection for this type to the current Query
*
* The default implementation here should be good for nearly all types, it simply
* passes the given parameters to the query builder. But type may do more fancy
* stuff here, eg. join more tables or select multiple values and combine them to
* JSON. If you do, be sure implement a fitting rawValue() method.
*
* The passed $tablealias.$columnname might be a data_* table (referencing a single
* row) or a multi_* table (referencing multiple rows). In the latter case the
* multi table has already been joined with the proper conditions.
*
* You may assume a column alias named 'PID' to be available, should you need the
* current page context for a join or sub select.
*
* @param QueryBuilder $QB
* @param string $tablealias The table the currently saved value(s) are stored in
* @param string $colname The column name on above table
* @param string $alias The added selection *has* to use this column alias
*/
public function select(QueryBuilder $QB, $tablealias, $colname, $alias) {
$QB->addSelectColumn($tablealias, $colname, $alias);
}
/**
* Sort results by this type
*
* The default implementation should be good for nearly all types. However some
* types may need to do proper SQLite type casting to have the right order.
*
* Generally if you implemented @see select() you probably want to implement this,
* too.
*
* @param QueryBuilder $QB
* @param string $tablealias The table the currently saved value is stored in
* @param string $colname The column name on above table (always single column!)
* @param string $order either ASC or DESC
*/
public function sort(QueryBuilder $QB, $tablealias, $colname, $order) {
$QB->addOrderBy("$tablealias.$colname $order");
}
/**
* Get the string by which to sort values of this type
*
* This implementation is designed to work both as registered function in sqlite
* and to provide a string to be used in sorting values of this type in PHP.
*
* @param string|Value $value The string by which the types would usually be sorted
*
* @return string
*/
public function getSortString($value) {
if (is_string($value)) {
return $value;
}
$display = $value->getDisplayValue();
if (is_array($display)) {
return blank($display[0]) ? "" : $display[0];
}
return $display;
}
/**
* This allows types to apply a transformation to the value read by select()
*
* The returned value should always be a single, non-complex string. In general
* it is the identifier a type stores in the database.
*
* This value will be used wherever the raw saved data is needed for comparisons.
* The default implementations of renderValue() and valueEditor() will call this
* function as well.
*
* @param string $value The value as returned by select()
* @return string The value as saved in the database
*/
public function rawValue($value) {
return $value;
}
/**
* This is called when a single string is needed to represent this Type's current
* value as a single (non-HTML) string. Eg. in a dropdown or in autocompletion.
*
* @param string $value
* @return string
*/
public function displayValue($value) {
return $this->rawValue($value);
}
/**
* This is the value to be used as argument to a filter for another column.
*
* In a sense this is the counterpart to the @see filter() function
*
* @param string $value
*
* @return string
*/
public function compareValue($value) {
return $this->rawValue($value);
}
/**
* Validate and optionally clean a single value
*
* This function needs to throw a validation exception when validation fails.
* The exception message will be prefixed by the appropriate field on output
*
* The function should return the value as it should be saved later on.
*
* @param string|int $rawvalue
* @return int|string the cleaned value
* @throws ValidationException
*/
public function validate($rawvalue) {
return trim($rawvalue);
}
/**
* Overwrite to handle Ajax requests
*
* A call to DOKU_BASE/lib/exe/ajax.php?call=plugin_struct&column=schema.name will
* be redirected to this function on a fully initialized type. The result is
* JSON encoded and returned to the caller. Access additional parameter via $INPUT
* as usual
*
* @throws StructException when something goes wrong
* @return mixed
*/
public function handleAjax() {
throw new StructException('not implemented');
}
/**
* Convenience method to access plugin language strings
*
* @param string $string
* @return string
*/
public function getLang($string) {
if(is_null($this->hlp)) $this->hlp = plugin_load('helper', 'struct');
return $this->hlp->getLang($string);
}
/**
* With what comparator should dynamic filters filter this type?
*
* This default does a LIKE operation
*
* @see Search::$COMPARATORS
* @return string
*/
public function getDefaultComparator() {
return '*~';
}
}