* @version $Revision$ * created Thursday May 08, 2008 * @package liberty * @subpackage liberty_mime_handler **/ /** * setup */ global $gLibertySystem, $gBitSystem; /** * 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_IMAGE', 'mimeimage' ); $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_image_store', 'update_function' => 'mime_image_update', 'load_function' => 'mime_image_load', 'download_function' => 'mime_default_download', 'expunge_function' => 'mime_default_expunge', 'help_function' => 'mime_image_help', // Brief description of what the plugin does 'title' => 'Advanced Image Processing', 'description' => 'Extract image meta data and display relevant information to the user and pick individual display options for images.', // Templates to display the files 'view_tpl' => 'bitpackage:liberty/mime/image/view.tpl', 'attachment_tpl' => 'bitpackage:liberty/mime/image/attachment.tpl', // url to page with options for this plugin 'plugin_settings_url' => LIBERTY_PKG_URL.'admin/plugins/mime_image.php', // This needs to be specified by plugins that are included by other plugins 'file_name' => 'mime.image.php', // This should be the same for all mime plugins 'plugin_type' => MIME_PLUGIN, // Set this to true if you want the plugin active right after installation 'auto_activate' => true, // Help page on bitweaver.org //'help_page' => 'LibertyMime+Image+Plugin', // this should pick up all image 'mimetypes' => [ '#image/.*#i', ], ]; // currently, there's only one option in the image edit file - panorama image setting if( $gBitSystem->isFeatureActive( 'mime_image_panoramas' )) { $pluginParams['edit_tpl'] = 'bitpackage:liberty/mime/image/edit.tpl'; } $gLibertySystem->registerPlugin( PLUGIN_MIME_GUID_IMAGE, $pluginParams ); /** * 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 */ function mime_image_store( &$pStoreRow ) { // this will set the correct pluign guid, even if we let default handle the store process $pStoreRow['attachment_plugin_guid'] = PLUGIN_MIME_GUID_IMAGE; $pStoreRow['log'] = []; // if storing works, we process the image if( $ret = mime_default_store( $pStoreRow )) { if( !mime_image_store_exif_data( $pStoreRow )) { // if it all goes tits up, we'll know why $pStoreRow['errors'] = $pStoreRow['log']; $ret = false; } } return $ret; } /** * mime_image_update update file information in the database if there were changes. * * @param array $pStoreRow File data needed to update details in the database * @access public * @return bool true on success, false on failure - $pStoreRow[errors] will contain reason */ function mime_image_update( &$pStoreRow, $pParams = null ) { global $gThumbSizes, $gBitSystem; $ret = true; // this will set the correct pluign guid, even if we let default handle the store process $pStoreRow['attachment_plugin_guid'] = PLUGIN_MIME_GUID_IMAGE; // if storing works, we process the image if( !empty( $pStoreRow['upload'] ) && $ret = mime_default_update( $pStoreRow )) { if( !mime_image_store_exif_data( $pStoreRow )) { // if it all goes tits up, we'll know why $pStoreRow['errors'] = $pStoreRow['log']; $ret = false; } } elseif( $gBitSystem->isFeatureActive( 'mime_image_panoramas' ) && !empty( $pParams['preference']['is_panorama'] ) && empty( $pStoreRow['thumbnail_url']['panorama'] )) { if( !mime_image_create_panorama( $pStoreRow )) { $ret = false; } } return $ret; } /** * Load file data from the database * * @param array $pFileHash Contains all file information * @param array $pPrefs Attachment preferences taken liberty_attachment_prefs * @param array $pParams Parameters for loading the plugin - e.g.: might contain values such as thumbnail size from the view page * @access public * @return bool true on success, false on failure - $pStoreRow[errors] will contain reason */ function mime_image_load( &$pFileHash, &$pPrefs, $pParams = null ) { global $gBitSystem; // don't load a mime image if we don't have an image for this file if( $ret = mime_default_load( $pFileHash, $pPrefs )) { // fetch meta data from the db $ret['meta'] = LibertyMime::getMetaData( $ret['attachment_id'], "EXIF" ); // if we have GPS data and geo is active, we calculate geo stuff if( $gBitSystem->isPackageActive( 'geo' ) && ( $ret['gps'] = LibertyMime::getMetaData( $ret['attachment_id'], "GPS" ))) { // longitude if( !empty( $ret['gps']['gpslongitude'] )) { $ret['geo']['lng'] = $ret['gps']['gpslongitude']; if( !empty( $ret['gps']['gpslongituderef'] ) && $ret['gps']['gpslongituderef'] == 'W' ) { $ret['geo']['lng'] = 0 - $ret['geo']['lng']; } } // latitude if( !empty( $ret['gps']['gpslatitude'] )) { $ret['geo']['lat'] = $ret['gps']['gpslatitude']; if( !empty( $ret['gps']['gpslatituderef'] ) && $ret['gps']['gpslatituderef'] == 'S' ) { $ret['geo']['lat'] = 0 - $ret['geo']['lat']; } } // set sea level data when available if( !empty( $ret['gps']['gpsaltitude'] )) { list( $dividend, $divisor ) = explode( "/", $ret['gps']['gpsaltitude'] ); $ret['geo']['amsl'] = $dividend / $divisor; $ret['geo']['amsl_unit'] = 'm'; } // final check to see if we have enough data if( empty( $ret['geo']['lng'] ) || empty( $ret['geo']['lat'] )) { unset( $ret['geo'] ); } } // check for panorama image if( isset( $ret['source_file'] ) && is_file( dirname( $ret['source_file'] )."/thumbs/panorama.jpg" )) { // if the panorama doesn't have 180⁰ vertical field of view we will restrict up / down movement if(( $ret['pano'] = LibertyMime::getMetaData( $ret['attachment_id'], "PANO" )) && !empty( $ret['pano']['aspect'] )) { // calculation based on logarythmic regression curve $ret['pano']['pa'] = round( 40 - 31 * log( $ret['pano']['aspect'] - 1.4 )); if( $ret['pano']['pa'] > 49 ) { $ret['pano']['pa'] = 90; } elseif( $ret['pano']['pa'] < 0 ) { $ret['pano']['pa'] = 0; } } $ret['thumbnail_url']['panorama'] = KernelTools::storage_path_to_url( dirname( $ret['source_file'] )."/thumbs/panorama.jpg" ); } } return $ret; } /** * mime_image_store_exif_data Process a JPEG and store its EXIF data as meta data. * * @param array $pFileHash file details. * @param array $pFileHash[upload] should contain a complete hash from $_FILES * @access public * @return bool true on success, false on failure */ function mime_image_store_exif_data( $pFileHash ) { global $gBitSystem; if( !empty( $pFileHash['upload'] )) { $upload = &$pFileHash['upload']; } if( BitBase::verifyId( $pFileHash['attachment_id'] ) && $exifHash = mime_image_get_exif_data( $upload ) ) { // only makes sense to store the GPS data if we at least have latitude and longitude if( !empty( $exifHash['GPS'] )) { LibertyMime::storeMetaData( $pFileHash['attachment_id'], $exifHash['GPS'], 'GPS' ); } if( !empty( $exifHash['EXIF'] )) { LibertyMime::storeMetaData( $pFileHash['attachment_id'], $exifHash['EXIF'], 'EXIF' ); } } return true; } /** * mime_image_get_exif_data fetch meta data from uploaded image * * @param array $pUpload uploaded file data * @access public * @return array filled with exif goodies */ function mime_image_get_exif_data( $pUpload ) { $exifHash = []; if( function_exists( 'exif_read_data' ) && !empty( $pUpload['source_file'] ) && is_file( $pUpload['source_file'] ) && preg_match( "#/(jpe?g|tiff)#i", $pUpload['type'] )) { // exif_read_data can be noisy due to crappy files, e.g. "Incorrect APP1 Exif Identifier Code" etc... $exifHash = @exif_read_data( $pUpload['source_file'], 0, true ); // extract more information if we can find it // if( ini_get( 'short_open_tag' )) { // require_once UTIL_PKG_CLASS_PATH.'metadata/src/Metadata.php'; // require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/JPEG.php'; // require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/JFIF.php'; // require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/PictureInfo.php'; // require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/XMP.php'; // require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/EXIF.php'; // $exifReader = new Metadata(); // $exifHash2 = $exifReader->read( $pUpload['source_file'], true, true ); // All data, read only /* // Retrieve the header information from the JPEG file $jpeg_header_data = \get_jpeg_header_data( $pUpload['source_file'] ); // Retrieve EXIF information from the JPEG file $Exif_array = \get_EXIF_JPEG( $pUpload['source_file'] ); // Retrieve XMP information from the JPEG file $XMP_array = read_XMP_array_from_text( get_XMP_text( $jpeg_header_data ) ); // Retrieve Photoshop IRB information from the JPEG file $IRB_array = \get_Photoshop_IRB( $jpeg_header_data ); if( !empty( $exifHash['IFD0']['Software'] ) && preg_match( '/photoshop/i', $exifHash['IFD0']['Software'] ) ) { require_once UTIL_PKG_INCLUDE_PATH.'jpeg_metadata_tk/Photoshop_File_Info.php'; // Retrieve Photoshop File Info from the three previous arrays $psFileInfo = get_photoshop_file_info( $Exif_array, $XMP_array, $IRB_array ); if( !empty( $psFileInfo['headline'] ) ) { $exifHash['headline'] = $psFileInfo['headline']; } if( !empty( $psFileInfo['caption'] ) ) { $exifHash['caption'] = $psFileInfo['caption']; } } } */ // only makes sense to store the GPS data if we at least have latitude and longitude if( !empty( $exifHash['GPS'] )) { // store GPS coordinates as deg decimal float $gpsConv = [ 'GPSLatitude', 'GPSDestLatitude', 'GPSLongitude', 'GPSDestLongitude' ]; foreach( $gpsConv as $conv ) { if( !empty( $exifHash['GPS'][$conv] ) && is_array( $exifHash['GPS'][$conv] )) { $exifHash['GPS'][$conv] = mime_image_convert_exifgps( $exifHash['GPS'][$conv] ); } } } } return $exifHash; } /** * mime_image_convert_exifgps GPS EIXF data is stored as fractions in an array. here we convert this to a degree decimal float value for easy storing * * @param array $pParams array of positional data in fractions form EXIF tag * @access public * @return numeric value of positional data */ function mime_image_convert_exifgps( $pParams ) { $ret = 0; if( !empty( $pParams ) && is_array( $pParams ) && count( $pParams ) == 3 ) { list( $lng['deg'], $lng['min'], $lng['sec'] ) = array_values( $pParams ); foreach( $lng as $key => $fraction ) { list( $dividend, $divisor ) = explode( "/", $fraction ); $num = $dividend / $divisor; if( $key == 'min' ) { $num /= 60; } elseif( $key == 'sec' ) { $num /= 3600; } $ret += $num; } } return $ret; } /** * mime_image_create_panorama * * @param array $pStoreRow * @access public * @return bool true on success, false on failure - mErrors will contain reason for failure */ function mime_image_create_panorama( &$pStoreRow ) { global $gBitSystem, $gThumbSizes; // we know the panorama image will be a jpeg, so we don't need the canThumbFunc check here if(( $panoramaFunc = liberty_get_function( 'panorama' )) && !empty( $pStoreRow['source_file'] ) && is_file( $pStoreRow['source_file'] )) { // the panorama has to be a jpg $gBitSystem->setConfig( 'liberty_thumbnail_format', 'jpg' ); $width = $gBitSystem->getConfig( 'mime_image_panorama_width', 3000 ); $gThumbSizes['panorama'] = [ $width, $width / 2 ]; // for the panorama, we will force a quality lower than 75 to reduce image size if( $gBitSystem->getConfig( 'liberty_thumbnail_quality', 85 ) > 75 ) { $gBitSystem->setConfig( 'liberty_thumbnail_quality', 75 ); } $genHash = [ 'attachment_id' => $pStoreRow['attachment_id'], '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 ) ] ), 'file_name' => dirname( $pStoreRow['file_name'] )."/", 'source_file' => $pStoreRow['source_file'], 'type' => $pStoreRow['mime_type'], 'thumbnail_sizes' => [ 'panorama' ], ]; if( liberty_generate_thumbnails( $genHash )) { // we want to modify the panorama $genHash['source_file'] = $genHash['icon_thumb_path']; if( !$panoramaFunc( $genHash )) { $pStoreRow['errors']['panorama'] = $genHash['error']; } } } return empty( $pStoreRow['errors'] ); } /** * liberty_magickwand_panorama_image - strictly speaking, this belongs in one of the image processing plugin files, but we'll leave it here for the moment * * @param array $pFileHash File hash - souce_file is required * @param array $pOptions * @access public * @return bool true on success, false on failure - mErrors will contain reason for failure */ /* MagicWand is no longer activly available? function liberty_magickwand_panorama_image( &$pFileHash, $pOptions = [] ) { $magickWand = new NewMagickWand(); $pFileHash['error'] = null; if( !empty( $pFileHash['source_file'] ) && is_file( $pFileHash['source_file'] )) { if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickReadImage( $magickWand, $pFileHash['source_file'] ), $magickWand )) { // calculate border width $iwidth = round( MagickGetImageWidth( $magickWand )); $iheight = round( MagickGetImageHeight( $magickWand )); $aspect = $iwidth / $iheight; $metaHash = [ 'width' => $iwidth, 'height' => $iheight, 'aspect' => $aspect, ]; // store original file information that we can adjust the viewer LibertyMime::storeMetaData( $pFileHash['attachment_id'], $metaHash, 'PANO' ); // we need to pad the image if the aspect ratio is not 2:1 (give it a wee bit of leeway that we don't add annoying borders if not really needed) if( $aspect > 2.1 || $aspect < 1.9 ) { $bwidth = $bheight = 0; if( $aspect > 2 ) { $bheight = round(( $iwidth / 2 - $iheight ) / 2 ); } else { $bwidth = round(( $iheight / 2 - $iwidth ) / 2 ); } // if the ratio has nothing to do with a panorama image - i.e. gives us a negative number here, we won't generate a panorama image if( $bheight > 0 ) { $pixelWand = NewPixelWand(); PixelSetColor( $pixelWand, !empty( $pOptions['background'] ) ? $pOptions['background'] : 'black' ); if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickBorderImage( $magickWand, $pixelWand, $bwidth, $bheight ), $magickWand )) { if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickWriteImage( $magickWand, $pFileHash['source_file'] ), $magickWand )) { // yay! } } DestroyPixelWand( $pixelWand ); } } } } DestroyMagickWand( $magickWand ); return empty( $pFileHash['error'] ); } */ /** * mime_image_help * * @access public * @return string */ function mime_image_help() { $help = KernelTools::tra( "If you have a panoramic image and you are using {attachment} to insert it, you can use panosize as you would with the size parameter to specify the size." )."
" .KernelTools::tra( "Example:" ).' '."{attachment id='13' panosize='small'}"; return $help; }