0byt3m1n1
Path:
/
data
/
applications
/
aps
/
b2evolution
/
4.0.5-0
/
standard
/
htdocs
/
inc
/
files
/
[
Home
]
File: upload.ctrl.php
<?php /** * This file implements the UI controller for file upload. * * This file is part of the evoCore framework - {@link http://evocore.net/} * See also {@link http://sourceforge.net/projects/evocms/}. * * @copyright (c)2003-2010 by Francois PLANQUE - {@link http://fplanque.net/} * (dh please re-add) * * {@internal License choice * - If you have received this file as part of a package, please find the license.txt file in * the same folder or the closest folder above for complete license terms. * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) * then you must choose one of the following licenses before using the file: * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php * }} * * {@internal Open Source relicensing agreement: * (dh please re-add) * }} * * @package admin * * {@internal Below is a list of authors who have contributed to design/coding of this file: }} * @author fplanque: Francois PLANQUE. * (dh please re-add) * * @version $Id: upload.ctrl.php,v 1.37.2.2 2010/10/27 14:56:18 efy-asimo Exp $ */ if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); /** * Filelist * fp>> TODO: When the user is viewing details for a file he should (by default) not be presented with the filelist in addition to the file properties * In cases like that, we should try to avoid instanciating a Filelist. */ load_class( 'files/model/_filelist.class.php', 'FileList' ); global $current_User, $Plugins; global $dispatcher; global $blog; global $item_ID, $iframe_name; // Check permission: $current_User->check_perm( 'files', 'add', true, $blog ? $blog : NULL ); $AdminUI->set_path( 'files', 'upload' ); // Params that may need to be passed through: param( 'fm_mode', 'string', NULL, true ); param( 'item_ID', 'integer', NULL, true ); param( 'user_ID', 'integer', NULL, true ); param( 'iframe_name', 'string', '', true ); $action = param_action(); // Standard vs Advanced mode param( 'uploadwithproperties', 'integer', NULL, false ); if( !is_null($uploadwithproperties) ) { $UserSettings->set( 'fm_uploadwithproperties', $uploadwithproperties ); $UserSettings->dbupdate(); } else { $uploadwithproperties = $UserSettings->get( 'fm_uploadwithproperties' ); } // INIT params: if( param( 'root_and_path', 'string', '', false ) /* not memorized (default) */ && strpos( $root_and_path, '::' ) ) { // root and path together: decode and override (used by "radio-click-dirtree") list( $root, $path ) = explode( '::', $root_and_path, 2 ); // Memorize new root: memorize_param( 'root', 'string', NULL ); memorize_param( 'path', 'string', NULL ); } else { param( 'root', 'string', NULL, true ); // the root directory from the dropdown box (user_X or blog_X; X is ID - 'user' for current user (default)) param( 'path', 'string', '', true ); // the path relative to the root dir if( param( 'new_root', 'string', '' ) && $new_root != $root ) { // We have changed root in the select list $root = $new_root; $path = ''; } } // Get root: $ads_list_path = false; // false by default, gets set if we have a valid root /** * @var FileRoot */ $fm_FileRoot = NULL; $FileRootCache = & get_FileRootCache(); $available_Roots = $FileRootCache->get_available_FileRoots(); if( ! empty($root) ) { // We have requested a root folder by string: $fm_FileRoot = & $FileRootCache->get_by_ID($root, true); if( ! $fm_FileRoot || ! isset( $available_Roots[$fm_FileRoot->ID] ) ) { // Root not found or not in list of available ones $Messages->add( T_('You don\'t have access to the requested root directory.'), 'error' ); $fm_FileRoot = false; } } if( ! $fm_FileRoot ) { // No root requested (or the requested is invalid), get the first one available: if( $available_Roots && ( $tmp_keys = array_keys( $available_Roots ) ) && $first_Root = & $available_Roots[ $tmp_keys[0] ] ) { // get the first one $fm_FileRoot = & $first_Root; } else { $Messages->add( T_('You don\'t have access to any root directory.'), 'error' ); } } if( $fm_FileRoot ) { // We have access to a file root: if( empty($fm_FileRoot->ads_path) ) { // Not sure it's possible to get this far, but just in case... $Messages->add( sprintf( T_('The root directory «%s» does not exist.'), $fm_FileRoot->ads_path ), 'error' ); } else { // Root exists // Let's get into requested list dir... $non_canonical_list_path = $fm_FileRoot->ads_path.$path; // Dereference any /../ just to make sure, and CHECK if directory exists: $ads_list_path = get_canonical_path( $non_canonical_list_path ); if( !is_dir( $ads_list_path ) ) { // This should never happen, but just in case the diretory does not exist: $Messages->add( sprintf( T_('The directory «%s» does not exist.'), $path ), 'error' ); $path = ''; // fp> added $ads_list_path = NULL; } elseif( ! preg_match( '#^'.preg_quote($fm_FileRoot->ads_path, '#').'#', $ads_list_path ) ) { // cwd is OUTSIDE OF root! $Messages->add( T_( 'You are not allowed to go outside your root directory!' ), 'error' ); $path = ''; // fp> added $ads_list_path = $fm_FileRoot->ads_path; } elseif( $ads_list_path != $non_canonical_list_path ) { // We have reduced the absolute path, we should also reduce the relative $path (used in urls params) $path = get_canonical_path( $path ); } } } file_controller_build_tabs(); // If there were errors, display them and exit (especially in case there's no valid FileRoot ($fm_FileRoot)): // TODO: dh> this prevents users from uploading if _any_ blog media directory is not writable. // See http://forums.b2evolution.net/viewtopic.php?p=49001#49001 if( $Messages->count('error') ) { // Display <html><head>...</head> section! (Note: should be done early if actions do not redirect) $AdminUI->disp_html_head(); // Display title, menu, messages, etc. (Note: messages MUST be displayed AFTER the actions) $AdminUI->disp_body_top(); $AdminUI->disp_payload_begin(); $AdminUI->disp_payload_end(); $AdminUI->disp_global_footer(); exit(0); } $Debuglog->add( 'FM root: '.var_export( $fm_FileRoot, true ), 'files' ); $Debuglog->add( 'FM _ads_list_path: '.var_export( $ads_list_path, true ), 'files' ); if( empty($ads_list_path) ) { // We have no Root / list path, there was an error. Unset any action. $action = ''; } // Check permissions: // Tblue> Note: Perm 'files' (level 'add') gets checked above with $assert = true. if( ! $Settings->get('upload_enabled') ) { // Upload is globally disabled $Messages->add( T_('Upload is disabled.'), 'error' ); } // If there were errors, display them and exit (especially in case there's no valid FileRoot ($fm_FileRoot)): if( $Messages->count('error') ) { $AdminUI->disp_html_head(); // Display title, menu, messages, etc. (Note: messages MUST be displayed AFTER the actions) $AdminUI->disp_body_top(); // Begin payload block: $AdminUI->disp_payload_begin(); // nothing! // End payload block: $AdminUI->disp_payload_end(); $AdminUI->disp_global_footer(); exit(0); } // Quick mode means "just upload and leave mode when successful" param( 'upload_quickmode', 'integer', 0 ); /** * Remember failed files (and the error messages) * @var array */ $failedFiles = array(); /** * Remember renamed files (and the messages) * @var array */ param( 'renamedFiles', 'array', array(), true ); $renamedMessages = array(); // Process files we want to get from an URL: param( 'uploadfile_url', 'array', array() ); param( 'uploadfile_source', 'array', array() ); if( $uploadfile_url ) { // Check that this action request is not a CSRF hacked request: $Session->assert_received_crumb( 'file' ); foreach($uploadfile_url as $k => $url) { if( ! isset($uploadfile_source[$k]) || $uploadfile_source[$k] != 'upload' ) { // upload by URL has not been selected continue; } if( strlen($url) ) { // Validate URL and parse it for the file name if( ! is_absolute_url($url) || ! ($parsed_url = parse_url($url)) || empty($parsed_url['scheme']) || empty($parsed_url['host']) || empty($parsed_url['path']) || $parsed_url['path'] == '/' ) { // Includes forbidding getting the root of a server $failedFiles[$k] = T_('The URL must start with <code>http://</code> or <code>https://</code> and point to a valid file!'); continue; } $file_contents = fetch_remote_page($url, $info, NULL, $Settings->get('upload_maxkb')); if( $file_contents !== false ) { // Create temporary file and insert contents into it. $tmpfile_name = tempnam(sys_get_temp_dir(), 'fmupload'); if( ! $tmpfile_name ) { $failedFiles[$k] = 'Failed to find temporary directory.'; // no trans: very unlikely continue; } $tmpfile = fopen($tmpfile_name, 'w'); if( ! fwrite($tmpfile, $file_contents) ) { unlink($tmpfile); $failedFiles[$k] = sprintf( 'Could not write to temporary file (%s).', $tmpfile ); continue; } fclose($tmpfile); // Fake/inject info into PHP's array of uploaded files. // fp> TODO! This is a nasty dirty hack. That kind of stuff always breaks somewhere down the line. Needs cleanup. // This allows us to treat it (nearly) the same way as regular uploads, apart from // is_uploaded_file(), which we skip and move_uploaded_file() (where we use rename()). $_FILES['uploadfile']['name'][$k] = rawurldecode(basename($parsed_url['path'])); $_FILES['uploadfile']['size'][$k] = evo_bytes($file_contents); $_FILES['uploadfile']['error'][$k] = 0; $_FILES['uploadfile']['tmp_name'][$k] = $tmpfile_name; $_FILES['uploadfile']['_evo_fetched_url'][$k] = $url; // skip is_uploaded_file and keep info unset($file_contents); } else { $failedFiles[$k] = sprintf( T_('Could not retrieve file. Error: %s (status %s). Used method: %s.'), $info['error'], isset($info['status']) ? $info['status'] : '-', isset($info['used_method']) ? $info['used_method'] : '-'); } } } } // Process renaming/replacing of old versions: if( ! empty($renamedFiles) ) { foreach( $renamedFiles as $rKey => $rData ) { $replace_old = param( 'Renamed_'.$rKey, 'string', null ); if( $replace_old == "Yes" ) { // replace the old file with the new one $FileCache = & get_FileCache(); $newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$renamedFiles[$rKey]['newName'], true ); $oldFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$renamedFiles[$rKey]['oldName'], true ); $new_filename = $newFile->get_name(); $old_filename = $oldFile->get_name(); $dir = $newFile->get_dir(); $oldFile->rm_cache(); $newFile->rm_cache(); $error_message = ''; // rename new uploaded file to temp file name $index = 0; $temp_filename = 'temp'.$index.'-'.$new_filename; while( file_exists( $dir.$temp_filename ) ) { // find an unused filename $index++; $temp_filename = 'temp'.$index.'-'.$new_filename; } // @rename will overwrite a file with the same name if exists. In this case it shouldn't be a problem. if( ! @rename( $newFile->get_full_path(), $dir.$temp_filename ) ) { // rename new file to temp file name failed $error_message = $Messages->add( sprintf( T_('The new file could not be renamed to %s'), $temp_filename ), 'error' ); } if( empty( $error_message ) && ( ! @rename( $oldFile->get_full_path(), $dir.$new_filename ) ) ) { // rename original file to the new file name failed $error_message = sprintf( T_( "The original file could not be renamed to %s. The new file is now named %s." ), $new_filename, $temp_filename ); } if( empty( $error_message ) && ( ! @rename( $dir.$temp_filename, $dir.$old_filename ) ) ) { // rename new file to the original file name failed $error_message = sprintf( T_( "The new file could not be renamed to %s. It is now named %s." ), $old_filename, $temp_filename ); } if( empty( $error_message ) ) { $Messages->add( sprintf( T_('%s has been replaced with the new version!'), $old_filename ), 'success' ); } else { $Messages->add( $error_message, 'error' ); } } } forget_param( 'renamedFiles' ); unset( $renamedFiles ); if( $upload_quickmode ) { header_redirect( regenerate_url( 'ctrl', 'ctrl=files', '', '&' ) ); } } // Process uploaded files: if( isset($_FILES) && count( $_FILES ) ) { // Check that this action request is not a CSRF hacked request: $Session->assert_received_crumb( 'file' ); // Some files have been uploaded: param( 'uploadfile_title', 'array', array() ); param( 'uploadfile_alt', 'array', array() ); param( 'uploadfile_desc', 'array', array() ); param( 'uploadfile_name', 'array', array() ); foreach( $_FILES['uploadfile']['name'] as $lKey => $lName ) { if( empty( $lName ) ) { // No file name if( $upload_quickmode || !empty( $uploadfile_title[$lKey] ) || !empty( $uploadfile_alt[$lKey] ) || !empty( $uploadfile_desc[$lKey] ) || !empty( $uploadfile_name[$lKey] ) ) { // User specified params but NO file!!! // Remember the file as failed when additional info provided. $failedFiles[$lKey] = T_( 'Please select a local file to upload.' ); } // Abort upload for this file: continue; } if( $Settings->get( 'upload_maxkb' ) && $_FILES['uploadfile']['size'][$lKey] > $Settings->get( 'upload_maxkb' )*1024 ) { // bigger than defined by blog $failedFiles[$lKey] = sprintf( T_('The file is too large: %s but the maximum allowed is %s.'), bytesreadable( $_FILES['uploadfile']['size'][$lKey] ), bytesreadable($Settings->get( 'upload_maxkb' )*1024) ); // Abort upload for this file: continue; } if( $_FILES['uploadfile']['error'][$lKey] ) { // PHP has detected an error!: switch( $_FILES['uploadfile']['error'][$lKey] ) { case UPLOAD_ERR_FORM_SIZE: // The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form. // This can easily be changed, so we do not use it.. file size gets checked for real just above. break; case UPLOAD_ERR_INI_SIZE: // bigger than allowed in php.ini $failedFiles[$lKey] = T_('The file exceeds the upload_max_filesize directive in php.ini.'); // Abort upload for this file: continue; case UPLOAD_ERR_PARTIAL: $failedFiles[$lKey] = T_('The file was only partially uploaded.'); // Abort upload for this file: continue; case UPLOAD_ERR_NO_FILE: // Is probably the same as empty($lName) before. $failedFiles[$lKey] = T_('No file was uploaded.'); // Abort upload for this file: continue; case 6: // numerical value of UPLOAD_ERR_NO_TMP_DIR # (min_php: 4.3.10, 5.0.3) case UPLOAD_ERR_NO_TMP_DIR: // Missing a temporary folder. $failedFiles[$lKey] = T_('Missing a temporary folder (upload_tmp_dir in php.ini).'); // Abort upload for this file: continue; default: $failedFiles[$lKey] = T_('Unknown error.').' #'.$_FILES['uploadfile']['error'][$lKey]; // Abort upload for this file: continue; } } if( ! isset($_FILES['uploadfile']['_evo_fetched_url'][$lKey]) // skip check for fetched URLs && ! is_uploaded_file( $_FILES['uploadfile']['tmp_name'][$lKey] ) ) { // Ensure that a malicious user hasn't tried to trick the script into working on files upon which it should not be working. $failedFiles[$lKey] = T_('The file does not seem to be a valid upload! It may exceed the upload_max_filesize directive in php.ini.'); // Abort upload for this file: continue; } // Use new name on server if specified: $newName = !empty( $uploadfile_name[ $lKey ] ) ? $uploadfile_name[ $lKey ] : $lName; if( $error_filename = validate_filename( $newName ) ) { // Not a file name or not an allowed extension $failedFiles[$lKey] = $error_filename; // Abort upload for this file: continue; } $uploadfile_path = $_FILES['uploadfile']['tmp_name'][$lKey]; $image_info = getimagesize($uploadfile_path); if( $image_info ) { // This is an image, validate mimetype vs. extension $FiletypeCache = get_Cache('FiletypeCache'); $correct_Filetype = $FiletypeCache->get_by_mimetype($image_info['mime']); $correct_extension = array_shift($correct_Filetype->get_extensions()); $path_info = pathinfo($newName); $current_extension = $path_info['extension']; if( strtolower($current_extension) != strtolower($correct_extension) ) { $old_name = $newName; $newName = $path_info['filename'].'.'.$correct_extension; $Messages->add( sprintf(T_('The extension of the file «%s» has been corrected. The new filename is «%s».'), $old_name, $newName), 'warning' ); } } // Get File object for requested target location: $FileCache = & get_FileCache(); $newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$newName, true ); $num_ext = 0; $oldName = $newName; while( $newFile->exists() ) { // The file already exists in the target location! $num_ext++; $ext_pos = strrpos( $newName, '.'); if( $num_ext == 1 ) { $newName = substr_replace( $newName, '-'.$num_ext.'.', $ext_pos, 1 ); if( $image_info ) { $oldFile_thumb = $newFile->get_preview_thumb( 'fulltype' ); } else { $oldFile_thumb = $newFile->get_size_formatted(); } } else { $replace_length = strlen( '-'.($num_ext-1) ); $newName = substr_replace( $newName, '-'.$num_ext, $ext_pos-$replace_length, $replace_length ); } $newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$newName, true ); } // Trigger plugin event if( $Plugins->trigger_event_first_false( 'AfterFileUpload', array( 'File' => & $newFile, 'name' => & $_FILES['uploadfile']['name'][$lKey], 'type' => & $_FILES['uploadfile']['type'][$lKey], 'tmp_name' => & $_FILES['uploadfile']['tmp_name'][$lKey], 'size' => & $_FILES['uploadfile']['size'][$lKey], ) ) ) { // Plugin returned 'false'. Abort file upload continue; } // Attempt to move the uploaded file to the requested target location: if( isset($_FILES['uploadfile']['_evo_fetched_url'][$lKey]) ) { // fetched remotely if( ! rename( $_FILES['uploadfile']['tmp_name'][$lKey], $newFile->get_full_path() ) ) { $failedFiles[$lKey] = T_('An unknown error occurred when moving the uploaded file on the server.'); // Abort upload for this file: continue; } } elseif( ! move_uploaded_file( $_FILES['uploadfile']['tmp_name'][$lKey], $newFile->get_full_path() ) ) { $failedFiles[$lKey] = T_('An unknown error occurred when moving the uploaded file on the server.'); // Abort upload for this file: continue; } // change to default chmod settings if( $newFile->chmod( NULL ) === false ) { // add a note, this is no error! $Messages->add( sprintf( T_('Could not change permissions of «%s» to default chmod setting.'), $newFile->dget('name') ), 'note' ); } // Refreshes file properties (type, size, perms...) $newFile->load_properties(); if( $num_ext ) { // The file name was changed! if( $image_info ) { $newFile_thumb = $newFile->get_preview_thumb( 'fulltype' ); } else { $newFile_thumb = $newFile->get_size_formatted(); } //$newFile_size = bytesreadable ($_FILES['uploadfile']['size'][$lKey]); $renamedMessages[$lKey]['message'] = sprintf( T_('"%s was renamed to %s. Would you like to replace %s with the new version instead?'), '«'.$oldName.'»', '«'.$newName.'»', '«'.$oldName.'»' ); $renamedMessages[$lKey]['oldThumb'] = $oldFile_thumb; $renamedMessages[$lKey]['newThumb'] = $newFile_thumb; $renamedFiles[$lKey]['oldName'] = $oldName; $renamedFiles[$lKey]['newName'] = $newName; } // Store extra info about the file into File Object: if( isset( $uploadfile_title[$lKey] ) ) { // If a title text has been passed... (does not happen in quick upload mode) $newFile->set( 'title', trim( strip_tags($uploadfile_title[$lKey])) ); } if( isset( $uploadfile_alt[$lKey] ) ) { // If an alt text has been passed... (does not happen in quick upload mode) $newFile->set( 'alt', trim( strip_tags($uploadfile_alt[$lKey])) ); } if( isset( $uploadfile_desc[$lKey] ) ) { // If a desc text has been passed... (does not happen in quick upload mode) $newFile->set( 'desc', trim( strip_tags($uploadfile_desc[$lKey])) ); } // TODO: dh> store _evo_fetched_url (source URL) somewhere (e.g. at the end of desc)? // fp> no. why? $success_msg = sprintf( T_('The file «%s» has been successfully uploaded to the server.'), $newFile->dget('name') ); // Allow to insert/link new upload into currently edited post: if( $mode =='upload' && !empty($item_ID) ) { // The filemanager has been opened from an Item, offer to insert an img tag into original post. // TODO: Add plugin hook to allow generating JS insert code(s) $img_tag = format_to_output( $newFile->get_tag(), 'formvalue' ); if( $newFile->is_image() ) { $link_msg = T_('Link this image to your post'); $link_note = T_('recommended - allows automatic resizing'); } else { $link_msg = T_('Link this file to your post'); $link_note = T_('The file will be appended for download at the end of the post'); } $success_msg .= '<ul>' .'<li>'.action_icon( T_('Link this file!'), 'link', regenerate_url( 'fm_selected,ctrl', 'ctrl=files&action=link_inpost&fm_selected[]='.rawurlencode($newFile->get_rdfp_rel_path()).'&'.url_crumb('file') ), ' '.$link_msg, 5, 5, array( 'target' => $iframe_name ) ) .' ('.$link_note.')</li>' .'<li>'.T_('or').' <a href="#" onclick="if( window.focus && window.opener ){' .'window.opener.focus(); textarea_wrap_selection( window.opener.document.getElementById(\'itemform_post_content\'), \'' .format_to_output( $newFile->get_tag(), 'formvalue' ).'\', \'\', 1, window.opener.document ); } return false;">' .T_('Insert the following code snippet into your post').'</a> : <input type="text" value="'.$img_tag.'" size="60" /></li>' // fp> TODO: it would be supacool to have an ajaxy "tumbnail size selector" here that generates a thumnail of requested size on server and then changes the code in the input above .'</ul>'; } $Messages->add( $success_msg, 'success' ); // Store File object into DB: $newFile->dbsave(); } if( $upload_quickmode && !empty($failedFiles) ) { // Transmit file error to next page! $Messages->add( $failedFiles[0], 'error' ); unset($failedFiles); } if( empty($failedFiles) && empty($renamedFiles) ) { // quick mode or no failed files, Go back to Browsing // header_redirect( $dispatcher.'?ctrl=files&root='.$fm_FileRoot->ID.'&path='.rawurlencode($path) ); header_redirect( regenerate_url( 'ctrl', 'ctrl=files', '', '&' ) ); } } file_controller_build_tabs(); // fp> TODO: this here is a bit sketchy since we have Blog & fileroot not necessarilly in sync. Needs investigation / propositions. // Note: having both allows to post from any media dir into any blog. $AdminUI->breadcrumbpath_init(); $AdminUI->breadcrumbpath_add( T_('Files'), '?ctrl=files&blog=$blog$' ); if( !isset($Blog) || $fm_FileRoot->type != 'collection' || $fm_FileRoot->in_type_ID != $Blog->ID ) { // Display only if we're not browsing our home blog $AdminUI->breadcrumbpath_add( $fm_FileRoot->name, '?ctrl=files&blog=$blog$&root='.$fm_FileRoot->ID, (isset($Blog) && $fm_FileRoot->type == 'collection') ? sprintf( T_('You are ready to post files from %s into %s...'), $fm_FileRoot->name, $Blog->get('shortname') ) : '' ); } $AdminUI->breadcrumbpath_add( T_('Upload'), '?ctrl=upload&blog=$blog$&root='.$fm_FileRoot->ID ); // Display <html><head>...</head> section! (Note: should be done early if actions do not redirect) $AdminUI->disp_html_head(); // Display title, menu, messages, etc. (Note: messages MUST be displayed AFTER the actions) $AdminUI->disp_body_top(); /* * Display payload: */ $AdminUI->disp_view( 'files/views/_file_upload.view.php' ); // Display body bottom, debug info and close </html>: $AdminUI->disp_global_footer(); /* * $Log: upload.ctrl.php,v $ */ ?>