1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
|
<?php
/**
* Mime handler - flat attachments model default
*
* As an alternative to storing file attachments in a user based storage tree, this version of mime.default.php
* provides for a flat filing system based on id. A two level tree is currently provided bassed on a Mod 1000 trimming
* of the id number for the first level directories. This limits each branch to a maximum of 1000 sub directories but
* a change the mime_default_branch setting will allow other trimmimg to be implemented.
*
* Include
* define( 'LIBERTY_DEFAULT_MIME_HANDLER', 'mimeflatdefault' );
* in config_inc.php to activate
*
* @package liberty
* @subpackage liberty_mime_handler
**/
/**
* setup
*/
namespace Bitweaver\Liberty;
use Bitweaver\BitBase;
use Bitweaver\KernelTools;
global $gLibertySystem;
/**
* This is the name of the plugin - max char length is 16
* As a naming convention, the liberty mime handler definition should start with:
* PLUGIN_MIME_GUID_
*/
define( 'PLUGIN_MIME_GUID_FLATDEFAULT', 'mimeflatdefault' );
define( 'FLAT_STORAGE_NAME', 'attachments' );
$pluginParams = [
// Set of functions and what they are called in this paricular plugin
// Use the GUID as your namespace
'verify_function' => 'mime_default_verify',
'store_function' => 'mime_default_store',
'update_function' => 'mime_default_update',
'load_function' => 'mime_default_load',
'branch_function' => 'mime_default_branch',
'download_function' => 'mime_default_download',
'expunge_function' => 'mime_default_expunge',
// Brief description of what the plugin does
'title' => 'Default Flat File Handler',
'description' => 'This mime handler can handle any file type, creates thumbnails when possible and will make the file available as an attachment under a flat file system.',
// Templates to display the files
'upload_tpl' => 'bitpackage:liberty/mime/default/upload.tpl',
'view_tpl' => 'bitpackage:liberty/mime/default/view.tpl',
'inline_tpl' => 'bitpackage:liberty/mime/default/inline.tpl',
'storage_tpl' => 'bitpackage:liberty/mime/default/storage.tpl',
'attachment_tpl' => 'bitpackage:liberty/mime/default/attachment.tpl',
// This should be the same for all mime plugins
'plugin_type' => MIME_PLUGIN,
// This needs to be specified by plugins that are included by other plugins
'file_name' => 'mime.flatdefault.php',
// Ensure only one mime.default.php version is active and visible
// Set this to true if you want the plugin active right after installation
'auto_activate' => false,
// Help page on bitweaver.org
//'help_page' => 'MimeHelpPage',
// Here you can use a perl regular expression to pick out file extensions you want to handle
// e.g.: Some image types: '#^image/(jpe?g|gif|png)#i'
// This plugin will be picked if nothing matches.
//'mimetypes' => [ '/.*/' ],
];
$gLibertySystem->registerPlugin( PLUGIN_MIME_GUID_FLATDEFAULT, $pluginParams );
/**
* Sanitise and validate data before it's stored
*
* @param array $pStoreRow Hash of data that needs to be stored
* @param array $pStoreRow['upload'] Hash passed in by $_FILES upload
* @access public
* @return bool true on success, false on failure - $pStoreRow['errors'] will contain reason
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_verify' )) {
function mime_default_verify( &$pStoreRow ) {
global $gBitSystem, $gBitUser;
$ret = false;
// storage is always owned by the user that uploaded it!
// er... or at least admin if somehow we have a null mUserId - anon uploads maybe?
$pStoreRow['user_id'] = BitBase::verifyId( $gBitUser->mUserId ) ? $gBitUser->mUserId : ROOT_USER_ID;
// Bypass p_liberty_attach_attachments for managed uploads - access controlled by fisheye/treasury or other uploader
// Ignore warning when p_liberty_attach_attachments totally disabled
// if( $this->hasUserPermission( 'p_liberty_manage_attachments' )) {
$pStoreRow['no_perm_check'] = true;
// }
if( !empty( $pStoreRow['upload']['tmp_name'] ) && is_file( $pStoreRow['upload']['tmp_name'] )) {
// attachment_id is only set when we are updating the file
if( BitBase::verifyId( $pStoreRow['upload']['attachment_id'] ?? 0 )) {
// if a new file has been uploaded, we need to get some information from the database for the file update
$fileInfo = $gBitSystem->mDb->getRow( "
SELECT la.`attachment_id`, lf.`file_id`, lf.`file_name`
FROM `".BIT_DB_PREFIX."liberty_attachments` la
INNER JOIN `".BIT_DB_PREFIX."liberty_files` lf ON ( lf.`file_id` = la.`foreign_id` )
WHERE la.`attachment_id` = ?", [ $pStoreRow['upload']['attachment_id'] ] );
$pStoreRow = array_merge( $pStoreRow, $fileInfo );
} else {
// try to generate thumbnails for the upload
//$pStoreRow['upload']['thumbnail'] = !$gBitSystem->isFeatureActive( 'liberty_offline_thumbnailer' );
$pStoreRow['upload']['thumbnail'] = true;
// Little cluge - unattached files create an empty ['upload']['attachment_id'] attached ones do not
// Really need core liberty to be consistent on identifying these - ideally adding flag for linked files
$pStoreRow['attachment_id'] = isset($pStoreRow['upload']['attachment_id'])
? $gBitSystem->mDb->GenID( 'liberty_content_id_seq' )
: $pStoreRow['content_id'];
}
// Generic values needed by the storing mechanism
$pStoreRow['upload']['source_file'] = $pStoreRow['upload']['tmp_name'];
// Store all uploaded files in the flat tree storage area
if( empty( $pStoreRow['upload']['dest_branch'] )) {
$pStoreRow['upload']['dest_branch'] = mime_default_branch( $pStoreRow['attachment_id'] );
// Use group number to protect disk content - still need to set group of directory!
KernelTools::mkdir_p( STORAGE_PKG_PATH.$pStoreRow['upload']['dest_branch'], 0777 );
}
$ret = true;
} else {
$pStoreRow['errors']['upload'] = KernelTools::tra( 'There was a problem verifying the uploaded file.' );
}
return $ret;
}
}
/**
* When a file is edited
*
* @param array $pStoreRow File data needed to store details in the database - sanitised and generated in the verify function
* @access public
* @return bool true on success, false on failure - $pStoreRow['errors'] will contain reason
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_update' )) {
function mime_default_update( &$pStoreRow ) {
global $gBitSystem;
// this will reset the uploaded file
if( BitBase::verifyId( $pStoreRow['attachment_id'] ) && !empty( $pStoreRow['upload'] )) {
if( empty( $pStoreRow['dest_branch'] )) {
$pStoreRow['dest_branch'] = liberty_mime_get_storage_branch( [ 'attachment_id' => $pStoreRow['attachment_id'] ] );
}
if( !empty( $pStoreRow['dest_branch'] ) && !empty( $pStoreRow['file_name'] ) ) {
// First we remove the old file
$path = STORAGE_PKG_PATH.$pStoreRow['dest_branch'];
$file = $path.$pStoreRow['file_name'];
if(( $nuke = LibertyMime::validateStoragePath( $path )) && is_dir( $nuke )) {
if( !empty( $pStoreRow['unlink_dir'] )) {
@KernelTools::unlink_r( $path );
mkdir( $path );
} else {
@\unlink( $file );
}
}
// make sure we store the new file in the same place as before
$pStoreRow['upload']['dest_branch'] = $pStoreRow['dest_branch'];
// if we can create new thumbnails for this file, we remove the old ones first
$canThumbFunc = liberty_get_function( 'can_thumbnail' );
if( !empty( $canThumbFunc ) && $canThumbFunc( $pStoreRow['upload']['type'] )) {
liberty_clear_thumbnails( $pStoreRow['upload'] );
}
// Now we process the uploaded file
if( $storagePath = liberty_process_upload( $pStoreRow['upload'] )) {
$sql = "UPDATE `".BIT_DB_PREFIX."liberty_files` SET `file_name` = ?, `mime_type` = ?, `file_size` = ?, `user_id` = ? WHERE `file_id` = ?";
$gBitSystem->mDb->query( $sql, [ $pStoreRow['upload']['name'], $pStoreRow['upload']['type'], $pStoreRow['upload']['size'], $pStoreRow['user_id'], $pStoreRow['file_id'] ] );
}
// ensure we have the correct guid in the db
if( empty( $pStoreRow['attachment_plugin_guid'] )) {
$pStoreRow['attachment_plugin_guid'] = LIBERTY_DEFAULT_MIME_HANDLER;
}
$gBitSystem->mDb->associateUpdate(
BIT_DB_PREFIX."liberty_attachments",
[ 'attachment_plugin_guid' => $pStoreRow['attachment_plugin_guid'] ],
[ 'attachment_id' => $pStoreRow['attachment_id'] ]
);
}
}
return true;
}
}
/**
* Store the data in the database
*
* @param array $pStoreRow File data needed to store details in the database - sanitised and generated in the verify function
* @access public
* @return bool true on success, false on failure - $pStoreRow['errors'] will contain reason
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_store' )) {
function mime_default_store( &$pStoreRow ) {
global $gBitSystem, $gLibertySystem;
$ret = false;
// take care of the uploaded file and insert it into the liberty_files and liberty_attachments tables
if( $storagePath = liberty_process_upload( $pStoreRow['upload'], empty( $pStoreRow['upload']['copy_file'] ))) {
// add row to liberty_files
$storeHash = [
"file_name" => $pStoreRow['upload']['name'],
"file_id" => $pStoreRow['attachment_id'],
"mime_type" => $pStoreRow['upload']['type'],
"file_size" => $pStoreRow['upload']['size'],
"user_id" => $pStoreRow['user_id'],
];
if ( !$storeHash['file_size'] ) { $storeHash['file_size'] = 0; }
$gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_files", $storeHash );
// add the data into liberty_attachments to make this file available as attachment
$storeHash = [
"attachment_id" => $pStoreRow['attachment_id'],
"content_id" => $pStoreRow['content_id'],
"attachment_plugin_guid" => !empty( $pStoreRow['attachment_plugin_guid'] ) ? $pStoreRow['attachment_plugin_guid'] : PLUGIN_MIME_GUID_FLATDEFAULT,
"foreign_id" => $pStoreRow['attachment_id'],
"user_id" => $pStoreRow['user_id'],
];
$gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_attachments", $storeHash );
$ret = true;
} else {
$pStoreRow['errors']['liberty_process'] = "There was a problem processing the file.";
}
return $ret;
}
}
/**
* Load file data from the database
*
* @param array $pFileHash contains all file information
* @access public
* @return bool true on success, false on failure - ['errors'] will contain reason for failure
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_load' )) {
function mime_default_load( $pFileHash, &$pPrefs ) {
global $gBitSystem, $gLibertySystem;
$ret = false;
if( BitBase::verifyId( $pFileHash['attachment_id'] )) {
$query = "
SELECT *
FROM `".BIT_DB_PREFIX."liberty_attachments` la
LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_files` lf ON( la.`foreign_id` = lf.`file_id` )
WHERE la.`attachment_id`=?";
if( $row = $gBitSystem->mDb->getRow( $query, [ $pFileHash['attachment_id'] ] ) ) {
$ret = array_merge( $pFileHash, $row );
$storageName = basename( $row['file_name'] );
// compatibility with _FILES hash
$row['name'] = $storageName;
$row['type'] = $row['mime_type'];
$storageBranch = liberty_mime_get_storage_branch( [ 'attachment_id' => $row['attachment_id'] ] ).$storageName;
// this will fetch the correct thumbnails
$thumbHash['source_file'] = STORAGE_PKG_PATH.$storageBranch;
$row['source_file'] = STORAGE_PKG_PATH.$storageBranch;
$canThumbFunc = liberty_get_function( 'can_thumbnail' );
if( $canThumbFunc && $canThumbFunc( $row['mime_type'] )) {
$thumbHash['default_image'] = LIBERTY_PKG_URL.'icons/generating_thumbnails.png';
}
$ret['thumbnail_url'] = liberty_fetch_thumbnails( $thumbHash );
// indicate that this is a mime thumbnail
if( !empty( $ret['thumbnail_url']['medium'] ) && strpos( $ret['thumbnail_url']['medium'], '/mime/' )) {
$ret['thumbnail_is_mime'] = true;
}
// pretty URLs
$ret['display_url'] = $gBitSystem->isFeatureActive( "pretty_urls" ) || $gBitSystem->isFeatureActive( "pretty_urls_extended" )
? LIBERTY_PKG_URL."view/file/".$row['attachment_id']
: LIBERTY_PKG_URL."view_file.php?attachment_id=".$row['attachment_id'];
// legacy table data was named storage path and included a partial path. strip out any path just in case
$ret['file_name'] = $storageName;
$ret['preferences'] = $pPrefs;
// some stuff is only available if we have a source file
// make sure to check for these when you use them. frequently the original might not be available
// e.g.: video files are large and the original might be deleted after conversion
if( is_file( STORAGE_PKG_PATH.$storageBranch )) {
$ret['source_file'] = STORAGE_PKG_PATH.$storageBranch;
$ret['source_url'] = STORAGE_PKG_URL.$storageBranch;
$ret['last_modified'] = filemtime( $ret['source_file'] );
$ret['download_url'] = $gBitSystem->isFeatureActive( "pretty_urls" ) || $gBitSystem->isFeatureActive( "pretty_urls_extended" )
? LIBERTY_PKG_URL."download/file/".$row['attachment_id']
: LIBERTY_PKG_URL."download_file.php?attachment_id=".$row['attachment_id'];
}
// add a description of how to insert this file into a wiki page
if( $gLibertySystem->isPluginActive( 'dataattachment' )) {
$ret['wiki_plugin_link'] = "{image id=".$row['attachment_id']."}";
}
// additionally we'll add this to distinguish between old plugins and new ones
// TODO: this should hopefully not be necessary for too long
$ret['is_mime'] = true;
}
}
return $ret;
}
}
/**
* Takes care of the entire download process. Make sure it doesn't die at the end.
* in this functioin it would be possible to add download resume possibilites and the like
*
* @param array $pFileHash Basically the same has as returned by the load function
* @access public
* @return bool true on success, false on failure - $pParamHash['errors'] will contain reason for failure
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_download' )) {
function mime_default_download( &$pFileHash ) {
global $gBitSystem;
$ret = false;
// Check to see if the file actually exists
if( !empty( $pFileHash['source_file'] ) && is_readable( $pFileHash['source_file'] )) {
// make sure we close off obzip compression if it's on
if( $gBitSystem->isFeatureActive( 'site_output_obzip' )) {
@ob_end_clean();
}
// this will get the browser to open the download dialogue - even when the
// browser could deal with the content type - not perfect, but works
if( $gBitSystem->isFeatureActive( 'mime_force_download' )) {
$pFileHash['mime_type'] = "application/force-download";
}
// set up header
header( "Cache-Control: no-cache,must-revalidate" );
header( "Expires: 0" );
header( "Accept-Ranges: bytes" );
header( "Pragma: public" );
header( "Last-Modified: ".gmdate( "D, d M Y H:i:s T", $pFileHash['last_modified'] ), true, 200 );
header( 'Content-Disposition: attachment; filename="'.$pFileHash['file_name'].'"' );
header( "Content-type: ".$pFileHash['mime_type'] );
header( "Content-Description: File Transfer" );
header( "Content-Length: ".filesize( $pFileHash['source_file'] ));
header( "Content-Transfer-Encoding: binary" );
//header( "Connection: close" );
@ob_clean();
flush();
readfile( $pFileHash['source_file'] );
$ret = true;
} else {
$pFileHash['errors']['no_file'] = KernelTools::tra( 'No matching file found.' );
}
return $ret;
}
}
/**
* Nuke data in tables when content is removed
*
* @param integer $pAttachmentId The id of the attachment to delete
* @access public
* @return bool true on success, false on failure
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_expunge' )) {
function mime_default_expunge( $pAttachmentId ) {
global $gBitSystem, $gBitUser;
$ret = false;
if( BitBase::verifyId( $pAttachmentId )) {
if( $fileHash = LibertyMime::loadAttachment( $pAttachmentId )) {
if( $gBitUser->isAdmin() || $gBitUser->mUserId == $fileHash['user_id'] && isset( $fileHash['source_file'] ) && !empty( $fileHash['source_file'] )) {
// make sure this is a valid storage directory before removing it
if( preg_match( "#^".realpath( STORAGE_PKG_PATH )."/attachments/\d+/\d+/#", $fileHash['source_file'] ) && is_file( $fileHash['source_file'] )) {
KernelTools::unlink_r( dirname( $fileHash['source_file'] ));
}
$query = "DELETE FROM `".BIT_DB_PREFIX."liberty_files` WHERE `file_id` = ?";
$gBitSystem->mDb->query( $query, [ $fileHash['foreign_id'] ] );
$ret = true;
}
}
}
return $ret;
}
}
/**
* Generate branch from Id
*
* @param integer $pAttachmentId The id of the attachment to access
* @access public
* @return string containing path to storage location for attachment
*/
if( !function_exists( '\Bitweaver\Liberty\mime_default_branch' )) {
function mime_default_branch( $pAttachmentId ) {
$ret = false;
if( BitBase::verifyId( $pAttachmentId ) ) {
$ret = FLAT_STORAGE_NAME.'/'.($pAttachmentId % 1000).'/'.$pAttachmentId.'/';
}
return $ret;
}
}
/**
* liberty_mime_get_storage_branch - get url to store files for the feature site_upload_dir. It creates a calculable hierarchy of directories
*
* @access public
* @author Lester Caine<lester@lsces.co.uk>
* @param $pParamHash key=>value pairs to determine path. Possible keys in descending directory depth are: use the 'common' branch if null, 'package' - any desired directory below the StoragePath. this will be created if it doesn't exist, 'sub_dir' - the sub-directory in the package organization directory, this is often a primary id such as attachment_id
* @return string full path on local filsystem to store files.
*/
if( !function_exists( '\Bitweaver\Liberty\liberty_mime_get_storage_branch' )) {
function liberty_mime_get_storage_branch( $pParamHash ) { // $pSubDir = null, $pUserId = null, $pPackage = $gBitSystem->getActivePackage(), $pPermissions = 0755, $pCreateDir = true ) {
// *PRIVATE FUNCTION. GO AWAY! DO NOT CALL DIRECTLY!!!
global $gBitSystem;
$pathParts = [];
if( $pAttachmentId = BitBase::getParameter( $pParamHash, 'attachment_id' ) ) {
$pathParts[] = 'attachments';
$pathParts[] = (int)($pAttachmentId % 1000);
$pathParts[] = $pAttachmentId;
// Added in fisheye override
} else {
$pathParts[] = 'common';
}
if( $pPackage = BitBase::getParameter( $pParamHash, 'package' ) ) {
$pathParts[] = $pPackage;
}
$fullPath = implode( '/', $pathParts ).'/';
if( BitBase::getParameter( $pParamHash, 'create_dir', true ) ){
if( !file_exists( STORAGE_PKG_PATH.$fullPath ) ) {
KernelTools::mkdir_p( STORAGE_PKG_PATH.$fullPath );
}
}
return $fullPath;
}
}
if( !function_exists( '\Bitweaver\Liberty\liberty_mime_get_source_url' )) {
function liberty_mime_get_source_url( $pParamHash ) {
if( empty( $pParamHash['package'] ) ) {
$pParamHash['package'] = \Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( [ 'type' => BitBase::getParameter( $pParamHash, 'mime_type' ), 'name' => BitBase::getParameter( $pParamHash, 'file_name' ) ] );
}
if( empty( $pParamHash['sub_dir'] ) ) {
$pParamHash['sub_dir'] = BitBase::getParameter( $pParamHash, 'attachment_id' );
}
return STORAGE_PKG_URL.liberty_mime_get_storage_branch( $pParamHash ).basename( BitBase::getParameter( $pParamHash, 'file_name' ) );
}
}
if( !function_exists( '\Bitweaver\Liberty\liberty_mime_get_source_file' )) {
function liberty_mime_get_source_file( $pParamHash ) {
if( empty( $pParamHash['package'] ) ) {
$pParamHash['package'] = \Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( [ 'type' => BitBase::getParameter( $pParamHash, 'mime_type' ), 'name' => BitBase::getParameter( $pParamHash, 'file_name' ) ] );
}
if( empty( $pParamHash['sub_dir'] ) ) {
$pParamHash['sub_dir'] = BitBase::getParameter( $pParamHash, 'attachment_id' );
}
return STORAGE_PKG_PATH.liberty_mime_get_storage_branch( $pParamHash ).basename( BitBase::getParameter( $pParamHash, 'file_name' ) );
}
}
|