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