1: <?php
2: /**
3: * DataTables PHP libraries.
4: *
5: * PHP libraries for DataTables and DataTables Editor, utilising PHP 5.3+.
6: *
7: * @author SpryMedia
8: * @copyright 2012 SpryMedia ( http://sprymedia.co.uk )
9: * @license http://editor.datatables.net/license DataTables Editor
10: * @link http://editor.datatables.net
11: */
12:
13: namespace DataTables\Editor;
14: if (!defined('DATATABLES')) exit();
15:
16: use
17: DataTables,
18: DataTables\Editor,
19: DataTables\Editor\Join;
20:
21:
22: /**
23: * Field definitions for the DataTables Editor.
24: *
25: * Each Database column that is used with Editor can be described with this
26: * Field method (both for Editor and Join instances). It basically tells
27: * Editor what table column to use, how to format the data and if you want
28: * to read and/or write this column.
29: *
30: * Field instances are used with the {@link Editor::field} and
31: * {@link Join::field} methods to describe what fields should be interacted
32: * with by the editable table.
33: *
34: * @example
35: * Simply get a column with the name "city". No validation is performed.
36: * <code>
37: * Field::inst( 'city' )
38: * </code>
39: *
40: * @example
41: * Get a column with the name "first_name" - when edited a value must
42: * be given due to the "required" validation from the {@link Validate} class.
43: * <code>
44: * Field::inst( 'first_name' )->validator( 'Validate::required' )
45: * </code>
46: *
47: * @example
48: * Working with a date field, which is validated, and also has *get* and
49: * *set* formatters.
50: * <code>
51: * Field::inst( 'registered_date' )
52: * ->validator( 'Validate::dateFormat', 'D, d M y' )
53: * ->getFormatter( 'Format::date_sql_to_format', 'D, d M y' )
54: * ->setFormatter( 'Format::date_format_to_sql', 'D, d M y' )
55: * </code>
56: *
57: * @example
58: * Using an alias in the first parameter
59: * <code>
60: * Field::inst( 'name.first as first_name' )
61: * </code>
62: */
63: class Field extends DataTables\Ext {
64: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
65: * Statics
66: */
67:
68: /** Set option flag (`set()`) - do not set data */
69: const SET_NONE = 'none';
70:
71: /** Set option flag (`set()`) - write to database on both create and edit */
72: const SET_BOTH = 'both';
73:
74: /** Set option flag (`set()`) - write to database only on create */
75: const SET_CREATE = 'create';
76:
77: /** Set option flag (`set()`) - write to database only on edit */
78: const SET_EDIT = 'edit';
79:
80:
81: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
82: * Constructor
83: */
84:
85: /**
86: * Field instance constructor.
87: * @param string $dbField Name of the database column
88: * @param string $name Name to use in the JSON output from Editor and the
89: * HTTP submit from the client-side when editing. If not given then the
90: * $dbField name is used.
91: */
92: function __construct( $dbField=null, $name=null )
93: {
94: if ( $dbField !== null && $name === null ) {
95: // Allow just a single parameter to be passed - each can be
96: // overridden if needed later using the API.
97: $this->name( $dbField );
98: $this->dbField( $dbField );
99: }
100: else {
101: $this->name( $name );
102: $this->dbField( $dbField );
103: }
104: }
105:
106:
107:
108: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
109: * Private parameters
110: */
111:
112: /** @var string */
113: private $_dbField = null;
114:
115: /** @var boolean */
116: private $_get = true;
117:
118: /** @var mixed */
119: private $_getFormatter = null;
120:
121: /** @var mixed */
122: private $_getFormatterOpts = null;
123:
124: /** @var mixed */
125: private $_getValue = null;
126:
127: /** @var string|callable */
128: private $_optsTable = null;
129:
130: /** @var string */
131: private $_optsValue = null;
132:
133: /** @var string */
134: private $_optsLabel = null;
135:
136: /** @var callable */
137: private $_optsCond = null;
138:
139: /** @var callable */
140: private $_optsFormat = null;
141:
142: /** @var string */
143: private $_name = null;
144:
145: /** @var string */
146: private $_set = Field::SET_BOTH;
147:
148: /** @var mixed */
149: private $_setFormatter = null;
150:
151: /** @var mixed */
152: private $_setFormatterOpts = null;
153:
154: /** @var mixed */
155: private $_setValue = null;
156:
157: /** @var mixed */
158: private $_validator = array();
159:
160: /** @var Upload */
161: private $_upload = null;
162:
163: /** @var callable */
164: private $_xss = null;
165:
166: /** @var boolean */
167: private $_xssFormat = true;
168:
169:
170:
171: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
172: * Public methods
173: */
174:
175:
176: /**
177: * Get / set the DB field name.
178: *
179: * Note that when used as a setter, an alias can be given for the field
180: * using the SQL `as` keyword - for example: `firstName as name`. In this
181: * situation the dbField is set to the field name before the `as`, and the
182: * field's name (`name()`) is set to the name after the ` as `.
183: *
184: * As a result of this, the following constructs have identical
185: * functionality:
186: *
187: * Field::inst( 'firstName as name' );
188: * Field::inst( 'firstName', 'name' );
189: *
190: * @param string $_ Value to set if using as a setter.
191: * @return string|self The name of the db field if no parameter is given,
192: * or self if used as a setter.
193: */
194: public function dbField ( $_=null )
195: {
196: if ( $_ === null ) {
197: return $this->_dbField;
198: }
199:
200: if ( stripos( $_, ' as ' ) ) {
201: $a = preg_split( '/ as /i', $_ );
202: $this->_dbField = trim( $a[0] );
203: $this->_name = trim( $a[1] );
204: }
205: else {
206: $this->_dbField = $_;
207: }
208:
209: return $this;
210: }
211:
212:
213: /**
214: * Get / set the 'get' property of the field.
215: *
216: * A field can be marked as write only when setting the get property to false
217: * here.
218: * @param boolean $_ Value to set if using as a setter.
219: * @return boolean|self The get property if no parameter is given, or self
220: * if used as a setter.
221: */
222: public function get ( $_=null )
223: {
224: return $this->_getSet( $this->_get, $_ );
225: }
226:
227:
228: /**
229: * Get formatter for the field's data.
230: *
231: * When the data has been retrieved from the server, it can be passed through
232: * a formatter here, which will manipulate (format) the data as required. This
233: * can be useful when, for example, working with dates and a particular format
234: * is required on the client-side.
235: *
236: * Editor has a number of formatters available with the {@link Format} class
237: * which can be used directly with this method.
238: * @param callable|string $_ Value to set if using as a setter. Can be given as
239: * a closure function or a string with a reference to a function that will
240: * be called with call_user_func().
241: * @param mixed $opts Variable that is passed through to the get formatting
242: * function - can be useful for passing through extra information such as
243: * date formatting string, or a required flag. The actual options available
244: * depend upon the formatter used.
245: * @return callable|string|self The get formatter if no parameter is given, or
246: * self if used as a setter.
247: */
248: public function getFormatter ( $_=null, $opts=null )
249: {
250: if ( $opts !== null ) {
251: $this->_getFormatterOpts = $opts;
252: }
253: return $this->_getSet( $this->_getFormatter, $_ );
254: }
255:
256:
257: /**
258: * Get / set a get value. If given, then this value is used to send to the
259: * client-side, regardless of what value is held by the database.
260: *
261: * @param callable|string|number $_ Value to set, or no value to use as a
262: * getter
263: * @return callable|string|self Value if used as a getter, or self if used
264: * as a setter.
265: */
266: public function getValue ( $_=null )
267: {
268: return $this->_getSet( $this->_getValue, $_ );
269: }
270:
271:
272: /**
273: * Get / set the 'name' property of the field.
274: *
275: * The name is typically the same as the dbField name, since it makes things
276: * less confusing(!), but it is possible to set a different name for the data
277: * which is used in the JSON returned to DataTables in a 'get' operation and
278: * the field name used in a 'set' operation.
279: * @param string $_ Value to set if using as a setter.
280: * @return string|self The name property if no parameter is given, or self
281: * if used as a setter.
282: */
283: public function name ( $_=null )
284: {
285: return $this->_getSet( $this->_name, $_ );
286: }
287:
288:
289: /**
290: * Get a list of values that can be used for the options list in radio,
291: * select and checkbox inputs from the database for this field.
292: *
293: * Note that this is for simple 'label / value' pairs only. For more complex
294: * data, including pairs that require joins and where conditions, use a
295: * closure to provide a query
296: *
297: * @param string|callable $table Database table name to use to get the
298: * paired data from, or a closure function if providing a method
299: * @param string $value Table column name that contains the pair's
300: * value. Not used if the first parameter is given as a closure
301: * @param string $label Table column name that contains the pair's
302: * label. Not used if the first parameter is given as a closure
303: * @param callable $condition Function that will add `where`
304: * conditions to the query
305: * @return Field Self for chaining
306: */
307: public function options ( $table, $value=null, $label=null, $condition=null, $format=null )
308: {
309: $this->_optsTable = $table;
310: $this->_optsValue = $value;
311: $this->_optsLabel = $label;
312: $this->_optsCond = $condition;
313: $this->_optsFormat = $format;
314:
315: return $this;
316: }
317:
318:
319: /**
320: * Get / set the 'set' property of the field.
321: *
322: * A field can be marked as read only using this option, to be set only
323: * during an create or edit action or to be set during both actions. This
324: * provides the ability to have fields that are only set when a new row is
325: * created (for example a "created" time stamp).
326: * @param string|boolean $_ Value to set when the method is being used as a
327: * setter (leave as undefined to use as a getter). This can take the
328: * value of:
329: *
330: * * `true` - Same as `Field::SET_NONE`
331: * * `false` - Same as `Field::SET_BOTH`
332: * * `Field::SET_BOTH` - Set the database value on both create and edit commands
333: * * `Field::SET_NONE` - Never set the database value
334: * * `Field::SET_CREATE` - Set the database value only on create
335: * * `Field::SET_EDIT` - Set the database value only on edit
336: * @return string|self The set property if no parameter is given, or self
337: * if used as a setter.
338: */
339: public function set ( $_=null )
340: {
341: if ( $_ === true ) {
342: $_ = Field::SET_BOTH;
343: }
344: else if ( $_ === false ) {
345: $_ = Field::SET_NONE;
346: }
347:
348: return $this->_getSet( $this->_set, $_ );
349: }
350:
351:
352: /**
353: * Set formatter for the field's data.
354: *
355: * When the data has been retrieved from the server, it can be passed through
356: * a formatter here, which will manipulate (format) the data as required. This
357: * can be useful when, for example, working with dates and a particular format
358: * is required on the client-side.
359: *
360: * Editor has a number of formatters available with the {@link Format} class
361: * which can be used directly with this method.
362: * @param callable|string $_ Value to set if using as a setter. Can be given as
363: * a closure function or a string with a reference to a function that will
364: * be called with call_user_func().
365: * @param mixed $opts Variable that is passed through to the get formatting
366: * function - can be useful for passing through extra information such as
367: * date formatting string, or a required flag. The actual options available
368: * depend upon the formatter used.
369: * @return callable|string|self The set formatter if no parameter is given, or
370: * self if used as a setter.
371: */
372: public function setFormatter ( $_=null, $opts=null )
373: {
374: if ( $opts !== null ) {
375: $this->_setFormatterOpts = $opts;
376: }
377: return $this->_getSet( $this->_setFormatter, $_ );
378: }
379:
380:
381: /**
382: * Get / set a set value. If given, then this value is used to write to the
383: * database regardless of what data is sent from the client-side.
384: *
385: * @param callable|string|number $_ Value to set, or no value to use as a
386: * getter
387: * @return callable|string|self Value if used as a getter, or self if used
388: * as a setter.
389: */
390: public function setValue ( $_=null )
391: {
392: return $this->_getSet( $this->_setValue, $_ );
393: }
394:
395:
396: /**
397: * Get / set the upload class for this field.
398: * @param Upload $_ Upload class if used as a setter
399: * @return Upload|self Value if used as a getter, or self if used
400: * as a setter.
401: */
402: public function upload ( $_=null )
403: {
404: return $this->_getSet( $this->_upload, $_ );
405: }
406:
407:
408: /**
409: * Get / set the 'validator' of the field.
410: *
411: * The validator can be used to check if any abstract piece of data is valid
412: * or not according to the given rules of the validation function used.
413: *
414: * Multiple validation options can be applied to a field instance by calling
415: * this method multiple times. For example, it would be possible to have a
416: * 'required' validation and a 'maxLength' validation with multiple calls.
417: *
418: * Editor has a number of validation available with the {@link Validate} class
419: * which can be used directly with this method.
420: * @param callable|string $_ Value to set if using as the validation method.
421: * Can be given as a closure function or a string with a reference to a
422: * function that will be called with call_user_func().
423: * @param mixed $opts Variable that is passed through to the validation
424: * function - can be useful for passing through extra information such as
425: * date formatting string, or a required flag. The actual options available
426: * depend upon the validation function used.
427: * @return callable|string|self The validation method if no parameter is given,
428: * or self if used as a setter.
429: */
430: public function validator ( $_=null, $opts=null )
431: {
432: if ( $_ === null ) {
433: return $this->_validator;
434: }
435: else {
436: $this->_validator[] = array(
437: "func" => $_,
438: "opts" => $opts
439: );
440: }
441:
442: return $this;
443: }
444:
445:
446: /**
447: * Set a formatting method that will be used for XSS checking / removal.
448: * This should be a function that takes a single argument (the value to be
449: * cleaned) and returns the cleaned value.
450: *
451: * Editor will use HtmLawed by default for this operation, which is built
452: * into the software and no additional configuration is required, but a
453: * custom function can be used if you wish to use a different formatter such
454: * as HTMLPurifier.
455: *
456: * If you wish to disable this option (which you would only do if you are
457: * absolutely confident that your validation will pick up on any XSS inputs)
458: * simply provide a closure function that returns the value given to the
459: * function. This is _not_ recommended.
460: *
461: * @param callable|false $xssFormatter XSS cleaner function, use `false` or
462: * `null` to disable XSS cleaning.
463: * @return Field Self for chaining.
464: */
465: public function xss ( $xssFormatter )
466: {
467: if ( $xssFormatter === true || $xssFormatter === false || $xssFormatter === null ) {
468: $this->_xssFormat = $xssFormatter;
469: }
470: else {
471: $this->_xss = $xssFormatter;
472: }
473:
474: return $this;
475: }
476:
477:
478:
479: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
480: * Internal methods
481: * Used by the Editor class and not generally for public use
482: */
483:
484: /**
485: * Check to see if a field should be used for a particular action (get or set).
486: *
487: * Called by the Editor / Join class instances - not expected for general
488: * consumption - internal.
489: * @param string $action Direction that the data is travelling - 'get' is
490: * reading DB data, `create` and `edit` for writing to the DB
491: * @param array $data Data submitted from the client-side when setting.
492: * @return boolean true if the field should be used in the get / set.
493: * @internal
494: */
495: public function apply ( $action, $data=null )
496: {
497: if ( $action === 'get' ) {
498: // Get action - can we get this field
499: return $this->_get;
500: }
501: else {
502: // Note that validation must be done on input data before we get here
503:
504: // Create or edit action, are we configured to use this field
505: if ( $action === 'create' &&
506: ($this->_set === Field::SET_NONE || $this->_set === Field::SET_EDIT)
507: ) {
508: return false;
509: }
510: else if ( $action === 'edit' &&
511: ($this->_set === Field::SET_NONE || $this->_set === Field::SET_CREATE)
512: ) {
513: return false;
514: }
515:
516: // Check it was in the submitted data. If not, then not required
517: // (validation would have failed if it was) and therefore we don't
518: // set it. Check for a value as well, as it can format data from
519: // some other source
520: if ( $this->_setValue === null && ! $this->_inData( $this->name(), $data ) ) {
521: return false;
522: }
523:
524: // In the data set, so use it
525: return true;
526: }
527: }
528:
529:
530: /**
531: * Execute the ipOpts to get the list of options to return to the client-
532: * side
533: *
534: * @param \DataTables\Database $db Database instance
535: * @return Array Array of value / label options for the list
536: * @internal
537: */
538: public function optionsExec ( $db )
539: {
540: $table = $this->_optsTable;
541:
542: if ( ! $table ) {
543: return false;
544: }
545: else if ( is_callable($table) && is_object($table) ) {
546: return $table();
547: }
548: else {
549: $label = $this->_optsLabel;
550: $value = $this->_optsValue;
551: $formatter = $this->_optsFormat;
552: $fields = array( $value );
553:
554: // Create a list of the fields that we need to get from the db
555: if ( ! is_array( $label ) ) {
556: $fields[] = $label;
557: }
558: else {
559: $fields = array_merge( $fields, $label );
560: }
561:
562: // We need a default formatter if one isn't provided
563: if ( ! $formatter ) {
564: $formatter = is_array( $label ) ?
565: function ( $row ) use ( $label ) {
566: $a = array();
567:
568: for ( $i=0, $ien=count($label) ; $i<$ien ; $i++ ) {
569: $a[] = $row[ $label[$i] ];
570: }
571:
572: return implode(' ', $a);
573: } :
574: function ( $row ) use ( $label ) {
575: return $row[ $label ];
576: };
577: }
578:
579: // Get the data
580: $rows = $db
581: ->selectDistinct(
582: $table,
583: $fields,
584: $this->_optsCond
585: )
586: ->fetchAll();
587:
588: // Create the output array
589: $out = array();
590:
591: for ( $i=0, $ien=count($rows) ; $i<$ien ; $i++ ) {
592: $out[] = array(
593: "label" => $formatter( $rows[$i] ),
594: "value" => $rows[$i][$value]
595: );
596: }
597:
598: usort( $out, function ( $a, $b ) {
599: return is_numeric($a['label']) && is_numeric($b['label']) ?
600: ($a['label']*1) - ($b['label']*1) :
601: strcmp( $a['label'], $b['label'] );
602: } );
603:
604: return $out;
605: }
606: }
607:
608:
609: /**
610: * Get the options table
611: * @return string Options table or null
612: * @internal
613: */
614: public function optsTable ()
615: {
616: return is_string( $this->_optsTable ) ?
617: $this->_optsTable :
618: null;
619: }
620:
621:
622: /**
623: * Get the options value column
624: * @return string Options value or null
625: * @internal
626: */
627: public function optsValue ()
628: {
629: return $this->_optsValue;
630: }
631:
632:
633: /**
634: * Get the value of the field, taking into account if it is coming from the
635: * DB or from a POST. If formatting has been specified for this field, it
636: * will be applied here.
637: *
638: * Called by the Editor / Join class instances - not expected for general
639: * consumption - internal.
640: * @param string $direction Direction that the data is travelling - 'get' is
641: * reading data, and 'set' is writing it to the DB.
642: * @param array $data Data submitted from the client-side when setting or the
643: * data for the row when getting data from the DB.
644: * @return string Value for the field
645: * @internal
646: */
647: public function val ( $direction, $data )
648: {
649: if ( $direction === 'get' ) {
650: if ( $this->_getValue !== null ) {
651: $val = $this->_getAssignedValue( $this->_getValue );
652: }
653: else {
654: // Getting data, so the db field name
655: $val = isset( $data[ $this->_dbField ] ) ?
656: $data[ $this->_dbField ] :
657: null;
658: }
659:
660: return $this->_format(
661: $val, $data, $this->_getFormatter, $this->_getFormatterOpts
662: );
663: }
664: else {
665: // Setting data, so using from the payload (POST usually) and thus
666: // use the 'name'
667: $val = $this->_setValue !== null ?
668: $this->_getAssignedValue( $this->_setValue ) :
669: $this->_readProp( $this->name(), $data );
670:
671: // XSS removal / checker
672: if ( $this->_xssFormat ) {
673: $val = $this->xssSafety( $val );
674: }
675:
676: return $this->_format(
677: $val, $data, $this->_setFormatter, $this->_setFormatterOpts
678: );
679: }
680: }
681:
682:
683: /**
684: * Check the validity of the field based on the data submitted. Note that
685: * this validation is performed on the wire data - i.e. that which is
686: * submitted, before any setFormatter is run
687: *
688: * Called by the Editor / Join class instances - not expected for general
689: * consumption - internal.
690: *
691: * @param array $data Data submitted from the client-side
692: * @param Editor $editor Editor instance
693: * @param * $id Row id that is being validated
694: * @return boolean|string `true` if valid, string with error message if not
695: * @internal
696: */
697: public function validate ( $data, $editor, $id=null )
698: {
699: // Three cases for the validator - closure, string or null
700: if ( ! count( $this->_validator ) ) {
701: return true;
702: }
703:
704: $val = $this->_readProp( $this->name(), $data );
705:
706: for ( $i=0, $ien=count( $this->_validator ) ; $i<$ien ; $i++ ) {
707: $validator = $this->_validator[$i];
708: $processData = $editor->inData();
709: $instances = array(
710: 'action' => $processData['action'],
711: 'id' => $id,
712: 'field' => $this,
713: 'editor' => $editor,
714: 'db' => $editor->db()
715: );
716:
717: if ( is_string( $validator['func'] ) ) {
718: // Don't require the Editor namespace if DataTables validator is given as a string
719: if ( strpos($validator['func'], "Validate::") === 0 ) {
720: $res = call_user_func( "\\DataTables\\Editor\\".$validator['func'], $val, $data, $validator['opts'], $instances );
721: }
722: else {
723: $res = call_user_func( $validator['func'], $val, $data, $validator['opts'], $instances );
724: }
725: }
726: else {
727: $func = $validator['func'];
728: $res = $func( $val, $data, $this, $instances );
729: }
730:
731: // Check if there was a validation error and if so, return it
732: if ( $res !== true ) {
733: return $res;
734: }
735: }
736:
737: // Validation methods all run, must be valid
738: return true;
739: }
740:
741:
742: /**
743: * Write the value for this field to the output array for a read operation
744: *
745: * @param array $out Row output data (to the JSON)
746: * @param mixed $srcData Row input data (raw, from the database)
747: * @internal
748: */
749: public function write( &$out, $srcData )
750: {
751: $this->_writeProp( $out, $this->name(), $this->val('get', $srcData) );
752: }
753:
754:
755: /**
756: * Perform XSS prevention on an input.
757: *
758: * @param * $val Value to be escaped
759: * @return string Safe value
760: */
761: public function xssSafety ( $val ) {
762: $xss = $this->_xss;
763:
764: if ( is_array( $val ) ) {
765: $res = array();
766:
767: foreach ( $val as $individual ) {
768: $res[] = $xss ?
769: $xss( $individual ) :
770: DataTables\Vendor\Htmlaw::filter( $individual );
771: }
772:
773: return $res;
774: }
775:
776: return $xss ?
777: $xss( $val ) :
778: DataTables\Vendor\Htmlaw::filter( $val );
779: }
780:
781:
782:
783: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
784: * Private methods
785: */
786:
787: /**
788: * Apply a formatter to data. The caller will decide what formatter to apply
789: * (get or set)
790: *
791: * @param mixed $val Value to be formatted
792: * @param mixed $data Full row data
793: * @param callable $formatter Formatting function to be called
794: * @param array $opts Array of options to be passed to the formatter
795: * @return mixed Formatted value
796: */
797: private function _format( $val, $data, $formatter, $opts )
798: {
799: // Three cases for the formatter - closure, string or null
800: if ( $formatter ) {
801: if ( is_string( $formatter ) ) {
802: // Don't require the Editor namespace if DataTables validator is given as a string
803: if ( strpos($formatter, "Format::") === 0 ) {
804: // Editor formatter
805: return call_user_func(
806: "\\DataTables\\Editor\\".$formatter,
807: $val,
808: $data,
809: $opts
810: );
811: }
812:
813: // User function (string identifier)
814: return call_user_func( $formatter, $val, $data, $opts );
815: }
816:
817: // Closure
818: return $formatter( $val, $data, $opts );
819: }
820: return $val;
821: }
822:
823: /**
824: * Get the value from `_[gs]etValue` - taking into account if it is callable
825: * function or not
826: *
827: * @param mixed $val Value to be evaluated
828: * @return mixed Value assigned, or returned from the function
829: */
830: private function _getAssignedValue ( $val )
831: {
832: return is_callable($val) && is_object($val) ?
833: $val() :
834: $val;
835: }
836:
837: /**
838: * Check is a parameter is in the submitted data set. This is functionally
839: * the same as the `_readProp()` method, but in this case a binary value
840: * is required to indicate if the value is present or not.
841: *
842: * @param string $name Javascript dotted object name to write to
843: * @param array $data Data source array to read from
844: * @return boolean `true` if present, `false` otherwise
845: * @private
846: */
847: private function _inData ( $name, $data )
848: {
849: if ( strpos($name, '.') === false ) {
850: return isset( $data[ $name ] ) ?
851: true :
852: false;
853: }
854:
855: $names = explode( '.', $name );
856: $inner = $data;
857:
858: for ( $i=0 ; $i<count($names)-1 ; $i++ ) {
859: if ( ! isset( $inner[ $names[$i] ] ) ) {
860: return false;
861: }
862:
863: $inner = $inner[ $names[$i] ];
864: }
865:
866: return isset( $inner [ $names[count($names)-1] ] ) ?
867: true :
868: false;
869: }
870:
871: /**
872: * Read a value from a data structure, using Javascript dotted object
873: * notation. This is the inverse of the `_writeProp` method and provides
874: * the same support, matching DataTables' ability to read nested JSON
875: * data objects.
876: *
877: * @param string $name Javascript dotted object name to write to
878: * @param array $data Data source array to read from
879: * @return mixed The read value, or null if no value found.
880: * @private
881: */
882: private function _readProp ( $name, $data )
883: {
884: if ( strpos($name, '.') === false ) {
885: return isset( $data[ $name ] ) ?
886: $data[ $name ] :
887: null;
888: }
889:
890: $names = explode( '.', $name );
891: $inner = $data;
892:
893: for ( $i=0 ; $i<count($names)-1 ; $i++ ) {
894: if ( ! isset( $inner[ $names[$i] ] ) ) {
895: return null;
896: }
897:
898: $inner = $inner[ $names[$i] ];
899: }
900:
901: if ( isset( $names[count($names)-1] ) ) {
902: $idx = $names[count($names)-1];
903:
904: return isset( $inner[ $idx ] ) ?
905: $inner[ $idx ] :
906: null;
907: }
908: return null;
909: }
910:
911: /**
912: * Write the field's value to an array structure, using Javascript dotted
913: * object notation to indicate JSON data structure. For example `name.first`
914: * gives the data structure: `name: { first: ... }`. This matches DataTables
915: * own ability to do this on the client-side, although this doesn't
916: * implement implement quite such a complex structure (no array / function
917: * support).
918: *
919: * @param array &$out Array to write the data to
920: * @param string $name Javascript dotted object name to write to
921: * @param mixed $value Value to write
922: * @throws \Exception Information about duplicate properties
923: * @private
924: */
925: private function _writeProp( &$out, $name, $value )
926: {
927: if ( strpos($name, '.') === false ) {
928: $out[ $name ] = $value;
929: return;
930: }
931:
932: $names = explode( '.', $name );
933: $inner = &$out;
934: for ( $i=0 ; $i<count($names)-1 ; $i++ ) {
935: $loopName = $names[$i];
936:
937: if ( ! isset( $inner[ $loopName ] ) ) {
938: $inner[ $loopName ] = array();
939: }
940: else if ( ! is_array( $inner[ $loopName ] ) ) {
941: throw new \Exception(
942: 'A property with the name `'.$name.'` already exists. This '.
943: 'can occur if you have properties which share a prefix - '.
944: 'for example `name` and `name.first`.'
945: );
946: }
947:
948: $inner = &$inner[ $loopName ];
949: }
950:
951: if ( isset( $inner[ $names[count($names)-1] ] ) ) {
952: throw new \Exception(
953: 'Duplicate field detected - a field with the name `'.$name.'` '.
954: 'already exists.'
955: );
956: }
957:
958: $inner[ $names[count($names)-1] ] = $value;
959: }
960: }
961:
962: