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\Field;
20:
21:
22: /**
23: * Join table class for DataTables Editor.
24: *
25: * The Join class can be used with {@link Editor::join} to allow Editor to
26: * obtain joined information from the database.
27: *
28: * For an overview of how Join tables work, please refer to the
29: * {@link https://editor.datatables.net/manual/php/ Editor manual} as it is
30: * useful to understand how this class represents the links between tables,
31: * before fully getting to grips with it's API.
32: *
33: * @example
34: * Join the parent table (the one specified in the {@link Editor::table}
35: * method) with the table *access*, with a link table *user__access*, which
36: * allows multiple properties to be found for each row in the parent table.
37: * <code>
38: * Join::inst( 'access', 'array' )
39: * ->link( 'users.id', 'user_access.user_id' )
40: * ->link( 'access.id', 'user_access.access_id' )
41: * ->field(
42: * Field::inst( 'id' )->validator( 'Validate::required' ),
43: * Field::inst( 'name' )
44: * )
45: * </code>
46: *
47: * @example
48: * Single row join - here we join the parent table with a single row in
49: * the child table, without an intermediate link table. In this case the
50: * child table is called *extra* and the two fields give the columns that
51: * Editor will read from that table.
52: * <code>
53: * Join::inst( 'extra', 'object' )
54: * ->link( 'user.id', 'extra.user_id' )
55: * ->field(
56: * Field::inst( 'comments' ),
57: * Field::inst( 'review' )
58: * )
59: * </code>
60: */
61: class Join extends DataTables\Ext {
62: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
63: * Constructor
64: */
65:
66: /**
67: * Join instance constructor.
68: * @param string $table Table name to get the joined data from.
69: * @param string $type Work with a single result ('object') or an array of
70: * results ('array') for the join.
71: */
72: function __construct( $table=null, $type='object' )
73: {
74: $this->table( $table );
75: $this->type( $type );
76: }
77:
78:
79: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
80: * Private properties
81: */
82:
83: /** @var DataTables\Editor\Field[] */
84: private $_fields = array();
85:
86: /** @var array */
87: private $_join = array(
88: "parent" => null,
89: "child" => null,
90: "table" => null
91: );
92:
93: /** @var string */
94: private $_table = null;
95:
96: /** @var string */
97: private $_type = null;
98:
99: /** @var string */
100: private $_name = null;
101:
102: /** @var boolean */
103: private $_get = true;
104:
105: /** @var boolean */
106: private $_set = true;
107:
108: /** @var string */
109: private $_aliasParentTable = null;
110:
111: /** @var array */
112: private $_where = array();
113:
114: /** @var boolean */
115: private $_whereSet = false;
116:
117: /** @var array */
118: private $_links = array();
119:
120: /** @var string */
121: private $_customOrder = null;
122:
123:
124: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
125: * Public methods
126: */
127:
128: /**
129: * Get / set parent table alias.
130: *
131: * When working with a self referencing table (i.e. a column in the table contains
132: * a primary key value from its own table) it can be useful to set an alias on the
133: * parent table's name, allowing a self referencing Join. For example:
134: * <code>
135: * SELECT p2.publisher
136: * FROM publisher as p2
137: * JOIN publisher on (publisher.idPublisher = p2.idPublisher)
138: * <code>
139: * Where, in this case, `publisher` is the table that is used by the Editor instance,
140: * and you want to use `Join` to link back to the table (resolving a name for example).
141: * This method allows that alias to be set. Fields can then use standard SQL notation
142: * to select a field, for example `p2.publisher` or `publisher.publisher`.
143: * @param string $_ Table alias to use
144: * @return string|self Table alias set (which is null by default), or self if used as
145: * a setter.
146: */
147: public function aliasParentTable ( $_=null )
148: {
149: return $this->_getSet( $this->_aliasParentTable, $_ );
150: }
151:
152:
153: /**
154: * Get / set field instances.
155: *
156: * The list of fields designates which columns in the table that will be read
157: * from the joined table.
158: * @param Field $_... Instances of the {@link Field} class, given as a single
159: * instance of {@link Field}, an array of {@link Field} instances, or multiple
160: * {@link Field} instance parameters for the function.
161: * @return Field[]|self Array of fields, or self if used as a setter.
162: * @see {@link Field} for field documentation.
163: */
164: public function field ( $_=null )
165: {
166: if ( $_ !== null && !is_array($_) ) {
167: $_ = func_get_args();
168: }
169: return $this->_getSet( $this->_fields, $_, true );
170: }
171:
172:
173: /**
174: * Get / set field instances.
175: *
176: * An alias of {@link field}, for convenience.
177: * @param Field $_... Instances of the {@link Field} class, given as a single
178: * instance of {@link Field}, an array of {@link Field} instances, or multiple
179: * {@link Field} instance parameters for the function.
180: * @return Field[]|self Array of fields, or self if used as a setter.
181: * @see {@link Field} for field documentation.
182: */
183: public function fields ( $_=null )
184: {
185: if ( $_ !== null && !is_array($_) ) {
186: $_ = func_get_args();
187: }
188: return $this->_getSet( $this->_fields, $_, true );
189: }
190:
191:
192: /**
193: * Get / set get attribute.
194: *
195: * When set to false no read operations will occur on the join tables.
196: * @param boolean $_ Value
197: * @return boolean|self Name
198: */
199: public function get ( $_=null )
200: {
201: return $this->_getSet( $this->_get, $_ );
202: }
203:
204:
205: /**
206: * Get / set join properties.
207: *
208: * Define how the SQL will be performed, on what columns. There are
209: * basically two types of join that are supported by Editor here, a direct
210: * foreign key reference in the join table to the parent table's primary
211: * key, or a link table that contains just primary keys for the parent and
212: * child tables (this approach is usually used with a {@link type} of
213: * 'array' since you can often have multiple links between the two tables,
214: * while a direct foreign key reference will typically use a type of
215: * 'object' (i.e. a single entry).
216: *
217: * @param string|string[] $parent Parent table's primary key names. If used
218: * with a link table (i.e. third parameter to this method is given, then
219: * an array should be used, with the first element being the pkey's name
220: * in the parent table, and the second element being the key's name in
221: * the link table.
222: * @param string|string[] $child Child table's primary key names. If used
223: * with a link table (i.e. third parameter to this method is given, then
224: * an array should be used, with the first element being the pkey's name
225: * in the child table, and the second element being the key's name in the
226: * link table.
227: * @param string $table Join table name, if using a link table
228: * @returns Join This for chaining
229: * @deprecated 1.5 Please use the {@link link} method rather than this
230: * method now.
231: */
232: public function join ( $parent=null, $child=null, $table=null )
233: {
234: if ( $parent === null && $child === null ) {
235: return $this->_join;
236: }
237:
238: $this->_join['parent'] = $parent;
239: $this->_join['child'] = $child;
240: $this->_join['table'] = $table;
241: return $this;
242: }
243:
244:
245: /**
246: * Create a join link between two tables. The order of the fields does not
247: * matter, but each field must contain the table name as well as the field
248: * name.
249: *
250: * This method can be called a maximum of two times for an Mjoin instance:
251: *
252: * * First time, creates a link between the Editor host table and a join
253: * table
254: * * Second time creates the links required for a link table.
255: *
256: * Please refer to the Editor Mjoin documentation for further details:
257: * https://editor.datatables.net/manual/php
258: *
259: * @param string $field1 Table and field name
260: * @param string $field2 Table and field name
261: * @return Join Self for chaining
262: * @throws \Exception Link limitations
263: */
264: public function link ( $field1, $field2 )
265: {
266: if ( strpos($field1, '.') === false || strpos($field2, '.') === false ) {
267: throw new \Exception("Link fields must contain both the table name and the column name");
268: }
269:
270: if ( count( $this->_links ) >= 4 ) {
271: throw new \Exception("Link method cannot be called more than twice for a single instance");
272: }
273:
274: $this->_links[] = $field1;
275: $this->_links[] = $field2;
276:
277: return $this;
278: }
279:
280:
281: /**
282: * Specify the property that the data will be sorted by.
283: *
284: * @param string $order SQL column name to order the data by
285: * @return Join Self for chaining
286: */
287: public function order ( $_=null )
288: {
289: // How this works is by setting the SQL order by clause, and since the
290: // join that is done in PHP is always done sequentially, the order is
291: // retained.
292: return $this->_getSet( $this->_customOrder, $_ );
293: }
294:
295:
296: /**
297: * Get / set name.
298: *
299: * The `name` of the Join is the JSON property key that is used when
300: * 'getting' the data, and the HTTP property key (in a JSON style) when
301: * 'setting' data. Typically the name of the db table will be used here,
302: * but this method allows that to be overridden.
303: * @param string $_ Field name
304: * @return String|self Name
305: */
306: public function name ( $_=null )
307: {
308: return $this->_getSet( $this->_name, $_ );
309: }
310:
311:
312: /**
313: * Get / set set attribute.
314: *
315: * When set to false no write operations will occur on the join tables.
316: * This can be useful when you want to display information which is joined,
317: * but want to only perform write operations on the parent table.
318: * @param boolean $_ Value
319: * @return boolean|self Name
320: */
321: public function set ( $_=null )
322: {
323: return $this->_getSet( $this->_set, $_ );
324: }
325:
326:
327: /**
328: * Get / set join table name.
329: *
330: * Please note that this will also set the {@link name} used by the Join
331: * as well. This is for convenience as the JSON output / HTTP input will
332: * typically use the same name as the database name. If you want to set a
333: * custom name, the {@link name} method must be called ***after*** this one.
334: * @param string $_ Name of the table to read the join data from
335: * @return String|self Name of the join table, or self if used as a setter.
336: */
337: public function table ( $_=null )
338: {
339: if ( $_ !== null ) {
340: $this->_name = $_;
341: }
342: return $this->_getSet( $this->_table, $_ );
343: }
344:
345:
346: /**
347: * Get / set the join type.
348: *
349: * The join type allows the data that is returned from the join to be given
350: * as an array (i.e. working with multiple possibly results for each record from
351: * the parent table), or as an object (i.e. working which one and only one result
352: * for each record form the parent table).
353: * @param string $_ Join type ('object') or an array of
354: * results ('array') for the join.
355: * @return String|self Join type, or self if used as a setter.
356: */
357: public function type ( $_=null )
358: {
359: return $this->_getSet( $this->_type, $_ );
360: }
361:
362:
363: /**
364: * Where condition to add to the query used to get data from the database.
365: * Note that this is applied to the child table.
366: *
367: * Can be used in two different ways:
368: *
369: * * Simple case: `where( field, value, operator )`
370: * * Complex: `where( fn )`
371: *
372: * @param string|callable $key Single field name or a closure function
373: * @param string|string[] $value Single field value, or an array of values.
374: * @param string $op Condition operator: <, >, = etc
375: * @return string[]|self Where condition array, or self if used as a setter.
376: */
377: public function where ( $key=null, $value=null, $op='=' )
378: {
379: if ( $key === null ) {
380: return $this->_where;
381: }
382:
383: if ( is_callable($key) && is_object($key) ) {
384: $this->_where[] = $key;
385: }
386: else {
387: $this->_where[] = array(
388: "key" => $key,
389: "value" => $value,
390: "op" => $op
391: );
392: }
393:
394: return $this;
395: }
396:
397:
398: /**
399: * Get / set if the WHERE conditions should be included in the create and
400: * edit actions.
401: *
402: * This means that the fields which have been used as part of the 'get'
403: * WHERE condition (using the `where()` method) will be set as the values
404: * given.
405: *
406: * This is default false (i.e. they are not included).
407: *
408: * @param boolean $_ Include (`true`), or not (`false`)
409: * @return boolean Current value
410: */
411: public function whereSet ( $_=null )
412: {
413: return $this->_getSet( $this->_whereSet, $_ );
414: }
415:
416:
417:
418: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
419: * Internal methods
420: */
421:
422: /**
423: * Get data
424: * @param Editor $editor Host Editor instance
425: * @param string[] $data Data from the parent table's get and were we need
426: * to add out output.
427: * @param array $options options array for fields
428: * @internal
429: */
430: public function data( $editor, &$data, &$options )
431: {
432: if ( ! $this->_get ) {
433: return;
434: }
435:
436: $this->_prep( $editor );
437: $db = $editor->db();
438: $dteTable = $editor->table();
439: $pkey = $editor->pkey();
440: $idPrefix = $editor->idPrefix();
441:
442: $dteTable = $dteTable[0];
443: $dteTableLocal = $this->_aliasParentTable ? // Can be aliased to allow a self join
444: $this->_aliasParentTable :
445: $dteTable;
446: $joinField = isset($this->_join['table']) ?
447: $this->_join['parent'][0] :
448: $this->_join['parent'];
449:
450: // This is something that will likely come in a future version, but it
451: // is a relatively low use feature. Please get in touch if this is
452: // something you require.
453: if ( count( $pkey ) > 1 ) {
454: throw new \Exception("MJoin is not currently supported with a compound primary key for the main table", 1);
455: }
456:
457: if ( count($data) > 0 ) {
458: $pkey = $pkey[0];
459: $pkeyIsJoin = $pkey === $joinField || $pkey === $dteTable.'.'.$joinField;
460:
461: // Sanity check that table selector fields are read only, and have an name without
462: // a dot (for DataTables mData to be able to read it)
463: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
464: $field = $this->_fields[$i];
465:
466: if ( strpos( $field->dbField() , "." ) !== false ) {
467: if ( $field->set() !== Field::SET_NONE && $this->_set ) {
468: echo json_encode( array(
469: "sError" => "Table selected fields (i.e. '{table}.{column}') in `Join` ".
470: "must be read only. Use `set(false)` for the field to disable writing."
471: ) );
472: exit(0);
473: }
474:
475: if ( strpos( $field->name() , "." ) !== false ) {
476: echo json_encode( array(
477: "sError" => "Table selected fields (i.e. '{table}.{column}') in `Join` ".
478: "must have a name alias which does not contain a period ('.'). Use ".
479: "name('---') to set a name for the field"
480: ) );
481: exit(0);
482: }
483: }
484: }
485:
486: // Set up the JOIN query
487: $stmt = $db
488: ->query( 'select' )
489: ->distinct( true )
490: ->get( $dteTableLocal.'.'.$joinField.' as dteditor_pkey' )
491: ->get( $this->_fields('get') )
492: ->table( $dteTable .' as '. $dteTableLocal );
493:
494: if ( $this->order() ) {
495: $stmt->order( $this->order() );
496: }
497:
498: $this->_apply_where( $stmt );
499:
500: if ( isset($this->_join['table']) ) {
501: // Working with a link table
502: $stmt
503: ->join(
504: $this->_join['table'],
505: $dteTableLocal.'.'.$this->_join['parent'][0] .' = '. $this->_join['table'].'.'.$this->_join['parent'][1]
506: )
507: ->join(
508: $this->_table,
509: $this->_table.'.'.$this->_join['child'][0] .' = '. $this->_join['table'].'.'.$this->_join['child'][1]
510: );
511: }
512: else {
513: // No link table in the middle
514: $stmt
515: ->join(
516: $this->_table,
517: $this->_table.'.'.$this->_join['child'] .' = '. $dteTableLocal.'.'.$this->_join['parent']
518: );
519: }
520:
521: // Check that the joining field is available. The joining key can
522: // come from the Editor instance's primary key, or any other field,
523: // including a nested value (from a left join). If the instance's
524: // pkey, then we've got that in the DT_RowId parameter, so we can
525: // use that. Otherwise, the key must be in the field list.
526: if ( $this->_propExists( $dteTable.'.'.$joinField, $data[0] ) ) {
527: $readField = $dteTable.'.'.$joinField;
528: }
529: else if ( $this->_propExists( $joinField, $data[0] ) ) {
530: $readField = $joinField;
531: }
532: else if ( ! $pkeyIsJoin ) {
533: echo json_encode( array(
534: "sError" => "Join was performed on the field '{$joinField}' which was not "
535: ."included in the Editor field list. The join field must be included "
536: ."as a regular field in the Editor instance."
537: ) );
538: exit(0);
539: }
540:
541: // Get list of pkey values and apply as a WHERE IN condition
542: // This is primarily useful in server-side processing mode and when filtering
543: // the table as it means only a sub-set will be selected
544: // This is only applied for "sensible" data sets. It will just complicate
545: // matters for really large data sets:
546: // https://stackoverflow.com/questions/21178390/in-clause-limitation-in-sql-server
547: if ( count($data) < 1000 ) {
548: $whereIn = array();
549:
550: for ( $i=0 ; $i<count($data) ; $i++ ) {
551: $whereIn[] = $pkeyIsJoin ?
552: str_replace( $idPrefix, '', $data[$i]['DT_RowId'] ) :
553: $this->_readProp( $readField, $data[$i] );
554: }
555:
556: $stmt->where_in( $dteTableLocal.'.'.$joinField, $whereIn );
557: }
558:
559: // Go!
560: $res = $stmt->exec();
561: if ( ! $res ) {
562: return;
563: }
564:
565: // Map to primary key for fast lookup
566: $join = array();
567: while ( $row=$res->fetch() ) {
568: $inner = array();
569:
570: for ( $j=0 ; $j<count($this->_fields) ; $j++ ) {
571: $field = $this->_fields[$j];
572: if ( $field->apply('get') ) {
573: $inner[ $field->name() ] = $field->val('get', $row);
574: }
575: }
576:
577: if ( $this->_type === 'object' ) {
578: $join[ $row['dteditor_pkey'] ] = $inner;
579: }
580: else {
581: if ( !isset( $join[ $row['dteditor_pkey'] ] ) ) {
582: $join[ $row['dteditor_pkey'] ] = array();
583: }
584: $join[ $row['dteditor_pkey'] ][] = $inner;
585: }
586: }
587:
588: // Loop over the data and do a join based on the data available
589: for ( $i=0 ; $i<count($data) ; $i++ ) {
590: $rowPKey = $pkeyIsJoin ?
591: str_replace( $idPrefix, '', $data[$i]['DT_RowId'] ) :
592: $this->_readProp( $readField, $data[$i] );
593:
594: if ( isset( $join[$rowPKey] ) ) {
595: $data[$i][ $this->_name ] = $join[$rowPKey];
596: }
597: else {
598: $data[$i][ $this->_name ] = ($this->_type === 'object') ?
599: (object)array() : array();
600: }
601: }
602: }
603:
604: // Field options
605: foreach ($this->_fields as $field) {
606: $opts = $field->optionsExec( $db );
607:
608: if ( $opts !== false ) {
609: $name = $this->name().
610: ($this->_type === 'object' ? '.' : '[].').
611: $field->name();
612: $options[ $name ] = $opts;
613: }
614: }
615: }
616:
617:
618: /**
619: * Create a row.
620: * @param Editor $editor Host Editor instance
621: * @param int $parentId Parent row's primary key value
622: * @param string[] $data Data to be set for the join
623: * @internal
624: */
625: public function create ( $editor, $parentId, $data )
626: {
627: // If not settable, or the many count for the join was not submitted
628: // there we do nothing
629: if (
630: ! $this->_set ||
631: ! isset($data[$this->_name]) ||
632: ! isset($data[$this->_name.'-many-count'])
633: ) {
634: return;
635: }
636:
637: $this->_prep( $editor );
638: $db = $editor->db();
639:
640: if ( $this->_type === 'object' ) {
641: $this->_insert( $db, $parentId, $data[$this->_name] );
642: }
643: else {
644: for ( $i=0 ; $i<count($data[$this->_name]) ; $i++ ) {
645: $this->_insert( $db, $parentId, $data[$this->_name][$i] );
646: }
647: }
648: }
649:
650:
651: /**
652: * Update a row.
653: * @param Editor $editor Host Editor instance
654: * @param int $parentId Parent row's primary key value
655: * @param string[] $data Data to be set for the join
656: * @internal
657: */
658: public function update ( $editor, $parentId, $data )
659: {
660: // If not settable, or the many count for the join was not submitted
661: // there we do nothing
662: if ( ! $this->_set || ! isset($data[$this->_name.'-many-count']) ) {
663: return;
664: }
665:
666: $this->_prep( $editor );
667: $db = $editor->db();
668:
669: if ( $this->_type === 'object' ) {
670: // update or insert
671: $this->_update_row( $db, $parentId, $data[$this->_name] );
672: }
673: else {
674: // WARNING - this will remove rows and then readd them. Any
675: // data not in the field list WILL BE LOST
676: // todo - is there a better way of doing this?
677: $this->remove( $editor, array($parentId) );
678: $this->create( $editor, $parentId, $data );
679: }
680: }
681:
682:
683: /**
684: * Delete rows
685: * @param Editor $editor Host Editor instance
686: * @param int[] $ids Parent row IDs to delete
687: * @internal
688: */
689: public function remove ( $editor, $ids )
690: {
691: if ( ! $this->_set ) {
692: return;
693: }
694:
695: $that = $this;
696: $this->_prep( $editor );
697: $db = $editor->db();
698:
699: if ( isset($this->_join['table']) ) {
700: $stmt = $db
701: ->query( 'delete' )
702: ->table( $this->_join['table'] )
703: ->or_where( $this->_join['parent'][1], $ids )
704: ->exec();
705: }
706: else {
707: $stmt = $db
708: ->query( 'delete' )
709: ->table( $this->_table )
710: ->where_group( function ( $q ) use ( $that, $ids ) {
711: $q->or_where( $that->_join['child'], $ids );
712: } );
713:
714: $this->_apply_where( $stmt );
715:
716: $stmt->exec();
717: }
718: }
719:
720:
721: /**
722: * Validate input data
723: *
724: * @param array $errors Errors array
725: * @param Editor $editor Editor instance
726: * @param string[] $data Data to validate
727: * @internal
728: */
729: public function validate ( &$errors, $editor, $data )
730: {
731: if ( ! $this->_set || ! isset($data[$this->_name]) ) {
732: return;
733: }
734:
735: $this->_prep( $editor );
736:
737: $joinData = $data[$this->_name];
738:
739: if ( $this->_type === 'object' ) {
740: $this->_validateFields( $errors, $editor, $joinData, $this->_name.'.' );
741: }
742: else {
743: for ( $i=0 ; $i<count($joinData) ; $i++ ) {
744: $this->_validateFields( $errors, $editor, $joinData[$i], $this->_name.'[].' );
745: }
746: }
747: }
748:
749:
750:
751: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
752: * Private methods
753: */
754:
755: /**
756: * Add local WHERE condition to query
757: * @param \DataTables\Database\Query $query Query instance to apply the WHERE conditions to
758: * @private
759: */
760: private function _apply_where ( $query )
761: {
762: for ( $i=0 ; $i<count($this->_where) ; $i++ ) {
763: if ( is_callable( $this->_where[$i] ) ) {
764: $this->_where[$i]( $query );
765: }
766: else {
767: $query->where(
768: $this->_where[$i]['key'],
769: $this->_where[$i]['value'],
770: $this->_where[$i]['op']
771: );
772: }
773: }
774: }
775:
776:
777: /**
778: * Create a row.
779: * @param \DataTables\Database $db Database reference to use
780: * @param int $parentId Parent row's primary key value
781: * @param string[] $data Data to be set for the join
782: * @private
783: */
784: private function _insert( $db, $parentId, $data )
785: {
786: if ( isset($this->_join['table']) ) {
787: // Insert keys into the join table
788: $stmt = $db
789: ->query('insert')
790: ->table( $this->_join['table'] )
791: ->set( $this->_join['parent'][1], $parentId )
792: ->set( $this->_join['child'][1], $data[$this->_join['child'][0]] )
793: ->exec();
794: }
795: else {
796: // Insert values into the target table
797: $stmt = $db
798: ->query('insert')
799: ->table( $this->_table )
800: ->set( $this->_join['child'], $parentId );
801:
802: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
803: $field = $this->_fields[$i];
804:
805: if ( $field->apply( 'set', $data ) ) { // TODO should be create or edit
806: $stmt->set( $field->dbField(), $field->val('set', $data) );
807: }
808: }
809:
810: // If the where condition variables should also be added to the database
811: // Note that `whereSet` is now deprecated
812: if ( $this->_whereSet ) {
813: for ( $i=0, $ien=count($this->_where) ; $i<$ien ; $i++ ) {
814: if ( ! is_callable( $this->_where[$i] ) ) {
815: $stmt->set( $this->_where[$i]['key'], $this->_where[$i]['value'] );
816: }
817: }
818: }
819:
820: $stmt->exec();
821: }
822: }
823:
824:
825: /**
826: * Prepare the instance to be run.
827: *
828: * @param Editor $editor Editor instance
829: * @private
830: */
831: private function _prep ( $editor )
832: {
833: $links = $this->_links;
834:
835: // Were links used to configure this instance - if so, we need to
836: // back them onto the join array
837: if ( $this->_join['parent'] === null && count($links) ) {
838: $editorTable = $editor->table();
839: $editorTable = $editorTable[0];
840: $joinTable = $this->table();
841:
842: if ( $this->_aliasParentTable ) {
843: $editorTable = $this->_aliasParentTable;
844: }
845:
846: if ( count( $links ) === 2 ) {
847: // No link table
848: $f1 = explode( '.', $links[0] );
849: $f2 = explode( '.', $links[1] );
850:
851: if ( $f1[0] === $editorTable ) {
852: $this->_join['parent'] = $f1[1];
853: $this->_join['child'] = $f2[1];
854: }
855: else {
856: $this->_join['parent'] = $f2[1];
857: $this->_join['child'] = $f1[1];
858: }
859: }
860: else {
861: // Link table
862: $f1 = explode( '.', $links[0] );
863: $f2 = explode( '.', $links[1] );
864: $f3 = explode( '.', $links[2] );
865: $f4 = explode( '.', $links[3] );
866:
867: // Discover the name of the link table
868: if ( $f1[0] !== $editorTable && $f1[0] !== $joinTable ) {
869: $this->_join['table'] = $f1[0];
870: }
871: else if ( $f2[0] !== $editorTable && $f2[0] !== $joinTable ) {
872: $this->_join['table'] = $f2[0];
873: }
874: else if ( $f3[0] !== $editorTable && $f3[0] !== $joinTable ) {
875: $this->_join['table'] = $f3[0];
876: }
877: else {
878: $this->_join['table'] = $f4[0];
879: }
880:
881: $this->_join['parent'] = array( $f1[1], $f2[1] );
882: $this->_join['child'] = array( $f3[1], $f4[1] );
883: }
884: }
885: }
886:
887:
888: /**
889: * Update a row.
890: * @param \DataTables\Database $db Database reference to use
891: * @param int $parentId Parent row's primary key value
892: * @param string[] $data Data to be set for the join
893: * @private
894: */
895: private function _update_row ( $db, $parentId, $data )
896: {
897: if ( isset($this->_join['table']) ) {
898: // Got a link table, just insert the pkey references
899: $db->push(
900: $this->_join['table'],
901: array(
902: $this->_join['parent'][1] => $parentId,
903: $this->_join['child'][1] => $data[$this->_join['child'][0]]
904: ),
905: array(
906: $this->_join['parent'][1] => $parentId
907: )
908: );
909: }
910: else {
911: // No link table, just a direct reference
912: $set = array(
913: $this->_join['child'] => $parentId
914: );
915:
916: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
917: $field = $this->_fields[$i];
918:
919: if ( $field->apply( 'set', $data ) ) {
920: $set[ $field->dbField() ] = $field->val('set', $data);
921: }
922: }
923:
924: // Add WHERE conditions
925: $where = array($this->_join['child'] => $parentId);
926: for ( $i=0, $ien=count($this->_where) ; $i<$ien ; $i++ ) {
927: $where[ $this->_where[$i]['key'] ] = $this->_where[$i]['value'];
928:
929: // Is there any point in this? Is there any harm?
930: // Note that `whereSet` is now deprecated
931: if ( $this->_whereSet ) {
932: if ( ! is_callable( $this->_where[$i] ) ) {
933: $set[ $this->_where[$i]['key'] ] = $this->_where[$i]['value'];
934: }
935: }
936: }
937:
938: $db->push( $this->_table, $set, $where );
939: }
940: }
941:
942:
943: /**
944: * Create an SQL string from the fields that this instance knows about for
945: * using in queries
946: * @param string $direction Direction: 'get' or 'set'.
947: * @returns array Fields to include
948: * @private
949: */
950: private function _fields ( $direction )
951: {
952: $fields = array();
953:
954: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
955: $field = $this->_fields[$i];
956:
957: if ( $field->apply( $direction, null ) ) {
958: if ( strpos( $field->dbField() , "." ) === false ) {
959: $fields[] = $this->_table.'.'.$field->dbField() ." as ".$field->dbField();;
960: }
961: else {
962: $fields[] = $field->dbField();// ." as ".$field->dbField();
963: }
964: }
965: }
966:
967: return $fields;
968: }
969:
970:
971: /**
972: * Validate input data
973: *
974: * @param array $errors Errors array
975: * @param Editor $editor Editor instance
976: * @param string[] $data Data to validate
977: * @param string $prefix Field error prefix for client-side to show the
978: * error message on the appropriate field
979: * @internal
980: */
981: private function _validateFields ( &$errors, $editor, $data, $prefix )
982: {
983: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
984: $field = $this->_fields[$i];
985: $validation = $field->validate( $data, $editor );
986:
987: if ( $validation !== true ) {
988: $errors[] = array(
989: "name" => $prefix.$field->name(),
990: "status" => $validation
991: );
992: }
993: }
994: }
995: }
996:
997: