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