M\á:l‹°;ZfbJôØèÞ®^—X£ß (qª·vÓIÍnÛ³séRÈEÜ]ð>=½ô €“²ñ–ïsHƒR<¿×™¦.¤yÈrHl‚·üêãpÞ´-)QAù€pëægÇv¼T†¶�hnç+4²éaÌ@ØÖuD­‚#ÎÌH¨éò+�G5�¸±¤Úr,²œ…âéŽjàs^4mËmKòäW-ò²@¯¢XU _¯¤BXJަªq¿–«â¾tvì"¨Ï¥Oµ`¢qßl�"¬Á„ñ«8µöß]¤->¥u°3Ò̇�ù@’|þ£ô‚Ü׋òxO|îŒéƒu£-âY¬}®„‚5Í2æm,ŽpÏu÷Cªçåd#u˜,VÊþ¦NéþÓHªËÔ? ëϨ?Eb£ókBj&:^–^‹ã)Ç+3\Õ ‹Ô™"‚®Õ×�I�BÜ·ã›$Îö.¢YÛ5Ìâ³›Š©jXÔ‡Šæ‚[ešÂ…/À.¼�à;½p䩌î»ÊÀã·¢iŒ¸³è€Ã_ÈBåšh:쫇±Ò±#$¤uWáê®É–ÄíR£-Vº~dûÏV$`l�ѪP‹Ž"´ ó&¶‘‹>ë)Õâ§^P1á�R3xæ‹ßá!' EÙØ±XôlgŒ'Ñ;2©ìY_ë?Joã2BŒÈ=¡ó Ø�…Væ4a5¨=ãÈ#5¸Bk¹8”%ŸZ¨ãÀ e‡.Ã6õí~ñý8Ljµsaêt_•Ï›YNªQ9Aµ¶[Äç÷ÂÉÙ®jÞhCE�‡‘Å|ïk—}•K…’,¸£M•ª£'gCSlR «Ë“³Oeýòƒ‚¶<Ûý€Oµoxs‚Ûyá5ÁúLÁDT ãÿe;xé¿ÆzUjkÞÚÄôŒ�tš8„ =!2ÑÑ’0]{–>˜¾ûì 5·ÅØå Bb,Ø0z¢Æñ]A¯W'1aâe™ûç<]]`kdSˆ‹òäYbáÔÄ`Ð-~Úío¬jÝ‹ªÃ“¢ß¤Ô²á¾‘1Jgô…Ü=ÁÛ Ê@Ó²µƒ$jKF£q”Íî\OcŽÎÜ4(uâ-Ÿ{æ-ý�M/ÿ—âÅ¡­rXQö†I"]âLx>µ+Å ×۱ɱÆÜ£3Ìo¬ô¡gJÆçdþÕ—ï Ô²þó]øLš)+ò& Ôýûô¹ÀrË«ê[æ%ê¡’æû¿›�H¤·5l£Fƒˆ/šÛ¦¶+V×<¾ë d†CÀ&=Wlú[KîѼ>øÖD˼K)©[tÉe�«Êìh@.Gqö¨úBêRÖnŽ¡äòçꌅ|SGÑÓ¬6ÐÌžÓ7%"¢-³JmÞ7?7²{æá1ÓîO|´>”ÇøÑµMUwú èn0ñýn½­vyþƒò!+Ùê׀ qÁt!Ò³™N=í¼^·sWŸÎ´•õ°pŒ^ß®Ÿ©{y­À¯â}éˆrÈ´¾:siýQœ“Yg½þ“hHEƒõÏ_(±%wô+V�…Kü—ìKh�pÉŽ~lÿ�Ø1¨»;ì×ôËÄ¥¼ÆX@ÕaE{ý>xÇ8=¡H �¡´¼sœé=ÒZD¿W3WvÔ틽aH¥/æìh@.Gqö¨úBêRÖnŽ¡äòçꌅ|SGÑÓz¬þÆŸâBS¨î«*övŠ›ÃÁQÑ·”áÄØKNͽþ¾rLÈXêG¡.OÌàòÿ£ '˜¬B©)Roš*ÿ�Ø1¨»;ì×ôËÄ¥¼ÆX@ÕaE{ý>xÇ8=¡îŽ�Þü‡E®]>lâ6>ÕØÉ~ÒWä°'ó^@�÷"Ç,j¡§_HÉÚ6¦Ó“fÚ| Ã)=Kl�Á¯‚m¹ô(Ë5�IRdšÎPT†^ ¾ð †iÇB9˜Ï‘öBÒ.Ç P¿5]Ã=4²ƒïø¢OÛ-\m�‹ó�M‡d(F3äÝè#±ƒ`HÂÞõ…û¿›�H¤·5l£Fƒˆ/šÁDT ãÿe;xé¿ÆzUj°bÖ½Þèºí°üö£ô°å“µêÿȪ�oÉb:]%tYï*µŸN›¥¡uèyÈf È,S[?ð–^X1jðHo¬Oð�²‰ËûˆåÜ Ü�C¡Ÿ—“Üû®t/A«Í!SN$_¿÷hÐLñVÖwµtF3äÝè#±ƒ`HÂÞõ…û¿›�H¤·5l£Fƒˆ/šÁDT ãÿe;xé¿ÆzUjÊ>7®OÄtÝ=e[úØÙEÝÛÇhfœ4Œ²ÍQñ›È5AfXœ‰\¶/�Dì)‹ÏM9׆ìh@.Gqö¨úBêRÖnŽ¡äòçꌅ|SGÑÓz¬þÆŸâBS¨î«*ö¤—~åâO3CØž¥¶ª#¦C—hÒqæ. Ã}+aƒŽêŽ^´QVíhÞÑ¥$Çð�¡uèyÈf È,S[?ð–^X1jðHo¬Oð2Üí~úT q'gÙþfza R»ä§~Ï\�ñÉÖnË�P‘M0P{_”I'˜§mÐxH¦H‘[¤Piê\ݯç ‡.ì£:~öœ†²ãí =¦¤_éÃÐS²½Î]å¥à]ÕUÔ÷àv{ÕØÙEÝÛÇhfœ4Œ²Í›‡N^AßD�›—¦zAù{Û¸$’ZˆÉò”Ò˯‡“fÚ| Ã)=Kl�Á¯‚m¹ô(Ë5�IRdšÎPT†^y”* ¶Á8q‡ì÷µ†œ9§ø�#Ý›>Æ_�†F3äÝè#±ƒ`HÂÞõ…û¿›�H¤·5l£Fƒˆ/š„ÍÁG Û¾QȲa_àv"µnÿoOÄ+öRaQ?û9§sˆl²#¬v}Q P¦áèx|�3„ÃæfWÑsê׀ qÁt!Ò³™N=í¼^·sWŸÎ´•õ°pŒ^ƒt¡ãoÔx¡k»yØ‘‚tgwæÕ3&XÚ­zÚÍç1cÉ-ÿ2F‰™%©Â�‰Þ6 ž>õ?H-פ«Ó[tŽGæQ'ÛJ0�Ž®[æ�,?Íê²’¿¯„1ë_½bÏ¥wS ­Í2?Œ%Èl†Üã+'ÜmrmmùðßBõ›Ú"ZÌýºç¹â1 (ÆÄåb 9ÓwõÛŠa¼8P‡|« W1¾õ›„qÑïu«¹­ÂWÙ§M =Qû”.q÷I\V xh³¹ï‹¨6@+öhì\´9oàd€(ã›N#_¹©j¾«å›Øø�«ô­Ó¿ÿŒÅ;þýì]Èd�7›{®¯™Ü%'¿ùù§A 02ŸKŸÀ'g³eJüúÊPÉøñ)‚œË·×/÷ÓßD@÷ÍÐOp<+ÑÈ–eÓæ œDÄÛ|‡Š¡ÐMËÕ¸ùêÁ¾Ž°HM\$n7¬¡¢UÂid!5 ˆLÔÕˆ8"Àa-iñ'üøl%'mƒã›aÊ&´Ù(ë}Òò¨føîàûúJ§yûMc&ù¸ö,ŸÑÄ59ØÆ¥Z9€ÂŽÈ »ÿüÖ4fxçÓž%"Y¨ñDŒê¸â{@’±_òñÇå¾Hr E7WøÖ¨òaÎ�x2˜L(ûeŦƒÅ=œï˜|è¬"ù¥d1/wN³vã@z¸šZòkFãOäàe�[õ-ŒQÝöÖ¯µÝÂKœÝæ^!¿æ3µÎ%(�o‚*]áõÎ.‹ |¼y¶oHEB÷†<áDyxµÞÂÿÓº²aÏ–ï°Ã×vIr•0£g¹¨ g‰ž9ý¸ªªî;ÌYá´$p¯ßHÀ•°½ÄŽ#Ž¡D^•-Í/ÄtVqy~'èfOò s˜—8r< ‚_„þÞÇ5½CH 9Í™�ñó£áŒIª[¿ŠCûÖ|îñ3BNmB¦Ë¨bÐ>!%¨²7ísß ˜-ÒcI0hè AE%»>XS1RšÛþAMx#¨9�Ø� »>:Åß\áf×ç&äªî;ÌYá´$p¯ß‹:î&â ºHú€€¤)O¿GÍ/ÄtVqy~'èfOò sËig(HÿípC¶È½ø$…@.�‡sF�>¸šZòkF"zñ;èoL6¾&áU9!SŤJQü6ŠCœkóÌ�×#ë°ÈÚ1N\¸‹�Ô™L¶oHEB÷†<áDyxµÞÂÿ[òúŒô_÷²¶uâ¶ô¹`rsŸ×Ìpº`�nÐ�²ˆ¼êŒ{@’±_òñÇå¾Hr }E™íÓ~Ãé GRrÆ £�Q¿á˜Ga«óþä�±¥d1/wN³vã@züpU–q°®U£]1PÁ67PK—Q K=‡YÊ:6–sƒa—JÃÛ�WV·Ê‘ÛÞ�¯Î :©+• ¤ùR¾�S¨\´“Vùè3·²ÓÇóOs¢‡kå?‚'ænfkÁoQºM9LuºåŽ ªî;ÌYá´$p¯ßhL±Aí�a~«wê‰fã›dòe݆åë÷qBMx2ï&ó‡ÐX�ÎЦFq|²ùËœ›0µz)ñ›ÇÌG(Š¢òcUyÚ. ‘wìå$þΚst ’iý�Êg)XÌ,@ÐHY8t[Ù‚û>‘fu; */ 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: