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