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 2015 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 DataTables;
 17: 
 18: 
 19: /**
 20:  * Upload class for Editor. This class provides the ability to easily specify
 21:  * file upload information, specifically how the file should be recorded on
 22:  * the server (database and file system).
 23:  *
 24:  * An instance of this class is attached to a field using the {@link
 25:  * Field.upload} method. When Editor detects a file upload for that file the
 26:  * information provided for this instance is executed.
 27:  *
 28:  * The configuration is primarily driven through the {@link db} and {@link
 29:  * action} methods:
 30:  *
 31:  * * {@link db} Describes how information about the uploaded file is to be
 32:  *   stored on the database.
 33:  * * {@link action} Describes where the file should be stored on the file system
 34:  *   and provides the option of specifying a custom action when a file is
 35:  *   uploaded.
 36:  *
 37:  * Both methods are optional - you can store the file on the server using the
 38:  * {@link db} method only if you want to store the file in the database, or if
 39:  * you don't want to store relational data on the database us only {@link
 40:  * action}. However, the majority of the time it is best to use both - store
 41:  * information about the file on the database for fast retrieval (using a {@link
 42:  * Editor.leftJoin()} for example) and the file on the file system for direct
 43:  * web access.
 44:  *
 45:  * @example
 46:  *   Store information about a file in a table called `files` and the actual
 47:  *   file in an `uploads` directory.
 48:  *   <code>
 49:  *      Field::inst( 'imageId' )
 50:  *          ->upload(
 51:  *              Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/uploads/__ID__.__EXTN__' )
 52:  *                  ->db( 'files', 'id', array(
 53:  *                      'webPath'     => Upload::DB_WEB_PATH,
 54:  *                      'fileName'    => Upload::DB_FILE_NAME,
 55:  *                      'fileSize'    => Upload::DB_FILE_SIZE,
 56:  *                      'systemPath'  => Upload::DB_SYSTEM_PATH
 57:  *                  ) )
 58:  *                  ->allowedExtensions( array( 'png', 'jpg' ), "Please upload an image file" )
 59:  *          )
 60:  *  </code>
 61:  *
 62:  * @example
 63:  *   As above, but with PHP 5.4 (which allows chaining from new instances of a
 64:  *   class)
 65:  *   <code>
 66:  *      newField( 'imageId' )
 67:  *          ->upload(
 68:  *              new Upload( $_SERVER['DOCUMENT_ROOT'].'/uploads/__ID__.__EXTN__' )
 69:  *                  ->db( 'files', 'id', array(
 70:  *                      'webPath'     => Upload::DB_WEB_PATH,
 71:  *                      'fileName'    => Upload::DB_FILE_NAME,
 72:  *                      'fileSize'    => Upload::DB_FILE_SIZE,
 73:  *                      'systemPath'  => Upload::DB_SYSTEM_PATH
 74:  *                  ) )
 75:  *                  ->allowedExtensions( array( 'png', 'jpg' ), "Please upload an image file" )
 76:  *          )
 77:  *  </code>
 78:  */
 79: class Upload extends DataTables\Ext {
 80:     /*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 81:      * Constants
 82:      */
 83:     
 84:     /** Database value option (`Db()`) - File content. This should be written to
 85:      * a blob. Typically this should be avoided and the file saved on the file
 86:      * system, but there are cases where it can be useful to store the file in
 87:      * the database.
 88:      */
 89:     const DB_CONTENT      = 'editor-content';
 90: 
 91:     /** Database value option (`Db()`) - Content type */
 92:     const DB_CONTENT_TYPE = 'editor-contentType';
 93: 
 94:     /** Database value option (`Db()`) - File extension */
 95:     const DB_EXTN         = 'editor-extn';
 96: 
 97:     /** Database value option (`Db()`) - File name (with extension) */
 98:     const DB_FILE_NAME    = 'editor-fileName';
 99: 
100:     /** Database value option (`Db()`) - File size (bytes) */
101:     const DB_FILE_SIZE    = 'editor-fileSize';
102: 
103:     /** Database value option (`Db()`) - MIME type */
104:     const DB_MIME_TYPE    = 'editor-mimeType';
105: 
106:     /** Database value option (`Db()`) - Full system path to the file */
107:     const DB_SYSTEM_PATH  = 'editor-systemPath';
108: 
109:     /** Database value option (`Db()`) - HTTP path to the file. This is derived 
110:      * from the system path by removing `$_SERVER['DOCUMENT_ROOT']`. If your
111:      * images live outside of the document root a custom value would be to be
112:      * used.
113:      */
114:     const DB_WEB_PATH     = 'editor-webPath';
115: 
116:     /** Read from the database - don't write to it
117:      */
118:     const DB_READ_ONLY    = 'editor-readOnly';
119: 
120: 
121:     /*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
122:      * Private parameters
123:      */
124:     
125:     private $_action = null;
126:     private $_dbCleanCallback = null;
127:     private $_dbCleanTableField = null;
128:     private $_dbTable = null;
129:     private $_dbPKey = null;
130:     private $_dbFields = null;
131:     private $_extns = null;
132:     private $_extnError = null;
133:     private $_error = null;
134:     private $_validators = array();
135:     private $_where = array();
136: 
137: 
138:     /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
139:      * Constructor
140:      */
141: 
142:     /**
143:      * Upload instance constructor
144:      * @param string|callable $action Action to take on upload - this is applied
145:      *     directly to {@link action}.
146:      */
147:     function __construct( $action=null )
148:     {
149:         if ( $action ) {
150:             $this->action( $action );
151:         }
152:     }
153: 
154: 
155:     /*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
156:      * Public methods
157:      */
158: 
159:     /**
160:      * Set the action to take when a file is uploaded. This can be either of:
161:      *
162:      * * A string - the value given is the full system path to where the
163:      *   uploaded file is written to. The value given can include three "macros"
164:      *   which are replaced by the script dependent on the uploaded file:
165:      *   * `__EXTN__` - the file extension
166:      *   * `__NAME__` - the uploaded file's name (including the extension)
167:      *   * `__ID__` - Database primary key value if the {@link db} method is
168:      *     used.
169:      * * A closure - if a function is given the responsibility of what to do
170:      *   with the uploaded file is transferred to this function. That will
171:      *   typically involve writing it to the file system so it can be used
172:      *   later.
173:      * 
174:      * @param  string|callable $action Action to take - see description above.
175:      * @return self Current instance, used for chaining
176:      */
177:     public function action ( $action )
178:     {
179:         $this->_action = $action;
180: 
181:         return $this;
182:     }
183: 
184: 
185:     /**
186:      * An array of valid file extensions that can be uploaded. This is for
187:      * simple validation that the file is of the expected type - for example you
188:      * might use `[ 'png', 'jpg', 'jpeg', 'gif' ]` for images. The check is
189:      * case-insensitive. If no extensions are given, no validation is performed
190:      * on the file extension.
191:      *
192:      * @param  string[] $extn  List of file extensions that are allowable for
193:      *     the upload
194:      * @param  string $error Error message if a file is uploaded that doesn't
195:      *     match the valid list of extensions.
196:      * @return self Current instance, used for chaining
197:      * @deprecated Use Validate::fileExtensions
198:      */
199:     public function allowedExtensions ( $extn, $error="This file type cannot be uploaded" )
200:     {
201:         $this->_extns = $extn;
202:         $this->_extnError = $error;
203: 
204:         return $this;
205:     }
206: 
207: 
208:     /**
209:      * Database configuration method. When used, this method will tell Editor
210:      * what information you want written to a database on file upload, should
211:      * you wish to store relational information about your file on the database
212:      * (this is generally recommended).
213:      *
214:      * @param  string $table  The name of the table where the file information
215:      *     should be stored
216:      * @param  string $pkey   Primary key column name. The `Upload` class
217:      *     requires that the database table have a single primary key so each
218:      *     row can be uniquely identified.
219:      * @param  array $fields A list of the fields to be written to on upload.
220:      *     The property names are the database columns and the values can be
221:      *     defined by the constants of this class. The value can also be a
222:      *     string or a closure function if you wish to send custom information
223:      *     to the database.
224:      * @return self Current instance, used for chaining
225:      */
226:     public function db ( $table, $pkey, $fields )
227:     {
228:         $this->_dbTable = $table;
229:         $this->_dbPKey = $pkey;
230:         $this->_dbFields = $fields;
231: 
232:         return $this;
233:     }
234: 
235: 
236:     /**
237:      * Set a callback function that is used to remove files which no longer have
238:      * a reference in a source table.
239:      *
240:      * @param  callable $callback Function that will be executed on clean. It is
241:      *     given an array of information from the database about the orphaned
242:      *     rows, and can return true to indicate that the rows should be
243:      *     removed from the database. Any other return value (including none)
244:      *     will result in the records being retained.
245:      * @return self Current instance, used for chaining
246:      */
247:     public function dbClean( $tableField, $callback=null )
248:     {
249:         // Argument swapping
250:         if ( $callback === null ) {
251:             $callback = $tableField;
252:             $tableField = null;
253:         }
254: 
255:         $this->_dbCleanCallback = $callback;
256:         $this->_dbCleanTableField = $tableField;
257: 
258:         return $this;
259:     }
260: 
261: 
262:     /**
263:      * Add a validation method to check file uploads. Multiple validators can be
264:      * added by calling this method multiple times - they will be executed in
265:      * sequence when a file has been uploaded.
266:      *
267:      * @param  callable $fn Validation function. A PHP `$_FILES` parameter is
268:      *     passed in for the uploaded file and the return is either a string
269:      *     (validation failed and error message), or `null` (validation passed).
270:      * @return self Current instance, used for chaining
271:      */
272:     public function validator ( $fn )
273:     {
274:         $this->_validators[] = $fn;
275: 
276:         return $this;
277:     }
278: 
279: 
280:     /**
281:      * Add a condition to the data to be retrieved from the database. This
282:      * must be given as a function to be executed (usually anonymous) and
283:      * will be passed in a single argument, the `Query` object, to which
284:      * conditions can be added. Multiple calls to this method can be made.
285:      *
286:      * @param  callable $fn Where function.
287:      * @return self Current instance, used for chaining
288:      */
289:     public function where ( $fn )
290:     {
291:         $this->_where[] = $fn;
292: 
293:         return $this;
294:     }
295: 
296: 
297:     /*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
298:      * Internal methods
299:      */
300:     
301:     /**
302:      * Get database information data from the table
303:      *
304:      * @param \DataTables\Database $db Database
305:      * @param number[] [$ids=null] Limit to a specific set of ids
306:      * @return array Database information
307:      * @internal
308:      */
309:     public function data ( $db, $ids=null )
310:     {
311:         if ( ! $this->_dbTable ) {
312:             return null;
313:         }
314: 
315:         // Select the details requested, for the columns requested
316:         $q = $db
317:             ->query( 'select' )
318:             ->table( $this->_dbTable )
319:             ->get( $this->_dbPKey );
320: 
321:         foreach ( $this->_dbFields as $column => $prop ) {
322:             if ( $prop !== self::DB_CONTENT ) {
323:                 $q->get( $column );
324:             }
325:         }
326: 
327:         if ( $ids !== null ) {
328:             $q->where_in( $this->_dbPKey, $ids );
329:         }
330: 
331:         for ( $i=0, $ien=count($this->_where) ; $i<$ien ; $i++ ) {
332:             $q->where( $this->_where[$i] );
333:         }
334: 
335:         $result = $q->exec()->fetchAll();
336:         $out = array();
337: 
338:         for ( $i=0, $ien=count($result) ; $i<$ien ; $i++ ) {
339:             $out[ $result[$i][ $this->_dbPKey ] ] = $result[$i];
340:         }
341: 
342:         return $out;
343:     }
344: 
345: 
346:     /**
347:      * Clean the database
348:      * @param  \DataTables\Editor $editor Calling Editor instance
349:      * @param  Field $field   Host field
350:      * @internal
351:      */
352:     public function dbCleanExec ( $editor, $field )
353:     {
354:         // Database and file system clean up BEFORE adding the new file to
355:         // the db, otherwise it will be removed immediately
356:         $tables = $editor->table();
357:         $this->_dbClean( $editor->db(), $tables[0], $field->dbField() );
358:     }
359: 
360: 
361:     /**
362:      * Get the set error message
363:      * 
364:      * @return string Class error
365:      * @internal
366:      */
367:     public function error ()
368:     {
369:         return $this->_error;
370:     }
371: 
372: 
373:     /**
374:      * Execute an upload
375:      *
376:      * @param  \DataTables\Editor $editor Calling Editor instance
377:      * @return int Primary key value
378:      * @internal
379:      */
380:     public function exec ( $editor )
381:     {
382:         $id = null;
383:         $upload = $_FILES['upload'];
384: 
385:         // Validation - PHP standard validation
386:         if ( $upload['error'] !== UPLOAD_ERR_OK ) {
387:             if ( $upload['error'] === UPLOAD_ERR_INI_SIZE ) {
388:                 $this->_error = "File exceeds maximum file upload size"; 
389:             }
390:             else {
391:                 $this->_error = "There was an error uploading the file (".$upload['error'].")";
392:             }
393:             return false;
394:         }
395: 
396:         // Validation - acceptable file extensions
397:         if ( is_array( $this->_extns ) ) {
398:             $extn = pathinfo($upload['name'], PATHINFO_EXTENSION);
399: 
400:             if ( in_array( strtolower($extn), array_map( 'strtolower', $this->_extns ) ) === false ) {
401:                 $this->_error = $this->_extnError;
402:                 return false;
403:             }
404:         }
405: 
406:         // Validation - custom callback
407:         for ( $i=0, $ien=count($this->_validators) ; $i<$ien ; $i++ ) {
408:             $res = $this->_validators[$i]( $upload );
409: 
410:             if ( is_string( $res ) ) {
411:                 $this->_error = $res;
412:                 return false;
413:             }
414:         }
415: 
416:         // Database
417:         if ( $this->_dbTable ) {
418:             foreach ( $this->_dbFields as $column => $prop ) {
419:                 // We can't know what the path is, if it has moved into place
420:                 // by an external function - throw an error if this does happen
421:                 if ( ! is_string( $this->_action ) &&
422:                      ($prop === self::DB_SYSTEM_PATH || $prop === self::DB_WEB_PATH )
423:                 ) {
424:                     $this->_error = "Cannot set path information in database ".
425:                         "if a custom method is used to save the file.";
426: 
427:                     return false;
428:                 }
429:             }
430: 
431:             // Commit to the database
432:             $id = $this->_dbExec( $editor->db() );
433:         }
434: 
435:         // Perform file system actions
436:         return $this->_actionExec( $id );
437:     }
438: 
439: 
440:     /**
441:      * Get the primary key column for the table
442:      *
443:      * @return string Primary key column name
444:      * @internal
445:      */
446:     public function pkey ()
447:     {
448:         return $this->_dbPKey;
449:     }
450: 
451: 
452:     /**
453:      * Get the db table name
454:      *
455:      * @return string DB table name
456:      * @internal
457:      */
458:     public function table ()
459:     {
460:         return $this->_dbTable;
461:     }
462: 
463: 
464: 
465:     /*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
466:      * Private methods
467:      */
468: 
469:     /**
470:      * Execute the configured action for the upload
471:      *
472:      * @param  int $id Primary key value
473:      * @return int File identifier - typically the primary key
474:      */
475:     private function _actionExec ( $id )
476:     {
477:         $upload = $_FILES['upload'];
478: 
479:         if ( ! is_string( $this->_action ) ) {
480:             // Custom function
481:             $action = $this->_action;
482:             return $action( $upload, $id );
483:         }
484: 
485:         // Default action - move the file to the location specified by the
486:         // action string
487:         $to  = $this->_path( $upload['name'], $id );
488:         $res = move_uploaded_file( $upload['tmp_name'], $to );
489: 
490:         if ( $res === false ) {
491:             $this->_error = "An error occurred while moving the uploaded file.";
492:             return false;
493:         }
494: 
495:         return $id !== null ?
496:             $id :
497:             $to;
498:     }
499: 
500:     /**
501:      * Perform the database clean by first getting the information about the
502:      * orphaned rows and then calling the callback function. The callback can
503:      * then instruct the rows to be removed through the return value.
504:      *
505:      * @param  \DataTables\Database $db Database instance
506:      * @param  string $editorTable Editor Editor instance table name
507:      * @param  string $fieldName   Host field's name
508:      */
509:     private function _dbClean ( $db, $editorTable, $fieldName )
510:     {
511:         $callback = $this->_dbCleanCallback;
512: 
513:         if ( ! $this->_dbTable || ! $callback ) {
514:             return;
515:         }
516: 
517:         // If there is a table / field that we should use to check if the value
518:         // is in use, then use that. Otherwise we'll try to use the information
519:         // from the Editor / Field instance.
520:         if ( $this->_dbCleanTableField ) {
521:             $fieldName = $this->_dbCleanTableField;
522:         }
523: 
524:         $a = explode('.', $fieldName);
525:         if ( count($a) === 1 ) {
526:             $table = $editorTable;
527:             $field = $a[0];
528:         }
529:         else if ( count($a) === 2 ) {
530:             $table = $a[0];
531:             $field = $a[1];
532:         }
533:         else {
534:             $table = $a[1];
535:             $field = $a[2];
536:         }
537: 
538:         // Select the details requested, for the columns requested
539:         $q = $db
540:             ->query( 'select' )
541:             ->table( $this->_dbTable )
542:             ->get( $this->_dbPKey );
543: 
544:         foreach ( $this->_dbFields as $column => $prop ) {
545:             if ( $prop !== self::DB_CONTENT ) {
546:                 $q->get( $column );
547:             }
548:         }
549: 
550:         $q->where( $this->_dbPKey, '(SELECT '.$field.' FROM '.$table.'  WHERE '.$field.' IS NOT NULL)', 'NOT IN', false );
551: 
552:         $data = $q->exec()->fetchAll();
553: 
554:         if ( count( $data ) === 0 ) {
555:             return;
556:         }
557: 
558:         $result = $callback( $data );
559: 
560:         // Delete the selected rows, iff the developer says to do so with the
561:         // returned value (i.e. acknowledge that the files have be removed from
562:         // the file system)
563:         if ( $result === true ) {
564:             $qDelete = $db
565:                 ->query( 'delete' )
566:                 ->table( $this->_dbTable );
567: 
568:             for ( $i=0, $ien=count( $data ) ; $i<$ien ; $i++ ) {
569:                 $qDelete->or_where( $this->_dbPKey, $data[$i][ $this->_dbPKey ] );
570:             }
571: 
572:             $qDelete->exec();
573:         }
574:     }
575: 
576:     /**
577:      * Add a record to the database for a newly uploaded file
578:      *
579:      * @param  \DataTables\Database $db Database instance
580:      * @return int Primary key value for the newly uploaded file
581:      */
582:     private function _dbExec ( $db )
583:     {
584:         $upload = $_FILES['upload'];
585:         $pathFields = array();
586: 
587:         // Insert the details requested, for the columns requested
588:         $q = $db
589:             ->query( 'insert' )
590:             ->table( $this->_dbTable )
591:             ->pkey( $this->_dbPKey );
592: 
593:         foreach ( $this->_dbFields as $column => $prop ) {
594:             switch ( $prop ) {
595:                 case self::DB_READ_ONLY:
596:                     break;
597: 
598:                 case self::DB_CONTENT:
599:                     $q->set( $column, file_get_contents($upload['tmp_name']) );
600:                     break;
601: 
602:                 case self::DB_CONTENT_TYPE:
603:                 case self::DB_MIME_TYPE:
604:                     $finfo = finfo_open(FILEINFO_MIME);
605:                     $mime = finfo_file($finfo, $upload['tmp_name']);
606:                     finfo_close($finfo);
607: 
608:                     $q->set( $column, $mime );
609:                     break;
610: 
611:                 case self::DB_EXTN:
612:                     $extn = pathinfo($upload['name'], PATHINFO_EXTENSION);
613:                     $q->set( $column, $extn );
614:                     break;
615: 
616:                 case self::DB_FILE_NAME:
617:                     $q->set( $column, $upload['name'] );
618:                     break;
619: 
620:                 case self::DB_FILE_SIZE:
621:                     $q->set( $column, $upload['size'] );
622:                     break;
623: 
624:                 case self::DB_SYSTEM_PATH:
625:                     $pathFields[ $column ] = self::DB_SYSTEM_PATH;
626:                     $q->set( $column, '-' ); // Use a temporary value to avoid cases 
627:                     break;                   // where the db will reject empty values
628: 
629:                 case self::DB_WEB_PATH:
630:                     $pathFields[ $column ] = self::DB_WEB_PATH;
631:                     $q->set( $column, '-' ); // Use a temporary value (as above)
632:                     break;
633: 
634:                 default:
635:                     if ( is_callable($prop) && is_object($prop) ) { // is a closure
636:                         $q->set( $column, $prop( $db, $upload ) );
637:                     }
638:                     else {
639:                         $q->set( $column, $prop );
640:                     }
641: 
642:                     break;
643:             }
644:         }
645: 
646:         $res = $q->exec();
647:         $id  = $res->insertId();
648: 
649:         // Update the newly inserted row with the path information. We have to
650:         // use a second statement here as we don't know in advance what the
651:         // database schema is and don't want to prescribe that certain triggers
652:         // etc be created. It makes it a bit less efficient but much more
653:         // compatible
654:         if ( count( $pathFields ) ) {
655:             // For this to operate the action must be a string, which is
656:             // validated in the `exec` method
657:             $path = $this->_path( $upload['name'], $id );
658:             $webPath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $path);
659:             $q = $db
660:                 ->query( 'update' )
661:                 ->table( $this->_dbTable )
662:                 ->where( $this->_dbPKey, $id );
663: 
664:             foreach ( $pathFields as $column => $type ) {
665:                 $q->set( $column, $type === self::DB_WEB_PATH ? $webPath : $path );
666:             }
667: 
668:             $q->exec();
669:         }
670: 
671:         return $id;
672:     }
673: 
674: 
675:     /**
676:      * Apply macros to a user specified path
677:      *
678:      * @param  string $name File path
679:      * @param  int $id Primary key value for the file
680:      * @return string Resolved path
681:      */
682:     private function _path ( $name, $id )
683:     {
684:         $extn = pathinfo( $name, PATHINFO_EXTENSION );
685: 
686:         $to = $this->_action;
687:         $to = str_replace( "__NAME__", $name, $to   );
688:         $to = str_replace( "__ID__",   $id,   $to   );
689:         $to = str_replace( "__EXTN__", $extn, $to );
690: 
691:         return $to;
692:     }
693: }
694: 
695: 
DataTables Editor 1.9.4 - PHP libraries API documentation generated by ApiGen