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: * @version 1.5.6
9: * @copyright 2012 SpryMedia ( http://sprymedia.co.uk )
10: * @license http://editor.datatables.net/license DataTables Editor
11: * @link http://editor.datatables.net
12: */
13:
14: namespace DataTables;
15: if (!defined('DATATABLES')) exit();
16:
17: use
18: DataTables,
19: DataTables\Editor\Join,
20: DataTables\Editor\Field;
21:
22:
23: /**
24: * DataTables Editor base class for creating editable tables.
25: *
26: * Editor class instances are capable of servicing all of the requests that
27: * DataTables and Editor will make from the client-side - specifically:
28: *
29: * * Get data
30: * * Create new record
31: * * Edit existing record
32: * * Delete existing records
33: *
34: * The Editor instance is configured with information regarding the
35: * database table fields that you which to make editable, and other information
36: * needed to read and write to the database (table name for example!).
37: *
38: * This documentation is very much focused on describing the API presented
39: * by these DataTables Editor classes. For a more general overview of how
40: * the Editor class is used, and how to install Editor on your server, please
41: * refer to the {@link http://editor.datatables.net/manual Editor manual}.
42: *
43: * @example
44: * A very basic example of using Editor to create a table with four fields.
45: * This is all that is needed on the server-side to create a editable
46: * table - the {@link process} method determines what action DataTables /
47: * Editor is requesting from the server-side and will correctly action it.
48: * <code>
49: * Editor::inst( $db, 'browsers' )
50: * ->fields(
51: * Field::inst( 'first_name' )->validator( 'Validate::required' ),
52: * Field::inst( 'last_name' )->validator( 'Validate::required' ),
53: * Field::inst( 'country' ),
54: * Field::inst( 'details' )
55: * )
56: * ->process( $_POST )
57: * ->json();
58: * </code>
59: */
60: class Editor extends Ext {
61: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
62: * Statics
63: */
64:
65: /** Request type - read */
66: const ACTION_READ = 'read';
67:
68: /** Request type - create */
69: const ACTION_CREATE = 'create';
70:
71: /** Request type - edit */
72: const ACTION_EDIT = 'edit';
73:
74: /** Request type - delete */
75: const ACTION_DELETE = 'delete';
76:
77: /** Request type - upload */
78: const ACTION_UPLOAD = 'upload';
79:
80:
81: /**
82: * Determine the request type from an HTTP request.
83: *
84: * @param array $http Typically $_POST, but can be any array used to carry
85: * an Editor payload
86: * @return string `Editor::ACTION_READ`, `Editor::ACTION_CREATE`,
87: * `Editor::ACTION_EDIT` or `Editor::ACTION_DELETE` indicating the request
88: * type.
89: */
90: static public function action ( $http )
91: {
92: if ( ! isset( $http['action'] ) ) {
93: return self::ACTION_READ;
94: }
95:
96: switch ( $http['action'] ) {
97: case 'create':
98: return self::ACTION_CREATE;
99:
100: case 'edit':
101: return self::ACTION_EDIT;
102:
103: case 'remove':
104: return self::ACTION_DELETE;
105:
106: case 'upload':
107: return self::ACTION_UPLOAD;
108:
109: default:
110: throw new \Exception("Unknown Editor action: ".$http['action']);
111: }
112: }
113:
114:
115: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
116: * Constructor
117: */
118:
119: /**
120: * Constructor.
121: * @param Database $db An instance of the DataTables Database class that we can
122: * use for the DB connection. Can be given here or with the 'db' method.
123: * <code>
124: * 456
125: * </code>
126: * @param string|array $table The table name in the database to read and write
127: * information from and to. Can be given here or with the 'table' method.
128: * @param string $pkey Primary key column name in the table given in the $table
129: * parameter. Can be given here or with the 'pkey' method.
130: */
131: function __construct( $db=null, $table=null, $pkey=null )
132: {
133: // Set constructor parameters using the API - note that the get/set will
134: // ignore null values if they are used (i.e. not passed in)
135: $this->db( $db );
136: $this->table( $table );
137: $this->pkey( $pkey );
138: }
139:
140:
141: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
142: * Public properties
143: */
144:
145: /** @var string */
146: public $version = '1.5.6';
147:
148:
149:
150: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
151: * Private properties
152: */
153:
154: /** @var DataTables\Database */
155: private $_db = null;
156:
157: /** @var DataTables\Editor\Field[] */
158: private $_fields = array();
159:
160: /** @var array */
161: private $_formData;
162:
163: /** @var array */
164: private $_processData;
165:
166: /** @var string */
167: private $_idPrefix = 'row_';
168:
169: /** @var DataTables\Editor\Join[] */
170: private $_join = array();
171:
172: /** @var string */
173: private $_pkey = 'id';
174:
175: /** @var string[] */
176: private $_table = array();
177:
178: /** @var boolean */
179: private $_transaction = true;
180:
181: /** @var array */
182: private $_where = array();
183:
184: /** @var array */
185: private $_leftJoin = array();
186:
187: /** @var boolean - deprecated */
188: private $_whereSet = false;
189:
190: /** @var array */
191: private $_out = array(
192: "fieldErrors" => array(),
193: "error" => "",
194: "data" => array(),
195: "ipOpts" => array()
196: );
197:
198: /** @var array */
199: private $_events = array();
200:
201:
202:
203: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
204: * Public methods
205: */
206:
207: /**
208: * Get the data constructed in this instance.
209: *
210: * This will get the PHP array of data that has been constructed for the
211: * command that has been processed by this instance. Therefore only useful after
212: * process has been called.
213: * @return array Processed data array.
214: */
215: public function data ()
216: {
217: return $this->_out;
218: }
219:
220:
221: /**
222: * Get / set the DB connection instance
223: * @param Database $_ DataTable's Database class instance to use for database
224: * connectivity. If not given, then used as a getter.
225: * @return Database|self The Database connection instance if no parameter
226: * is given, or self if used as a setter.
227: */
228: public function db ( $_=null )
229: {
230: return $this->_getSet( $this->_db, $_ );
231: }
232:
233:
234: /**
235: * Get / set field instance.
236: *
237: * The list of fields designates which columns in the table that Editor will work
238: * with (both get and set).
239: * @param Field|string $_... This parameter effects the return value of the
240: * function:
241: *
242: * * `null` - Get an array of all fields assigned to the instance
243: * * `string` - Get a specific field instance whose 'name' matches the
244: * field passed in
245: * * {@link Field} - Add a field to the instance's list of fields. This
246: * can be as many fields as required (i.e. multiple arguments)
247: * * `array` - An array of {@link Field} instances to add to the list
248: * of fields.
249: * @return Field|Field[]|Editor The selected field, an array of fields, or
250: * the Editor instance for chaining, depending on the input parameter.
251: * @throws \Exception Unkown field error
252: * @see {@link Field} for field documentation.
253: */
254: public function field ( $_=null )
255: {
256: if ( is_string( $_ ) ) {
257: for ( $i=0, $ien=count($this->_fields) ; $i<$ien ; $i++ ) {
258: if ( $this->_fields[$i]->name() === $_ ) {
259: return $this->_fields[$i];
260: }
261: }
262:
263: throw new \Exception('Unknown field: '.$_);
264: }
265:
266: if ( $_ !== null && !is_array($_) ) {
267: $_ = func_get_args();
268: }
269: return $this->_getSet( $this->_fields, $_, true );
270: }
271:
272:
273: /**
274: * Get / set field instances.
275: *
276: * An alias of {@link field}, for convenience.
277: * @param Field $_... Instances of the {@link Field} class, given as a single
278: * instance of {@link Field}, an array of {@link Field} instances, or multiple
279: * {@link Field} instance parameters for the function.
280: * @return Field[]|self Array of fields, or self if used as a setter.
281: * @see {@link Field} for field documentation.
282: */
283: public function fields ( $_=null )
284: {
285: if ( $_ !== null && !is_array($_) ) {
286: $_ = func_get_args();
287: }
288: return $this->_getSet( $this->_fields, $_, true );
289: }
290:
291:
292: /**
293: * Get / set the DOM prefix.
294: *
295: * Typically primary keys are numeric and this is not a valid ID value in an
296: * HTML document - is also increases the likelihood of an ID clash if multiple
297: * tables are used on a single page. As such, a prefix is assigned to the
298: * primary key value for each row, and this is used as the DOM ID, so Editor
299: * can track individual rows.
300: * @param string $_ Primary key's name. If not given, then used as a getter.
301: * @return string|self Primary key value if no parameter is given, or
302: * self if used as a setter.
303: */
304: public function idPrefix ( $_=null )
305: {
306: return $this->_getSet( $this->_idPrefix, $_ );
307: }
308:
309:
310: /**
311: * Get the data that is being processed by the Editor instance. This is only
312: * useful once the `process()` method has been called, and is available for
313: * use in validation and formatter methods.
314: *
315: * @return array Data given to `process()`.
316: */
317: public function inData ()
318: {
319: return $this->_processData;
320: }
321:
322:
323: /**
324: * Get / set join instances. Note that for the majority of use cases you
325: * will want to use the `leftJoin()` method. It is significantly easier
326: * to use if you are just doing a simple left join!
327: *
328: * The list of Join instances that Editor will join the parent table to
329: * (i.e. the one that the {@link table} and {@link fields} methods refer to
330: * in this class instance).
331: *
332: * @param Join $_,... Instances of the {@link Join} class, given as a
333: * single instance of {@link Join}, an array of {@link Join} instances,
334: * or multiple {@link Join} instance parameters for the function.
335: * @return Join[]|self Array of joins, or self if used as a setter.
336: * @see {@link Join} for joining documentation.
337: */
338: public function join ( $_=null )
339: {
340: if ( $_ !== null && !is_array($_) ) {
341: $_ = func_get_args();
342: }
343: return $this->_getSet( $this->_join, $_, true );
344: }
345:
346:
347: /**
348: * Get the JSON for the data constructed in this instance.
349: *
350: * Basically the same as the {@link data} method, but in this case we echo, or
351: * return the JSON string of the data.
352: * @param boolean $print Echo the JSON string out (true, default) or return it
353: * (false).
354: * @return string|self self if printing the JSON, or JSON representation of
355: * the processed data if false is given as the first parameter.
356: */
357: public function json ( $print=true )
358: {
359: if ( $print ) {
360: echo json_encode( $this->_out );
361: return $this;
362: }
363: return json_encode( $this->_out );
364: }
365:
366:
367: /**
368: * Echo out JSONP for the data constructed and processed in this instance.
369: * This is basically the same as {@link json} but wraps the return in a
370: * JSONP callback.
371: *
372: * @param string $callback The callback function name to use. If not given
373: * or `null`, then `$_GET['callback']` is used (the jQuery default).
374: * @return self Self for chaining.
375: * @throws \Exception JSONP function name validation
376: */
377: public function jsonp ( $callback=null )
378: {
379: if ( ! $callback ) {
380: $callback = $_GET['callback'];
381: }
382:
383: if ( preg_match('/[^a-zA-Z0-9_]/', $callback) ) {
384: throw new \Exception("Invalid JSONP callback function name");
385: }
386:
387: echo $callback.'('.json_encode( $this->_out ).');';
388: return $this;
389: }
390:
391:
392: /**
393: * Add a left join condition to the Editor instance, allowing it to operate
394: * over multiple tables. Multiple `leftJoin()` calls can be made for a
395: * single Editor instance to join multiple tables.
396: *
397: * A left join is the most common type of join that is used with Editor
398: * so this method is provided to make its use very easy to configure. Its
399: * parameters are basically the same as writing an SQL left join statement,
400: * but in this case Editor will handle the create, update and remove
401: * requirements of the join for you:
402: *
403: * * Create - On create Editor will insert the data into the primary table
404: * and then into the joined tables - selecting the required data for each
405: * table.
406: * * Edit - On edit Editor will update the main table, and then either
407: * update the existing rows in the joined table that match the join and
408: * edit conditions, or insert a new row into the joined table if required.
409: * * Remove - On delete Editor will remove the main row and then loop over
410: * each of the joined tables and remove the joined data matching the join
411: * link from the main table.
412: *
413: * Please note that when using join tables, Editor requires that you fully
414: * qualify each field with the field's table name. SQL can result table
415: * names for ambiguous field names, but for Editor to provide its full CRUD
416: * options, the table name must also be given. For example the field
417: * `first_name` in the table `users` would be given as `users.first_name`.
418: *
419: * @param string $table Table name to do a join onto
420: * @param string $field1 Field from the parent table to use as the join link
421: * @param string $operator Join condition (`=`, '<`, etc)
422: * @param string $field2 Field from the child table to use as the join link
423: * @return self Self for chaining.
424: *
425: * @example
426: * Simple join:
427: * <code>
428: * ->field(
429: * Field::inst( 'users.first_name as myField' ),
430: * Field::inst( 'users.last_name' ),
431: * Field::inst( 'users.dept_id' ),
432: * Field::inst( 'dept.name' )
433: * )
434: * ->leftJoin( 'dept', 'users.dept_id', '=', 'dept.id' )
435: * ->process($_POST)
436: * ->json();
437: * </code>
438: *
439: * This is basically the same as the following SQL statement:
440: *
441: * <code>
442: * SELECT users.first_name, users.last_name, user.dept_id, dept.name
443: * FROM users
444: * LEFT JOIN dept ON users.dept_id = dept.id
445: * </code>
446: */
447: public function leftJoin ( $table, $field1, $operator, $field2 )
448: {
449: $this->_leftJoin[] = array(
450: "table" => $table,
451: "field1" => $field1,
452: "field2" => $field2,
453: "operator" => $operator
454: );
455:
456: return $this;
457: }
458:
459:
460: /**
461: * Add an event listener. The `Editor` class will trigger an number of
462: * events that some action can be taken on.
463: *
464: * @param [type] $name Event name
465: * @param [type] $callback Callback function to execute when the event
466: * occurs
467: * @return self Self for chaining.
468: */
469: public function on ( $name, $callback )
470: {
471: if ( ! isset( $this->_events[ $name ] ) ) {
472: $this->_events[ $name ] = array();
473: }
474:
475: $this->_events[ $name ][] = $callback;
476:
477: return $this;
478: }
479:
480:
481: /**
482: * Get / set the table name.
483: *
484: * The table name designated which DB table Editor will use as its data
485: * source for working with the database. Table names can be given with an
486: * alias, which can be used to simplify larger table names. The field
487: * names would also need to reflect the alias, just like an SQL query. For
488: * example: `users as a`.
489: *
490: * @param string|array $_,... Table names given as a single string, an array of
491: * strings or multiple string parameters for the function.
492: * @return string[]|self Array of tables names, or self if used as a setter.
493: */
494: public function table ( $_=null )
495: {
496: if ( $_ !== null && !is_array($_) ) {
497: $_ = func_get_args();
498: }
499: return $this->_getSet( $this->_table, $_, true );
500: }
501:
502:
503: /**
504: * Get / set transaction support.
505: *
506: * When enabled (which it is by default) Editor will use an SQL transaction
507: * to ensure data integrity while it is performing operations on the table.
508: * This can be optionally disabled using this method, if required by your
509: * database configuration.
510: * @param boolean $_ Enable (`true`) or disabled (`false`) transactions.
511: * If not given, then used as a getter.
512: * @return boolean|self Transactions enabled flag, or self if used as a
513: * setter.
514: */
515: public function transaction ( $_=null )
516: {
517: return $this->_getSet( $this->_transaction, $_ );
518: }
519:
520:
521: /**
522: * Get / set the primary key.
523: *
524: * The primary key must be known to Editor so it will know which rows are being
525: * edited / deleted upon those actions. The default value is 'id'.
526: * @param string $_ Primary key's name. If not given, then used as a getter.
527: * @return string|self Primary key value if no parameter is given, or
528: * self if used as a setter.
529: */
530: public function pkey ( $_=null )
531: {
532: return $this->_getSet( $this->_pkey, $_ );
533: }
534:
535:
536: /**
537: * Process a request from the Editor client-side to get / set data.
538: * @param array $data Typically $_POST or $_GET as required by what is sent by Editor
539: * @return self
540: */
541: public function process ( $data )
542: {
543: $this->_processData = $data;
544: $this->_formData = isset($data['data']) ? $data['data'] : null;
545:
546: if ( $this->_transaction ) {
547: $this->_db->transaction();
548: }
549:
550: try {
551: $this->_prepJoin();
552:
553: if ( !isset($data['action']) ) {
554: /* Get data */
555: $this->_out = array_merge( $this->_out, $this->_get( null, $data ) );
556: }
557: else if ( $data['action'] == "upload" ) {
558: /* File upload */
559: $this->_upload( $data );
560: }
561: else if ( $data['action'] == "remove" ) {
562: /* Remove rows */
563: $this->_remove( $data );
564: $this->_fileClean();
565: }
566: else {
567: /* Create or edit row */
568: // Pre events so they can occur before the validation
569: foreach ($data['data'] as $id => $values) {
570: if ( $data['action'] == 'create' ) {
571: $this->_trigger( 'preCreate', $values );
572: }
573: else {
574: $id = str_replace( $this->_idPrefix, '', $id );
575: $this->_trigger( 'preEdit', $id, $values );
576: }
577: }
578:
579: // Validation
580: $valid = $this->validate( $this->_out['fieldErrors'], $data );
581:
582: // Global validation - if you want global validation - do it here
583: // $this->_out['error'] = "";
584:
585: if ( $valid ) {
586: foreach ($data['data'] as $id => $values) {
587: $d = $data['action'] == "create" ?
588: $this->_insert( $values ) :
589: $this->_update( $id, $values );
590:
591: if ( $d !== null ) {
592: $this->_out['data'][] = $d;
593: }
594: }
595: }
596:
597: $this->_fileClean();
598: }
599:
600: if ( $this->_transaction ) {
601: $this->_db->commit();
602: }
603: }
604: catch (\Exception $e) {
605: // Error feedback
606: $this->_out['error'] = $e->getMessage();
607:
608: if ( $this->_transaction ) {
609: $this->_db->rollback();
610: }
611: }
612:
613: // Tidy up the reply
614: if ( count( $this->_out['fieldErrors'] ) === 0 ) {
615: unset( $this->_out['fieldErrors'] );
616: }
617:
618: if ( $this->_out['error'] === '' ) {
619: unset( $this->_out['error'] );
620: }
621:
622: if ( count( $this->_out['ipOpts'] ) === 0 ) {
623: unset( $this->_out['ipOpts'] );
624: }
625:
626: return $this;
627: }
628:
629:
630: /**
631: * Perform validation on a data set.
632: *
633: * Note that validation is performed on data only when the action is
634: * `create` or `edit`. Additionally, validation is performed on the _wire
635: * data_ - i.e. that which is submitted from the client, without formatting.
636: * Any formatting required by `setFormatter` is performed after the data
637: * from the client has been validated.
638: *
639: * @param &array $errors Output array to which field error information will
640: * be written. Each element in the array represents a field in an error
641: * condition. These elements are themselves arrays with two properties
642: * set; `name` and `status`.
643: * @param array $data The format data to check
644: * @return boolean `true` if the data is valid, `false` if not.
645: */
646: public function validate ( &$errors, $data )
647: {
648: // Validation is only performed on create and edit
649: if ( $data['action'] != "create" && $data['action'] != "edit" ) {
650: return true;
651: }
652:
653: foreach( $data['data'] as $id => $values ) {
654: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
655: $field = $this->_fields[$i];
656: $validation = $field->validate( $values, $this,
657: str_replace( $this->idPrefix(), '', $id )
658: );
659:
660: if ( $validation !== true ) {
661: $errors[] = array(
662: "name" => $field->name(),
663: "status" => $validation
664: );
665: }
666: }
667:
668: // MJoin validation
669: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
670: $this->_join[$i]->validate( $errors, $this, $values );
671: }
672: }
673:
674: return count( $errors ) > 0 ? false : true;
675: }
676:
677:
678: /**
679: * Where condition to add to the query used to get data from the database.
680: *
681: * Can be used in two different ways:
682: *
683: * * Simple case: `where( field, value, operator )`
684: * * Complex: `where( fn )`
685: *
686: * The simple case is fairly self explanatory, a condition is applied to the
687: * data that looks like `field operator value` (e.g. `name = 'Allan'`). The
688: * complex case allows full control over the query conditions by providing a
689: * closure function that has access to the database Query that Editor is
690: * using, so you can use the `where()`, `or_where()`, `and_where()` and
691: * `where_group()` methods as you require.
692: *
693: * Please be very careful when using this method! If an edit made by a user
694: * using Editor removes the row from the where condition, the result is
695: * undefined (since Editor expects the row to still be available, but the
696: * condition removes it from the result set).
697: *
698: * @param string|callable $key Single field name or a closure function
699: * @param string $value Single field value.
700: * @param string $op Condition operator: <, >, = etc
701: * @return string[]|self Where condition array, or self if used as a setter.
702: */
703: public function where ( $key=null, $value=null, $op='=' )
704: {
705: if ( $key === null ) {
706: return $this->_where;
707: }
708:
709: if ( is_callable($key) && is_object($key) ) {
710: $this->_where[] = $key;
711: }
712: else {
713: $this->_where[] = array(
714: "key" => $key,
715: "value" => $value,
716: "op" => $op
717: );
718: }
719:
720: return $this;
721: }
722:
723:
724: /**
725: * Get / set if the WHERE conditions should be included in the create and
726: * edit actions.
727: *
728: * @param boolean $_ Include (`true`), or not (`false`)
729: * @return boolean Current value
730: * @deprecated Note that `whereSet` is now deprecated and replaced with the
731: * ability to set values for columns on create and edit. The C# libraries
732: * do not support this option at all.
733: */
734: public function whereSet ( $_=null )
735: {
736: return $this->_getSet( $this->_whereSet, $_ );
737: }
738:
739:
740:
741: /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
742: * Private methods
743: */
744:
745: /**
746: * Get an array of objects from the database to be given to DataTables as a
747: * result of an sAjaxSource request, such that DataTables can display the information
748: * from the DB in the table.
749: *
750: * @param integer $id Primary key value to get an individual row (after create or
751: * update operations). Gets the full set if not given.
752: * @param array $http HTTP parameters from GET or POST request (so we can service
753: * server-side processing requests from DataTables).
754: * @return array DataTables get information
755: * @throws \Exception Error on SQL execution
756: * @private
757: */
758: private function _get( $id=null, $http=null )
759: {
760: $query = $this->_db
761: ->query('select')
762: ->table( $this->_table )
763: ->get( $this->_pkey );
764:
765: // Add all fields that we need to get from the database
766: foreach ($this->_fields as $field) {
767: if ( $field->apply('get') && $field->getValue() === null ) {
768: $query->get( $field->dbField() );
769: }
770: }
771:
772: $this->_get_where( $query );
773: $this->_perform_left_join( $query );
774: $ssp = $this->_ssp_query( $query, $http );
775:
776: if ( $id !== null ) {
777: $query->where( $this->_pkey, $id );
778: }
779:
780: $res = $query->exec();
781: if ( ! $res ) {
782: throw new \Exception('Error executing SQL for data get');
783: }
784:
785: $out = array();
786: while ( $row=$res->fetch() ) {
787: $inner = array();
788: $inner['DT_RowId'] = $this->_idPrefix . $row[ $this->_pkey ];
789:
790: foreach ($this->_fields as $field) {
791: if ( $field->apply('get') ) {
792: $field->write( $inner, $row );
793: }
794: }
795:
796: $out[] = $inner;
797: }
798:
799: // Field options
800: $options = array();
801:
802: foreach ($this->_fields as $field) {
803: $opts = $field->optionsExec( $this->_db );
804:
805: if ( $opts !== false ) {
806: $options[ $field->name() ] = $opts;
807: }
808: }
809:
810: // Row based "joins"
811: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
812: $this->_join[$i]->data( $this, $out, $options );
813: }
814:
815: return array_merge(
816: array(
817: 'data' => $out,
818: 'options' => $options,
819: 'files' => $this->_fileData()
820: ),
821: $ssp
822: );
823: }
824:
825:
826: /**
827: * Insert a new row in the database
828: * @private
829: */
830: private function _insert( $values )
831: {
832: // Insert the new row
833: $id = $this->_insert_or_update( null, $values );
834:
835: // Was the primary key sent and set? Unusual, but it is possible
836: $pkeyField = $this->_find_field( $this->_pkey, 'name' );
837: if ( $pkeyField && $pkeyField->apply( 'edit', $values ) ) {
838: $id = $pkeyField->val( 'set', $values );
839: }
840:
841: // Join tables
842: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
843: $this->_join[$i]->create( $this, $id, $values );
844: }
845:
846: // Full data set for the created row
847: $row = $this->_get( $id );
848: $row = count( $row['data'] ) > 0 ?
849: $row['data'][0] :
850: null;
851:
852: $this->_trigger( 'postCreate', $id, $values, $row );
853:
854: return $row;
855: }
856:
857:
858: /**
859: * Update a row in the database
860: * @param string $id The DOM ID for the row that is being edited.
861: * @return array Row's data
862: * @private
863: */
864: private function _update( $id, $values )
865: {
866: $id = str_replace( $this->_idPrefix, '', $id );
867:
868: // Update or insert the rows for the parent table and the left joined
869: // tables
870: $this->_insert_or_update( $id, $values );
871:
872: // And the join tables
873: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
874: $this->_join[$i]->update( $this, $id, $values );
875: }
876:
877: // Was the primary key altered as part of the edit? Unusual, but it is
878: // possible
879: $pkeyField = $this->_find_field( $this->_pkey, 'name' );
880: $getId = $pkeyField && $pkeyField->apply( 'edit', $values ) ?
881: $pkeyField->val( 'set', $values ) :
882: $id;
883:
884: // Full data set for the modified row
885: $row = $this->_get( $getId );
886: $row = count( $row['data'] ) > 0 ?
887: $row['data'][0] :
888: null;
889:
890: $this->_trigger( 'postEdit', $id, $values, $row );
891:
892: return $row;
893: }
894:
895:
896: /**
897: * Delete one or more rows from the database
898: * @private
899: */
900: private function _remove( $data )
901: {
902: $ids = array();
903:
904: // Get the ids to delete from the data source
905: foreach ($data['data'] as $idSrc => $rowData) {
906: // Strip the ID prefix that the client-side sends back
907: $id = str_replace( $this->_idPrefix, "", $idSrc );
908:
909: $this->_trigger( 'preRemove', $id, $rowData );
910: $ids[] = $id;
911: }
912:
913: if ( count( $ids ) === 0 ) {
914: throw new \Exception('No ids submitted for the delete');
915: }
916:
917: // Row based joins - remove first as the host row will be removed which
918: // is a dependency
919: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
920: $this->_join[$i]->remove( $this, $ids );
921: }
922:
923: // Remove from the left join tables
924: for ( $i=0, $ien=count($this->_leftJoin) ; $i<$ien ; $i++ ) {
925: $join = $this->_leftJoin[$i];
926: $table = $this->_alias( $join['table'], 'orig' );
927:
928: // which side of the join refers to the parent table?
929: if ( strpos( $join['field1'], $join['table'] ) === 0 ) {
930: $parentLink = $join['field2'];
931: $childLink = $join['field1'];
932: }
933: else {
934: $parentLink = $join['field1'];
935: $childLink = $join['field2'];
936: }
937:
938: // Only delete on the primary key, since that is what the ids refer
939: // to - otherwise we'd be deleting random data!
940: if ( $parentLink === $this->_pkey ) {
941: $this->_remove_table( $join['table'], $ids, $childLink );
942: }
943: }
944:
945: // Remove from the primary tables
946: for ( $i=0, $ien=count($this->_table) ; $i<$ien ; $i++ ) {
947: $this->_remove_table( $this->_table[$i], $ids );
948: }
949:
950: foreach ($data['data'] as $idSrc => $rowData) {
951: $id = str_replace( $this->_idPrefix, "", $idSrc );
952:
953: $this->_trigger( 'postRemove', $id, $rowData );
954: }
955: }
956:
957:
958: /**
959: * File upload
960: * @param array $data Upload data
961: * @throws \Exception File upload name error
962: * @private
963: */
964: private function _upload( $data )
965: {
966: // Search for upload field in local fields
967: $field = $this->_find_field( $data['uploadField'], 'name' );
968: $fieldName = '';
969:
970: if ( ! $field ) {
971: // Perhaps it is in a join instance
972: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
973: $join = $this->_join[$i];
974: $fields = $join->fields();
975:
976: for ( $j=0, $jen=count($fields) ; $j<$jen ; $j++ ) {
977: $joinField = $fields[ $j ];
978: $name = $join->name().'[].'.$joinField->name();
979:
980: if ( $name === $data['uploadField'] ) {
981: $field = $joinField;
982: $fieldName = $name;
983: }
984: }
985: }
986: }
987: else {
988: $fieldName = $field->name();
989: }
990:
991: if ( ! $field ) {
992: throw new \Exception("Unknown upload field name submitted");
993: }
994:
995: $upload = $field->upload();
996: if ( ! $upload ) {
997: throw new \Exception("File uploaded to a field that does not have upload options configured");
998: }
999:
1000: $res = $upload->exec( $this );
1001:
1002: if ( $res === false ) {
1003: $this->_out['fieldErrors'][] = array(
1004: "name" => $fieldName, // field name can be just the field's
1005: "status" => $upload->error() // name or a join combination
1006: );
1007: }
1008: else {
1009: $files = $this->_fileData( $upload->table() );
1010:
1011: $this->_out['files'] = $files;
1012: $this->_out['upload']['id'] = $res;
1013: }
1014: }
1015:
1016:
1017: /**
1018: * Get information about the files that are detailed in the database for
1019: * the fields which have an upload method defined on them.
1020: *
1021: * @param string [$limitTable=null] Limit the data gathering to a single
1022: * table only
1023: * @return array File information
1024: * @private
1025: */
1026: private function _fileData ( $limitTable=null )
1027: {
1028: $files = array();
1029:
1030: // The fields in this instance
1031: $this->_fileDataFields( $files, $this->_fields, $limitTable );
1032:
1033: // From joined tables
1034: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
1035: $this->_fileDataFields( $files, $this->_join[$i]->fields(), $limitTable );
1036: }
1037:
1038: return $files;
1039: }
1040:
1041:
1042: /**
1043: * Common file get method for any array of fields
1044: * @param array &$files File output array
1045: * @param Field[] $fields Fields to get file information about
1046: * @param string $limitTable Limit the data gathering to a single table
1047: * only
1048: * @private
1049: */
1050: private function _fileDataFields ( &$files, $fields, $limitTable )
1051: {
1052: foreach ($fields as $field) {
1053: $upload = $field->upload();
1054:
1055: if ( $upload ) {
1056: $table = $upload->table();
1057:
1058: if ( ! $table ) {
1059: continue;
1060: }
1061:
1062: if ( $limitTable !== null && $table !== $limitTable ) {
1063: continue;
1064: }
1065:
1066: if ( isset( $files[ $table ] ) ) {
1067: continue;
1068: }
1069:
1070: $fileData = $upload->data( $this->_db );
1071:
1072: if ( $fileData !== null ) {
1073: $files[ $table ] = $fileData;
1074: }
1075: }
1076: }
1077: }
1078:
1079: /**
1080: * Run the file clean up
1081: *
1082: * @private
1083: */
1084: private function _fileClean ()
1085: {
1086: foreach ( $this->_fields as $field ) {
1087: $upload = $field->upload();
1088:
1089: if ( $upload ) {
1090: $upload->dbCleanExec( $this, $field );
1091: }
1092: }
1093:
1094: for ( $i=0 ; $i<count($this->_join) ; $i++ ) {
1095: foreach ( $this->_join[$i]->fields() as $field ) {
1096: $upload = $field->upload();
1097:
1098: if ( $upload ) {
1099: $upload->dbCleanExec( $this, $field );
1100: }
1101: }
1102: }
1103: }
1104:
1105:
1106: /* * * * * * * * * * * * * * * * * * * * * * * * *
1107: * Server-side processing methods
1108: */
1109:
1110: /**
1111: * When server-side processing is being used, modify the query with // the
1112: * required extra conditions
1113: *
1114: * @param \DataTables\Database\Query $query Query instance to apply the SSP commands to
1115: * @param array $http Parameters from HTTP request
1116: * @return array Server-side processing information array
1117: * @private
1118: */
1119: private function _ssp_query ( $query, $http )
1120: {
1121: if ( ! isset( $http['draw'] ) ) {
1122: return array();
1123: }
1124:
1125: // Add the server-side processing conditions
1126: $this->_ssp_limit( $query, $http );
1127: $this->_ssp_sort( $query, $http );
1128: $this->_ssp_filter( $query, $http );
1129:
1130: // Get the number of rows in the result set
1131: $ssp_set_count = $this->_db
1132: ->query('select')
1133: ->table( $this->_table )
1134: ->get( 'COUNT('.$this->_pkey.') as cnt' );
1135: $this->_get_where( $ssp_set_count );
1136: $this->_ssp_filter( $ssp_set_count, $http );
1137: $this->_perform_left_join( $ssp_set_count );
1138: $ssp_set_count = $ssp_set_count->exec()->fetch();
1139:
1140: // Get the number of rows in the full set
1141: $ssp_full_count = $this->_db
1142: ->query('select')
1143: ->table( $this->_table )
1144: ->get( 'COUNT('.$this->_pkey.') as cnt' );
1145: $this->_get_where( $ssp_full_count );
1146: if ( count( $this->_where ) ) { // only needed if there is a where condition
1147: $this->_perform_left_join( $ssp_full_count );
1148: }
1149: $ssp_full_count = $ssp_full_count->exec()->fetch();
1150:
1151: return array(
1152: "draw" => intval( $http['draw'] ),
1153: "recordsTotal" => $ssp_full_count['cnt'],
1154: "recordsFiltered" => $ssp_set_count['cnt']
1155: );
1156: }
1157:
1158:
1159: /**
1160: * Convert a column index to a database field name - used for server-side
1161: * processing requests.
1162: * @param array $http HTTP variables (i.e. GET or POST)
1163: * @param int $index Index in the DataTables' submitted data
1164: * @returns string DB field name
1165: * @throws \Exception Unknown fields
1166: * @private
1167: */
1168: private function _ssp_field( $http, $index )
1169: {
1170: $name = $http['columns'][$index]['data'];
1171: $field = $this->_find_field( $name, 'name' );
1172:
1173: if ( ! $field ) {
1174: // Is it the primary key?
1175: if ( $name === 'DT_RowId' ) {
1176: return $this->_pkey;
1177: }
1178:
1179: throw new \Exception('Unknown field: '.$name .' (index '.$index.')');
1180: }
1181:
1182: return $field->dbField();
1183: }
1184:
1185:
1186: /**
1187: * Sorting requirements to a server-side processing query.
1188: * @param \DataTables\Database\Query $query Query instance to apply sorting to
1189: * @param array $http HTTP variables (i.e. GET or POST)
1190: * @private
1191: */
1192: private function _ssp_sort ( $query, $http )
1193: {
1194: for ( $i=0 ; $i<count($http['order']) ; $i++ ) {
1195: $order = $http['order'][$i];
1196:
1197: $query->order(
1198: $this->_ssp_field( $http, $order['column'] ) .' '.
1199: ($order['dir']==='asc' ? 'asc' : 'desc')
1200: );
1201: }
1202: }
1203:
1204:
1205: /**
1206: * Add DataTables' 'where' condition to a server-side processing query. This
1207: * works for both global and individual column filtering.
1208: * @param \DataTables\Database\Query $query Query instance to apply the WHERE conditions to
1209: * @param array $http HTTP variables (i.e. GET or POST)
1210: * @private
1211: */
1212: private function _ssp_filter ( $query, $http )
1213: {
1214: $that = $this;
1215:
1216: // Global filter
1217: $fields = $this->_fields;
1218:
1219: // Global search, add a ( ... or ... ) set of filters for each column
1220: // in the table (not the fields, just the columns submitted)
1221: if ( $http['search']['value'] ) {
1222: $query->where( function ($q) use (&$that, &$fields, $http) {
1223: for ( $i=0 ; $i<count($http['columns']) ; $i++ ) {
1224: if ( $http['columns'][$i]['searchable'] == 'true' ) {
1225: $field = $that->_ssp_field( $http, $i );
1226:
1227: if ( $field ) {
1228: $q->or_where( $field, '%'.$http['search']['value'].'%', 'like' );
1229: }
1230: }
1231: }
1232: } );
1233: }
1234:
1235: // if ( $http['search']['value'] ) {
1236: // $words = explode(" ", $http['search']['value']);
1237:
1238: // $query->where( function ($q) use (&$that, &$fields, $http, $words) {
1239: // for ( $j=0, $jen=count($words) ; $j<$jen ; $j++ ) {
1240: // if ( $words[$j] ) {
1241: // $q->where_group( true );
1242:
1243: // for ( $i=0, $ien=count($http['columns']) ; $i<$ien ; $i++ ) {
1244: // if ( $http['columns'][$i]['searchable'] == 'true' ) {
1245: // $field = $that->_ssp_field( $http, $i );
1246:
1247: // $q->or_where( $field, $words[$j].'%', 'like' );
1248: // $q->or_where( $field, '% '.$words[$j].'%', 'like' );
1249: // }
1250: // }
1251:
1252: // $q->where_group( false );
1253: // }
1254: // }
1255: // } );
1256: // }
1257:
1258: // Column filters
1259: for ( $i=0, $ien=count($http['columns']) ; $i<$ien ; $i++ ) {
1260: $column = $http['columns'][$i];
1261: $search = $column['search']['value'];
1262:
1263: if ( $search !== '' && $column['searchable'] == 'true' ) {
1264: $query->where( $this->_ssp_field( $http, $i ), '%'.$search.'%', 'like' );
1265: }
1266: }
1267: }
1268:
1269:
1270: /**
1271: * Add a limit / offset to a server-side processing query
1272: * @param \DataTables\Database\Query $query Query instance to apply the offset / limit to
1273: * @param array $http HTTP variables (i.e. GET or POST)
1274: * @private
1275: */
1276: private function _ssp_limit ( $query, $http )
1277: {
1278: if ( $http['length'] != -1 ) { // -1 is 'show all' in DataTables
1279: $query
1280: ->offset( $http['start'] )
1281: ->limit( $http['length'] );
1282: }
1283: }
1284:
1285:
1286: /* * * * * * * * * * * * * * * * * * * * * * * * *
1287: * Internal helper methods
1288: */
1289:
1290: /**
1291: * Add left join commands for the instance to a query.
1292: *
1293: * @param \DataTables\Database\Query $query Query instance to apply the joins to
1294: * @private
1295: */
1296: private function _perform_left_join ( $query )
1297: {
1298: if ( count($this->_leftJoin) ) {
1299: for ( $i=0, $ien=count($this->_leftJoin) ; $i<$ien ; $i++ ) {
1300: $join = $this->_leftJoin[$i];
1301:
1302: $query->join( $join['table'], $join['field1'].' '.$join['operator'].' '.$join['field2'], 'LEFT' );
1303: }
1304: }
1305: }
1306:
1307:
1308: /**
1309: * Add local WHERE condition to query
1310: * @param \DataTables\Database\Query $query Query instance to apply the WHERE conditions to
1311: * @private
1312: */
1313: private function _get_where ( $query )
1314: {
1315: for ( $i=0 ; $i<count($this->_where) ; $i++ ) {
1316: if ( is_callable( $this->_where[$i] ) ) {
1317: $this->_where[$i]( $query );
1318: }
1319: else {
1320: $query->where(
1321: $this->_where[$i]['key'],
1322: $this->_where[$i]['value'],
1323: $this->_where[$i]['op']
1324: );
1325: }
1326: }
1327: }
1328:
1329:
1330: /**
1331: * Get a field instance from a known field name
1332: *
1333: * @param string $name Field name
1334: * @param string $type Matching name type
1335: * @return Field Field instance
1336: * @private
1337: */
1338: private function _find_field ( $name, $type )
1339: {
1340: for ( $i=0, $ien=count($this->_fields) ; $i<$ien ; $i++ ) {
1341: $field = $this->_fields[ $i ];
1342:
1343: if ( $type === 'name' && $field->name() === $name ) {
1344: return $field;
1345: }
1346: else if ( $type === 'db' && $field->dbField() === $name ) {
1347: return $field;
1348: }
1349: }
1350:
1351: return null;
1352: }
1353:
1354:
1355: /**
1356: * Insert or update a row for all main tables and left joined tables.
1357: *
1358: * @param int $id ID to use to condition the update. If null is given, the
1359: * first query performed is an insert and the inserted id used as the
1360: * value should there be any subsequent tables to operate on.
1361: * @return \DataTables\Database\Result Result from the query or null if no query
1362: * performed.
1363: * @private
1364: */
1365: private function _insert_or_update ( $id, $values )
1366: {
1367: // Loop over all tables in _table, doing the insert or update as needed
1368: for ( $i=0, $ien=count( $this->_table ) ; $i<$ien ; $i++ ) {
1369: $res = $this->_insert_or_update_table(
1370: $this->_table[$i],
1371: $values,
1372: $id === null ?
1373: null :
1374: array($this->_pkey => $id)
1375: );
1376:
1377: // If we don't have an id yet, then the first insert will return
1378: // the id we want
1379: if ( $id === null ) {
1380: $id = $res->insertId();
1381: }
1382: }
1383:
1384: // And for the left join tables as well
1385: for ( $i=0, $ien=count( $this->_leftJoin ) ; $i<$ien ; $i++ ) {
1386: $join = $this->_leftJoin[$i];
1387:
1388: // which side of the join refers to the parent table?
1389: $joinTable = $this->_alias( $join['table'], 'alias' );
1390: $tablePart = $this->_part( $join['field1'] );
1391:
1392: if ( $this->_part( $join['field1'], 'db' ) ) {
1393: $tablePart = $this->_part( $join['field1'], 'db' ).'.'.$tablePart;
1394: }
1395:
1396: if ( $tablePart === $joinTable ) {
1397: $parentLink = $join['field2'];
1398: $childLink = $join['field1'];
1399: }
1400: else {
1401: $parentLink = $join['field1'];
1402: $childLink = $join['field2'];
1403: }
1404:
1405: if ( $parentLink === $this->_pkey ) {
1406: $whereVal = $id;
1407: }
1408: else {
1409: // We need submitted information about the joined data to be
1410: // submitted as well as the new value. We first check if the
1411: // host field was submitted
1412: $field = $this->_find_field( $parentLink, 'db' );
1413:
1414: if ( ! $field || ! $field->apply( 'set', $values ) ) {
1415: // If not, then check if the child id was submitted
1416: $field = $this->_find_field( $childLink, 'db' );
1417:
1418: // No data available, so we can't do anything
1419: if ( ! $field || ! $field->apply( 'set', $values ) ) {
1420: continue;
1421: }
1422: }
1423:
1424: $whereVal = $field->val('set', $values);
1425: }
1426:
1427: $whereName = $this->_part( $childLink, 'field' );
1428:
1429: $this->_insert_or_update_table(
1430: $join['table'],
1431: $values,
1432: array( $whereName => $whereVal )
1433: );
1434: }
1435:
1436: return $id;
1437: }
1438:
1439:
1440: /**
1441: * Insert or update a row in a single database table, based on the data
1442: * given and the fields configured for the instance.
1443: *
1444: * The function will find the fields which are required for this specific
1445: * table, based on the names of fields and use only the appropriate data for
1446: * this table. Therefore the full submitted data set can be passed in.
1447: *
1448: * @param string $table Database table name to use (can include an alias)
1449: * @param array $where Update condition
1450: * @return \DataTables\Database\Result Result from the query or null if no query
1451: * performed.
1452: * @throws \Exception Where set error
1453: * @private
1454: */
1455: private function _insert_or_update_table ( $table, $values, $where=null )
1456: {
1457: $set = array();
1458: $action = ($where === null) ? 'create' : 'edit';
1459: $tableAlias = $this->_alias( $table, 'alias' );
1460:
1461: for ( $i=0 ; $i<count($this->_fields) ; $i++ ) {
1462: $field = $this->_fields[$i];
1463: $tablePart = $this->_part( $field->dbField() );
1464:
1465: if ( $this->_part( $field->dbField(), 'db' ) ) {
1466: $tablePart = $this->_part( $field->dbField(), 'db' ).'.'.$tablePart;
1467: }
1468:
1469: // Does this field apply to this table (only check when a join is
1470: // being used)
1471: if ( count($this->_leftJoin) && $tablePart !== $tableAlias ) {
1472: continue;
1473: }
1474:
1475: // Check if this field should be set, based on options and
1476: // submitted data
1477: if ( ! $field->apply( $action, $values ) ) {
1478: continue;
1479: }
1480:
1481: // Some db's (specifically postgres) don't like having the table
1482: // name prefixing the column name. Todo: it might be nicer to have
1483: // the db layer abstract this out?
1484: $fieldPart = $this->_part( $field->dbField(), 'field' );
1485: $set[ $fieldPart ] = $field->val( 'set', $values );
1486: }
1487:
1488: // Add where fields if setting where values and required for this
1489: // table
1490: // Note that `whereSet` is now deprecated
1491: if ( $this->_whereSet ) {
1492: for ( $j=0, $jen=count($this->_where) ; $j<$jen ; $j++ ) {
1493: $cond = $this->_where[$j];
1494:
1495: if ( ! is_callable( $cond ) ) {
1496: // Make sure the value wasn't in the submitted data set,
1497: // otherwise we would be overwriting it
1498: if ( ! isset( $set[ $cond['key'] ] ) )
1499: {
1500: $whereTablePart = $this->_part( $cond['key'], 'table' );
1501:
1502: // No table part on the where condition to match against
1503: // or table operating on matches table part from cond.
1504: if ( ! $whereTablePart || $tableAlias == $whereTablePart ) {
1505: $set[ $cond['key'] ] = $cond['value'];
1506: }
1507: }
1508: else {
1509: throw new \Exception( 'Where condition used as a setter, '.
1510: 'but value submitted for field: '.$cond['key']
1511: );
1512: }
1513: }
1514: }
1515: }
1516:
1517: // If nothing to do, then do nothing!
1518: if ( ! count( $set ) ) {
1519: return null;
1520: }
1521:
1522: // Insert or update
1523: if ( $action === 'create' ) {
1524: return $this->_db->insert( $table, $set );
1525: }
1526: else {
1527: return $this->_db->push( $table, $set, $where );
1528: }
1529: }
1530:
1531:
1532: /**
1533: * Delete one or more rows from the database for an individual table
1534: *
1535: * @param string $table Database table name to use
1536: * @param array $ids Array of ids to remove
1537: * @param string $pkey Database column name to match the ids on for the
1538: * delete condition. If not given the instance's base primary key is
1539: * used.
1540: * @private
1541: */
1542: private function _remove_table ( $table, $ids, $pkey=null )
1543: {
1544: if ( $pkey === null ) {
1545: $pkey = $this->_pkey;
1546: }
1547:
1548: // Check there is a field which has a set option for this table
1549: $count = 0;
1550:
1551: foreach ($this->_fields as $field) {
1552: if ( strpos( $field->dbField(), '.') === false || (
1553: $this->_part( $field->dbField(), 'table' ) === $table &&
1554: $field->set() !== Field::SET_NONE
1555: )
1556: ) {
1557: $count++;
1558: }
1559: }
1560:
1561: if ( $count > 0 ) {
1562: $this->_db
1563: ->query( 'delete' )
1564: ->table( $table )
1565: ->or_where( $pkey, $ids )
1566: ->exec();
1567: }
1568: }
1569:
1570:
1571: /**
1572: * Check the validity of the set options if we are doing a join, since
1573: * there are some conditions for this state. Will throw an error if not
1574: * valid.
1575: *
1576: * @private
1577: */
1578: private function _prepJoin ()
1579: {
1580: if ( count( $this->_leftJoin ) === 0 ) {
1581: return;
1582: }
1583:
1584: // Check if the primary key has a table identifier - if not - add one
1585: if ( strpos( $this->_pkey, '.' ) === false ) {
1586: $this->_pkey = $this->_alias( $this->_table[0], 'alias' ).'.'.$this->_pkey;
1587: }
1588:
1589: // Check that all fields have a table selector, otherwise, we'd need to
1590: // know the structure of the tables, to know which fields belong in
1591: // which. This extra requirement on the fields removes that
1592: for ( $i=0, $ien=count($this->_fields) ; $i<$ien ; $i++ ) {
1593: $field = $this->_fields[$i];
1594: $name = $field->dbField();
1595:
1596: if ( strpos( $name, '.' ) === false ) {
1597: throw new \Exception( 'Table part of the field "'.$name.'" was not found. '.
1598: 'In Editor instances that use a join, all fields must have the '.
1599: 'database table set explicitly.'
1600: );
1601: }
1602: }
1603: }
1604:
1605:
1606: /**
1607: * Get one side or the other of an aliased SQL field name.
1608: *
1609: * @param string $name SQL field
1610: * @param string $type Which part to get: `alias` (default) or `orig`.
1611: * @returns string Alias
1612: * @private
1613: */
1614: private function _alias ( $name, $type='alias' )
1615: {
1616: if ( stripos( $name, ' as ' ) !== false ) {
1617: $a = preg_split( '/ as /i', $name );
1618: return $type === 'alias' ?
1619: $a[1] :
1620: $a[0];
1621: }
1622:
1623: return $name;
1624: }
1625:
1626:
1627: /**
1628: * Get part of an SQL field definition regardless of how deeply defined it
1629: * is
1630: *
1631: * @param string $name SQL field
1632: * @param string $type Which part to get: `table` (default) or `db` or
1633: * `column`
1634: * @return string Part name
1635: * @private
1636: */
1637: private function _part ( $name, $type='table' )
1638: {
1639: $db = null;
1640: $table = null;
1641: $column = null;
1642:
1643: if ( strpos( $name, '.' ) !== false ) {
1644: $a = explode( '.', $name );
1645:
1646: if ( count($a) === 3 ) {
1647: $db = $a[0];
1648: $table = $a[1];
1649: $column = $a[2];
1650: }
1651: else if ( count($a) === 2 ) {
1652: $table = $a[0];
1653: $column = $a[1];
1654: }
1655: }
1656: else {
1657: $column = $name;
1658: }
1659:
1660: if ( $type === 'db' ) {
1661: return $db;
1662: }
1663: else if ( $type === 'table' ) {
1664: return $table;
1665: }
1666: return $column;
1667: }
1668:
1669:
1670: /**
1671: * Trigger an event
1672: *
1673: * @private
1674: */
1675: private function _trigger ()
1676: {
1677: $args = func_get_args();
1678: $eventName = array_shift( $args );
1679: array_unshift( $args, $this );
1680:
1681: if ( ! isset( $this->_events[ $eventName ] ) ) {
1682: return;
1683: }
1684:
1685: $events = $this->_events[ $eventName ];
1686:
1687: for ( $i=0, $ien=count($events) ; $i<$ien ; $i++ ) {
1688: call_user_func_array( $events[$i], $args );
1689: }
1690: }
1691: }
1692:
1693: