Overview

Namespaces

  • DataTables
    • Database
    • Editor
    • Vendor

Classes

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