* @version $Revision$
* created Thursday May 08, 2008
* @package liberty
* @subpackage liberty_mime_handler
*
* @TODO since plugins can do just about anything here, we might need the
* option to create specific tables during install. if required we can scan for
* files called:
* table.plugin_guid.php
* where plugins can insert their own tables
**/
/**
* 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_DEFAULT', 'mimedefault' );
$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',
'download_function' => 'mime_default_download',
'expunge_function' => 'mime_default_expunge',
// Brief description of what the plugin does
'title' => 'Default File Handler',
'description' => 'This mime handler can handle any file type, creates thumbnails when possible and will make the file available as an attachment.',
// 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.default.php',
// 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_DEFAULT, $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;
// if we have a user_id set, we use that.
if( !empty( $pStoreRow['upload']['user_id'] )) {
$pStoreRow['user_id'] = $pStoreRow['upload']['user_id'];
} else {
// storage is always owned by the user that uploaded it!
// er... or at least admin if somehow we have a null mUserId
$pStoreRow['user_id'] = BitBase::verifyId( $gBitUser->mUserId ) ? $gBitUser->mUserId : ROOT_USER_ID;
if( $pStoreRow['user_id'] < 2 ) {
\Bitweaver\bit_error_log( 'The user_id for the upload was not set. Defaulted to user_id = '.$pStoreRow['user_id'].' where 1 = ROOT_USER_ID, -1 = ANONYMOUS_USER_ID, other values = big problem.' );
}
}
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'] )) {
// 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 {
$pStoreRow['attachment_id'] = $gBitSystem->mDb->GenID( 'liberty_attachments_id_seq' );
}
// try to generate thumbnails for the upload
$pStoreRow['upload']['thumbnail'] = $pStoreRow['upload']['thumbnail'] ?? true;
// Generic values needed by the storing mechanism
$pStoreRow['upload']['source_file'] = $pStoreRow['upload']['tmp_name'];
// Store all uploaded files in the users storage area
if( empty( $pStoreRow['upload']['dest_branch'] )) {
$pStoreRow['upload']['dest_branch'] = liberty_mime_get_storage_branch( [ 'sub_dir'=>$pStoreRow['attachment_id'], 'user_id'=>$pStoreRow['user_id'], 'package'=>\Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( $pStoreRow['upload'] ) ] );
}
$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'] )) {
// Store all uploaded files in the users storage area
if( empty( $pStoreRow['dest_branch'] )) {
$pStoreRow['dest_branch'] = liberty_mime_get_storage_branch( [ 'sub_dir'=>$pStoreRow['attachment_id'], 'user_id'=>$pStoreRow['user_id'], 'package'=>\Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( $pStoreRow['upload'] ) ] );
}
if( !empty( $pStoreRow['dest_branch'] ) && !empty( $pStoreRow['file_name'] ) ) {
// First we remove the old file
$path = STORAGE_PKG_PATH.$pStoreRow['dest_branch'];
$file = $path.liberty_mime_get_default_file_name( $pStoreRow['file_name'], $pStoreRow['mime_type'] );
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'] ] );
}
// store pdf text layer in main content
if ( !empty( $pStoreRow['data'] ) ) {
$sql = "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `data` = ? WHERE `content_id` = ?";
$gBitSystem->mDb->query( $sql, [ $pStoreRow['data'], $pStoreRow['content_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" => $gBitSystem->mDb->GenID( 'liberty_files_id_seq' ),
"mime_type" => $pStoreRow['upload']['type'],
"file_size" => (int)$pStoreRow['upload']['size'],
"user_id" => $pStoreRow['user_id'],
];
$gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_files", $storeHash );
// add the data into liberty_attachments to make this file available as attachment
$storeHash = [
"attachment_plugin_guid" => !empty( $pStoreRow['attachment_plugin_guid'] ) ? $pStoreRow['attachment_plugin_guid'] : PLUGIN_MIME_GUID_DEFAULT,
"attachment_id" => $pStoreRow['attachment_id'],
"content_id" => $pStoreRow['content_id'],
"foreign_id" => $storeHash['file_id'],
"user_id" => $pStoreRow['user_id'],
];
$gBitSystem->mDb->associateInsert( BIT_DB_PREFIX."liberty_attachments", $storeHash );
// add text layer from attachment pdf and the like
if ( !empty( $pStoreRow['data'] ) ) {
$sql = "UPDATE `".BIT_DB_PREFIX."liberty_content` SET `data` = ? WHERE `content_id` = ?";
$gBitSystem->mDb->query( $sql, [ $pStoreRow['data'], $pStoreRow['content_id'] ] );
}
$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 la.`attachment_id`, la.`content_id`, la.`attachment_plugin_guid`, la.`foreign_id`, la.`user_id`, la.`is_primary`, la.`pos`, la.`error_code`, la.`caption`, la.`hits` AS `downloads`,
lf.`file_id`, lf.`user_id`, lf.`file_name`, lf.`file_size`, lf.`mime_type`
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;
$defaultFileName = liberty_mime_get_default_file_name( $row['file_name'], $row['mime_type'] );
$storageBranchPath = liberty_mime_get_storage_branch( [ 'sub_dir' => $row['attachment_id'], 'user_id' =>$row['user_id'], 'package' => \Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( $row ) ] );
$storageBranch = $storageBranchPath.$defaultFileName;
if( !file_exists( STORAGE_PKG_PATH.$storageBranch ) ) {
$storageBranch = liberty_mime_get_storage_branch( [ 'sub_dir' => $row['attachment_id'], 'user_id' =>$row['user_id'], 'package' => \Bitweaver\Liberty\liberty_mime_get_storage_sub_dir_name( $row ) ] ).$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['mime_type'] = $row['mime_type'];
$ret['source_file'] = STORAGE_PKG_PATH.$storageBranch;
$ret['source_url'] = STORAGE_PKG_URL.$storageBranch;
$ret['last_modified'] = filemtime( $ret['source_file'] );
$ret['download_url'] = LibertyMime::getAttachmentDownloadUrl( $row['attachment_id'] );
}
// add a description of how to insert this file into a wiki page
if( $gLibertySystem->isPluginActive( 'dataattachment' )) {
$ret['wiki_plugin_link'] = "{attachment id=".$row['attachment_id']."}";
}
}
}
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( "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'] );
if( $gBitSystem->isFeatureActive( 'site_nginx' )) {
// Nginx - most efficient
header( 'X-Accel-Redirect: '.$pFileHash['source_file'] );
} elseif( $gBitSystem->isFeatureActive( 'site_apache_xsendfile' )) {
// Apache with mod_xsendfile installed
header( 'X-Sendfile: '.$pFileHash['source_file'] );
} else {
// Fallback - any web server, no module needed
header( "Cache-Control: no-cache,must-revalidate" );
header( "Expires: 0" );
header( "Accept-Ranges: bytes" );
header( "Pragma: public" );
header( "Content-Length: ".filesize( $pFileHash['source_file'] ));
header( "Content-Transfer-Encoding: binary" );
@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 array $pParamHash The contents of LibertyMime->mInfo
* @access public
* @return bool true on success, false on failure - $pParamHash['errors'] will contain reason for 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'] ) {
// make sure this is a valid storage directory before removing it
if( !empty( $fileHash['source_file'] ) && ($nuke = LibertyMime::validateStoragePath( $fileHash['source_file'] )) && is_file( $nuke )) {
KernelTools::unlink_r( dirname( $nuke ));
}
$query = "DELETE FROM `".BIT_DB_PREFIX."liberty_files` WHERE `file_id` = ?";
$gBitSystem->mDb->query( $query, [ $fileHash['foreign_id'] ] );
$ret = true;
}
}
}
return $ret;
}
}