/
class.datasource.php
559 lines (489 loc) · 19.3 KB
/
class.datasource.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
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
<?php
/**
* @package toolkit
*/
/**
* The Datasource class provides functionality to mainly process any parameters
* that the fields will use in filters find the relevant Entries and return these Entries
* data as XML so that XSLT can be applied on it to create your website. In Symphony,
* there are four Datasource types provided, Section, Author, Navigation and Dynamic
* XML. Section is the mostly commonly used Datasource, which allows the filtering
* and searching for Entries in a Section to be returned as XML. Navigation datasources
* expose the Symphony Navigation structure of the Pages in the installation. Authors
* expose the Symphony Authors that are registered as users of the backend. Finally,
* the Dynamic XML datasource allows XML pages to be retrieved. This is especially
* helpful for working with Restful XML API's. Datasources are saved through the
* Symphony backend, which uses a Datasource template defined in
* `TEMPLATE . /datasource.tpl`.
*/
class Datasource
{
/**
* A constant that represents if this filter is an AND filter in which
* an Entry must match all these filters. This filter is triggered when
* the filter string contains a ` + `.
*
* @since Symphony 2.3.2
* @var integer
*/
const FILTER_AND = 1;
/**
* A constant that represents if this filter is an OR filter in which an
* entry can match any or all of these filters
*
* @since Symphony 2.3.2
* @var integer
*/
const FILTER_OR = 2;
/**
* Holds all the environment variables which include parameters set by
* other Datasources or Events.
* @var array
*/
protected $_env = array();
/**
* If true, this datasource only will be outputting parameters from the
* Entries, and no actual content.
* @var boolean
*/
protected $_param_output_only;
/**
* An array of datasource dependancies. These are datasources that must
* run first for this datasource to be able to execute correctly
* @var array
*/
protected $_dependencies = array();
/**
* When there is no entries found by the Datasource, this parameter will
* be set to true, which will inject the default Symphony 'No records found'
* message into the datasource's result
* @var boolean
*/
protected $_force_empty_result = false;
/**
* When there is a negating parameter, this parameter will
* be set to true, which will inject the default Symphony 'Results Negated'
* message into the datasource's result
* @var boolean
*/
protected $_negate_result = false;
/**
* Constructor for the datasource sets the parent, if `$process_params` is set,
* the `$env` variable will be run through `Datasource::processParameters`.
*
* @see toolkit.Datasource#processParameters()
* @param array $env
* The environment variables from the Frontend class which includes
* any params set by Symphony or Events or by other Datasources
* @param boolean $process_params
* If set to true, `Datasource::processParameters` will be called. By default
* this is true
* @throws FrontendPageNotFoundException
*/
public function __construct(array $env = null, $process_params = true)
{
// Support old the __construct (for the moment anyway).
// The old signature was array/array/boolean
// The new signature is array/boolean
$arguments = func_get_args();
if (count($arguments) == 3 && is_bool($arguments[1]) && is_bool($arguments[2])) {
$env = $arguments[0];
$process_params = $arguments[1];
}
if ($process_params) {
$this->processParameters($env);
}
}
/**
* This function is required in order to edit it in the datasource editor page.
* Do not overload this function if you are creating a custom datasource. It is only
* used by the datasource editor. If this is set to false, which is default, the
* Datasource's `about()` information will be displayed.
*
* @return boolean
* True if the Datasource can be edited, false otherwise. Defaults to false
*/
public function allowEditorToParse()
{
return false;
}
/**
* This function is required in order to identify what section this Datasource is for. It
* is used in the datasource editor. It must remain intact. Do not overload this function in
* custom events. Other datasources may return a string here defining their datasource
* type when they do not query a section.
*
* @return mixed
*/
public function getSource()
{
return null;
}
/**
* Accessor function to return this Datasource's dependencies
*
* @return array
*/
public function getDependencies()
{
return $this->_dependencies;
}
/**
* Returns an associative array of information about a datasource.
*
* @return array
*/
public function about()
{
return array();
}
/**
* @deprecated This function has been renamed to `execute` as of
* Symphony 2.3.1, please use `execute()` instead. This function will
* be removed in Symphony 3.0
* @see execute()
*/
public function grab(array &$param_pool = null)
{
return $this->execute($param_pool);
}
/**
* The meat of the Datasource, this function includes the datasource
* type's file that will preform the logic to return the data for this datasource
* It is passed the current parameters.
*
* @param array $param_pool
* The current parameter pool that this Datasource can use when filtering
* and finding Entries or data.
* @return XMLElement
* The XMLElement to add into the XML for a page.
*/
public function execute(array &$param_pool = null)
{
$result = new XMLElement($this->dsParamROOTELEMENT);
try {
$result = $this->execute($param_pool);
} catch (FrontendPageNotFoundException $e) {
// Work around. This ensures the 404 page is displayed and
// is not picked up by the default catch() statement below
FrontendPageNotFoundExceptionHandler::render($e);
} catch (Exception $e) {
$result->appendChild(new XMLElement('error', $e->getMessage()));
return $result;
}
if ($this->_force_empty_result) {
$result = $this->emptyXMLSet();
}
if ($this->_negate_result) {
$result = $this->negateXMLSet();
}
return $result;
}
/**
* By default, all Symphony filters are considering to be AND filters, that is
* they are all used and Entries must match each filter to be included. It is
* possible to use OR filtering in a field by using an + to separate the values.
* eg. If the filter is test1 + test2, this will match any entries where this field
* is test1 OR test2. This function is run on each filter (ie. each field) in a
* datasource
*
* @param string $value
* The filter string for a field.
* @return integer
* Datasource::FILTER_OR or Datasource::FILTER_AND
*/
public static function determineFilterType($value)
{
return (preg_match('/\s+\+\s+/', $value) ? Datasource::FILTER_AND : Datasource::FILTER_OR);
}
/**
* If there is no results to return this function calls `Datasource::__noRecordsFound`
* which appends an XMLElement to the current root element.
*
* @param XMLElement $xml
* The root element XMLElement for this datasource. By default, this will
* the handle of the datasource, as defined by `$this->dsParamROOTELEMENT`
* @return XMLElement
*/
public function emptyXMLSet(XMLElement $xml = null)
{
if (is_null($xml)) {
$xml = new XMLElement($this->dsParamROOTELEMENT);
}
$xml->appendChild($this->__noRecordsFound());
return $xml;
}
/**
* If the datasource has been negated this function calls `Datasource::__negateResult`
* which appends an XMLElement to the current root element.
*
* @param XMLElement $xml
* The root element XMLElement for this datasource. By default, this will
* the handle of the datasource, as defined by `$this->dsParamROOTELEMENT`
* @return XMLElement
*/
public function negateXMLSet(XMLElement $xml = null)
{
if (is_null($xml)) {
$xml = new XMLElement($this->dsParamROOTELEMENT);
}
$xml->appendChild($this->__negateResult());
return $xml;
}
/**
* Returns an error XMLElement with 'No records found' text
*
* @return XMLElement
*/
public function __noRecordsFound()
{
return new XMLElement('error', __('No records found.'));
}
/**
* Returns an error XMLElement with 'Result Negated' text
*
* @return XMLElement
*/
public function __negateResult()
{
$error = new XMLElement('error', __("Data source not executed, forbidden parameter was found."), array(
'forbidden-param' => $this->dsParamNEGATEPARAM
));
return $error;
}
/**
* This function will iterates over the filters and replace any parameters with their
* actual values. All other Datasource variables such as sorting, ordering and
* pagination variables are also set by this function
*
* @param array $env
* The environment variables from the Frontend class which includes
* any params set by Symphony or Events or by other Datasources
* @throws FrontendPageNotFoundException
*/
public function processParameters(array $env = null)
{
if ($env) {
$this->_env = $env;
}
if ((isset($this->_env) && is_array($this->_env)) && isset($this->dsParamFILTERS) && is_array($this->dsParamFILTERS) && !empty($this->dsParamFILTERS)) {
foreach ($this->dsParamFILTERS as $key => $value) {
$value = stripslashes($value);
$new_value = $this->__processParametersInString($value, $this->_env);
// If a filter gets evaluated to nothing, eg. ` + ` or ``, then remove
// the filter. Respects / as this may be real from current-path. RE: #1759
if (strlen(trim($new_value)) === 0 || !preg_match('/[\w|\/]+/u', $new_value)) {
unset($this->dsParamFILTERS[$key]);
} else {
$this->dsParamFILTERS[$key] = $new_value;
}
}
}
if (isset($this->dsParamORDER)) {
$this->dsParamORDER = $this->__processParametersInString($this->dsParamORDER, $this->_env);
}
if (isset($this->dsParamSORT)) {
$this->dsParamSORT = $this->__processParametersInString($this->dsParamSORT, $this->_env);
}
if (isset($this->dsParamSTARTPAGE)) {
$this->dsParamSTARTPAGE = $this->__processParametersInString($this->dsParamSTARTPAGE, $this->_env);
if ($this->dsParamSTARTPAGE === '') {
$this->dsParamSTARTPAGE = '1';
}
}
if (isset($this->dsParamLIMIT)) {
$this->dsParamLIMIT = $this->__processParametersInString($this->dsParamLIMIT, $this->_env);
}
if (
isset($this->dsParamREQUIREDPARAM)
&& strlen(trim($this->dsParamREQUIREDPARAM)) > 0
&& $this->__processParametersInString(trim($this->dsParamREQUIREDPARAM), $this->_env, false) === ''
) {
$this->_force_empty_result = true; // don't output any XML
$this->dsParamPARAMOUTPUT = null; // don't output any parameters
$this->dsParamINCLUDEDELEMENTS = null; // don't query any fields in this section
return;
}
if (
isset($this->dsParamNEGATEPARAM)
&& strlen(trim($this->dsParamNEGATEPARAM)) > 0
&& $this->__processParametersInString(trim($this->dsParamNEGATEPARAM), $this->_env, false) !== ''
) {
$this->_negate_result = true; // don't output any XML
$this->dsParamPARAMOUTPUT = null; // don't output any parameters
$this->dsParamINCLUDEDELEMENTS = null; // don't query any fields in this section
return;
}
$this->_param_output_only = ((!isset($this->dsParamINCLUDEDELEMENTS) || !is_array($this->dsParamINCLUDEDELEMENTS) || empty($this->dsParamINCLUDEDELEMENTS)) && !isset($this->dsParamGROUP));
if (isset($this->dsParamREDIRECTONEMPTY) && $this->dsParamREDIRECTONEMPTY === 'yes' && $this->_force_empty_result) {
throw new FrontendPageNotFoundException;
}
}
/**
* This function will parse a string (usually a URL) and fully evaluate any
* parameters (defined by {$param}) to return the absolute string value.
*
* @since Symphony 2.3
* @param string $url
* The string (usually a URL) that contains the parameters (or doesn't)
* @return string
* The parsed URL
*/
public function parseParamURL($url = null)
{
if (!isset($url)) {
return null;
}
// urlencode parameters
$params = array();
if (preg_match_all('@{([^}]+)}@i', $url, $matches, PREG_SET_ORDER)) {
foreach ($matches as $m) {
$params[$m[1]] = array(
'param' => preg_replace('/:encoded$/', null, $m[1]),
'encode' => preg_match('/:encoded$/', $m[1])
);
}
}
foreach ($params as $key => $info) {
$replacement = $this->__processParametersInString($info['param'], $this->_env, false);
if ($info['encode'] == true) {
$replacement = urlencode($replacement);
}
$url = str_replace("{{$key}}", $replacement, $url);
}
return $url;
}
/**
* This function will replace any parameters in a string with their value.
* Parameters are defined by being prefixed by a `$` character. In certain
* situations, the parameter will be surrounded by `{}`, which Symphony
* takes to mean, evaluate this parameter to a value, other times it will be
* omitted which is usually used to indicate that this parameter exists
*
* @param string $value
* The string with the parameters that need to be evaluated
* @param array $env
* The environment variables from the Frontend class which includes
* any params set by Symphony or Events or by other Datasources
* @param boolean $includeParenthesis
* Parameters will sometimes not be surrounded by `{}`. If this is the case
* setting this parameter to false will make this function automatically add
* them to the parameter. By default this is true, which means all parameters
* in the string already are surrounded by `{}`
* @param boolean $escape
* If set to true, the resulting value will passed through `urlencode` before
* being returned. By default this is `false`
* @return string
* The string with all parameters evaluated. If a parameter is not found, it will
* not be replaced and remain in the `$value`.
*/
public function __processParametersInString($value, array $env, $includeParenthesis = true, $escape = false)
{
if (trim($value) == '') {
return null;
}
if (!$includeParenthesis) {
$value = '{'.$value.'}';
}
if (preg_match_all('@{([^}]+)}@i', $value, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
list($source, $cleaned) = $match;
$replacement = null;
$bits = preg_split('/:/', $cleaned, -1, PREG_SPLIT_NO_EMPTY);
foreach ($bits as $param) {
if ($param{0} !== '$') {
$replacement = $param;
break;
}
$param = trim($param, '$');
$replacement = Datasource::findParameterInEnv($param, $env);
if (is_array($replacement)) {
$replacement = array_map(array('Datasource', 'escapeCommas'), $replacement);
if (count($replacement) > 1) {
$replacement = implode(',', $replacement);
} else {
$replacement = end($replacement);
}
}
if (!empty($replacement)) {
break;
}
}
if ($escape == true) {
$replacement = urlencode($replacement);
}
$value = str_replace($source, $replacement, $value);
}
}
return $value;
}
/**
* Using regexp, this escapes any commas in the given string
*
* @param string $string
* The string to escape the commas in
* @return string
*/
public static function escapeCommas($string)
{
return preg_replace('/(?<!\\\\),/', "\\,", $string);
}
/**
* Used in conjunction with escapeCommas, this function will remove
* the escaping pattern applied to the string (and commas)
*
* @param string $string
* The string with the escaped commas in it to remove
* @return string
*/
public static function removeEscapedCommas($string)
{
return preg_replace('/(?<!\\\\)\\\\,/', ',', $string);
}
/**
* Parameters can exist in three different facets of Symphony; in the URL,
* in the parameter pool or as an Symphony param. This function will attempt
* to find a parameter in those three areas and return the value. If it is not found
* null is returned
*
* @param string $needle
* The parameter name
* @param array $env
* The environment variables from the Frontend class which includes
* any params set by Symphony or Events or by other Datasources
* @return mixed
* If the value is not found, null, otherwise a string or an array is returned
*/
public static function findParameterInEnv($needle, $env)
{
if (isset($env['env']['url'][$needle])) {
return $env['env']['url'][$needle];
}
if (isset($env['env']['pool'][$needle])) {
return $env['env']['pool'][$needle];
}
if (isset($env['param'][$needle])) {
return $env['param'][$needle];
}
return null;
}
/**
* By default, all Symphony filters are considering to be AND filters, that is
* they are all used and Entries must match each filter to be included. It is
* possible to use OR filtering in a field by using an + to separate the values.
* eg. If the filter is test1 + test2, this will match any entries where this field
* is test1 OR test2. This function is run on each filter (ie. each field) in a
* datasource
*
* @deprecated Since Symphony 2.6.0 it is recommended to use the static version,
* `Datasource::determineFilterType`
* @param string $value
* The filter string for a field.
* @return integer
* Datasource::FILTER_OR or Datasource::FILTER_AND
*/
public function __determineFilterType($value)
{
return self::determineFilterType($value);
}
}