b2evolution PHP Cross Reference Blogging Systems

Source: /inc/files/upload.ctrl.php - 503 lines - 18245 bytes - Text - Print

Description: 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/}.

   1  <?php
   2  /**
   3   * This file implements the UI controller for file upload.
   4   *
   5   * This file is part of the evoCore framework - {@link http://evocore.net/}
   6   * See also {@link http://sourceforge.net/projects/evocms/}.
   7   *
   8   * @copyright (c)2003-2014 by Francois Planque - {@link http://fplanque.com/}
   9   * (dh please re-add)
  10   *
  11   * {@internal License choice
  12   * - If you have received this file as part of a package, please find the license.txt file in
  13   *   the same folder or the closest folder above for complete license terms.
  14   * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
  15   *   then you must choose one of the following licenses before using the file:
  16   *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  17   *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  18   * }}
  19   *
  20   * {@internal Open Source relicensing agreement:
  21   * (dh please re-add)
  22   * }}
  23   *
  24   * @package admin
  25   *
  26   * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  27   * @author fplanque: Francois PLANQUE.
  28   * (dh please re-add)
  29   *
  30   * @version $Id: upload.ctrl.php 6136 2014-03-08 07:59:48Z manuel $
  31   */
  32  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  33  
  34  /**
  35   * Filelist
  36   * 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
  37   * In cases like that, we should try to avoid instanciating a Filelist.
  38   */
  39  load_class( 'files/model/_filelist.class.php', 'FileList' );
  40  
  41  global $current_User, $Plugins;
  42  
  43  global $dispatcher;
  44  
  45  global $blog;
  46  
  47  global $item_ID, $iframe_name;
  48  
  49  
  50  // Check permission:
  51  $current_User->check_perm( 'files', 'add', true, $blog ? $blog : NULL );
  52  
  53  $AdminUI->set_path( 'files' );
  54  
  55  // Save the number of already existing error messages
  56  $initial_error_count = $Messages->count( 'error' );
  57  
  58  // Params that may need to be passed through:
  59  param( 'fm_mode', 'string', NULL, true );
  60  param( 'link_type', 'string', NULL, true );
  61  param( 'link_object_ID', 'integer', NULL, true );
  62  param( 'user_ID', 'integer', NULL, true );
  63  param( 'iframe_name', 'string', '', true );
  64  param( 'tab3', 'string' );
  65  if( empty( $tab3 ) )
  66  {
  67      $tab3 = param( 'tab3_onsubmit', 'string', 'quick' );
  68  }
  69  
  70  $action = param_action();
  71  
  72  if( $tab3 == 'quick' )
  73  {
  74      require_css( 'quick_upload.css' );
  75      //require_js( 'multiupload/sendfile.js' );
  76      //require_js( 'multiupload/quick_upload.js' );
  77      require_js( 'multiupload/fileuploader.js' );
  78      require_css( 'fileuploader.css' );
  79  }
  80  
  81  // INIT params:
  82  if( param( 'root_and_path', 'string', '', false ) /* not memorized (default) */ && strpos( $root_and_path, '::' ) )
  83  { // root and path together: decode and override (used by "radio-click-dirtree")
  84      list( $root, $path ) = explode( '::', $root_and_path, 2 );
  85      // Memorize new root:
  86      memorize_param( 'root', 'string', NULL );
  87      memorize_param( 'path', 'string', NULL );
  88  }
  89  else
  90  {
  91      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))
  92      param( 'path', 'string', '', true );  // the path relative to the root dir
  93      if( param( 'new_root', 'string', '' )
  94          && $new_root != $root )
  95      { // We have changed root in the select list
  96          $root = $new_root;
  97          $path = '';
  98      }
  99  }
 100  
 101  // Get root:
 102  $ads_list_path = false; // false by default, gets set if we have a valid root
 103  /**
 104   * @var FileRoot
 105   */
 106  $fm_FileRoot = NULL;
 107  
 108  $FileRootCache = & get_FileRootCache();
 109  
 110  $available_Roots = $FileRootCache->get_available_FileRoots();
 111  
 112  if( ! empty($root) )
 113  { // We have requested a root folder by string:
 114      $fm_FileRoot = & $FileRootCache->get_by_ID($root, true);
 115  
 116      if( ! $fm_FileRoot || !isset( $available_Roots[$fm_FileRoot->ID] ) || !$current_User->check_perm( 'files', 'add', false, $fm_FileRoot ) )
 117      { // Root not found or not in list of available ones. If avatar upload is in progress, the edited user root doesn't have to be available.
 118          $Messages->add( T_('You don\'t have upload permission to the requested root directory.'), 'warning' );
 119          $fm_FileRoot = false;
 120      }
 121  }
 122  
 123  if( ! $fm_FileRoot )
 124  { // No root requested (or the requested is invalid), get the first one available:
 125      if( $available_Roots )
 126      {
 127          foreach( $available_Roots as $l_FileRoot )
 128          {
 129              if( $current_User->check_perm( 'files', 'add', false, $l_FileRoot ) )
 130              { // select
 131                  $fm_FileRoot = $l_FileRoot;
 132                  break;
 133              }
 134          }
 135      }
 136      if( ! $fm_FileRoot )
 137      {
 138          $Messages->add( T_('You don\'t have access to any root directory.'), 'error' );
 139      }
 140  }
 141  
 142  if( $fm_FileRoot )
 143  { // We have access to a file root:
 144      if( empty($fm_FileRoot->ads_path) )
 145      {    // Not sure it's possible to get this far, but just in case...
 146          $Messages->add( sprintf( T_('The root directory &laquo;%s&raquo; does not exist.'), $fm_FileRoot->ads_path ), 'error' );
 147      }
 148      else
 149      { // Root exists
 150          // Let's get into requested list dir...
 151          $non_canonical_list_path = $fm_FileRoot->ads_path.$path;
 152  
 153          // Dereference any /../ just to make sure, and CHECK if directory exists:
 154          $ads_list_path = get_canonical_path( $non_canonical_list_path );
 155  
 156          if( !is_dir( $ads_list_path ) )
 157          { // This should never happen, but just in case the diretory does not exist:
 158              $Messages->add( sprintf( T_('The directory &laquo;%s&raquo; does not exist.'), $path ), 'error' );
 159              $path = '';        // fp> added
 160              $ads_list_path = NULL;
 161          }
 162          elseif( ! preg_match( '#^'.preg_quote($fm_FileRoot->ads_path, '#').'#', $ads_list_path ) )
 163          { // cwd is OUTSIDE OF root!
 164              $Messages->add( T_( 'You are not allowed to go outside your root directory!' ), 'error' );
 165              $path = '';        // fp> added
 166              $ads_list_path = $fm_FileRoot->ads_path;
 167          }
 168          elseif( $ads_list_path != $non_canonical_list_path )
 169          {    // We have reduced the absolute path, we should also reduce the relative $path (used in urls params)
 170              $path = get_canonical_path( $path );
 171          }
 172      }
 173  }
 174  
 175  
 176  file_controller_build_tabs();
 177  
 178  
 179  // If there were errors, display them and exit (especially in case there's no valid FileRoot ($fm_FileRoot)):
 180  // TODO: dh> this prevents users from uploading if _any_ blog media directory is not writable.
 181  //           See http://forums.b2evolution.net/viewtopic.php?p=49001#49001
 182  // Exit only if new error messages were added in this file
 183  if( $Messages->count( 'error' ) > $initial_error_count )
 184  {
 185      $AdminUI->set_path( 'files', 'upload', $tab3 );
 186  
 187      // Display <html><head>...</head> section! (Note: should be done early if actions do not redirect)
 188      $AdminUI->disp_html_head();
 189  
 190      // Display title, menu, messages, etc. (Note: messages MUST be displayed AFTER the actions)
 191      $AdminUI->disp_body_top();
 192      $AdminUI->disp_payload_begin();
 193      $AdminUI->disp_payload_end();
 194  
 195      $AdminUI->disp_global_footer();
 196      exit(0);
 197  }
 198  
 199  
 200  $Debuglog->add( 'FM root: '.var_export( $fm_FileRoot, true ), 'files' );
 201  $Debuglog->add( 'FM _ads_list_path: '.var_export( $ads_list_path, true ), 'files' );
 202  
 203  
 204  if( empty($ads_list_path) )
 205  { // We have no Root / list path, there was an error. Unset any action.
 206      $action = '';
 207  }
 208  
 209  
 210  
 211  
 212  // Check permissions:
 213  // Tblue> Note: Perm 'files' (level 'add') gets checked above with $assert = true.
 214  if( ! $Settings->get('upload_enabled') )
 215  { // Upload is globally disabled
 216      $Messages->add( T_('Upload is disabled.'), 'error' );
 217  }
 218  
 219  
 220  // If there were errors, display them and exit (especially in case there's no valid FileRoot ($fm_FileRoot)):
 221  // Exit only if new error messages were added in this file
 222  if( $Messages->count( 'error' ) > $initial_error_count )
 223  {
 224      $AdminUI->set_path( 'files', 'upload', $tab3 );
 225  
 226      $AdminUI->disp_html_head();
 227      // Display title, menu, messages, etc. (Note: messages MUST be displayed AFTER the actions)
 228      $AdminUI->disp_body_top();
 229      // Begin payload block:
 230      $AdminUI->disp_payload_begin();
 231      // nothing!
 232      // End payload block:
 233      $AdminUI->disp_payload_end();
 234      $AdminUI->disp_global_footer();
 235      exit(0);
 236  }
 237  
 238  
 239  // Quick mode means "just upload and leave mode when successful"
 240  param( 'upload_quickmode', 'integer', 0 );
 241  
 242  /**
 243   * Remember failed files (and the error messages)
 244   * @var array
 245   */
 246  $failedFiles = array();
 247  
 248  /**
 249   * Remember renamed files (and the messages)
 250   * @var array
 251   */
 252  param( 'renamedFiles', 'array/array/string', array(), true );
 253  $renamedMessages = array();
 254  
 255  // Process files we want to get from an URL:
 256  param( 'uploadfile_url', 'array/string', array() );
 257  param( 'uploadfile_source', 'array/string', array() );
 258  if( ( $action != 'switchtab' ) && $uploadfile_url )
 259  {
 260      // Check that this action request is not a CSRF hacked request:
 261      $Session->assert_received_crumb( 'file' );
 262  
 263      foreach($uploadfile_url as $k => $url)
 264      {
 265          if( ! isset($uploadfile_source[$k]) || $uploadfile_source[$k] != 'upload' )
 266          { // upload by URL has not been selected
 267              continue;
 268          }
 269          if( strlen($url) )
 270          {
 271              // Validate URL and parse it for the file name
 272              if( ! is_absolute_url($url)
 273                  || ! ($parsed_url = parse_url($url))
 274                  || empty($parsed_url['scheme']) || empty($parsed_url['host'])
 275                  || empty($parsed_url['path']) || $parsed_url['path'] == '/' )
 276              {    // Includes forbidding getting the root of a server
 277                  $failedFiles[$k] = T_('The URL must start with <code>http://</code> or <code>https://</code> and point to a valid file!');
 278                  continue;
 279              }
 280  
 281              $file_contents = fetch_remote_page($url, $info, NULL, $Settings->get('upload_maxkb'));
 282  
 283              if( $file_contents !== false )
 284              {
 285                  // Create temporary file and insert contents into it.
 286                  $tmpfile_name = tempnam(sys_get_temp_dir(), 'fmupload');
 287                  if( ! $tmpfile_name )
 288                  {
 289                      $failedFiles[$k] = 'Failed to find temporary directory.'; // no trans: very unlikely
 290                      continue;
 291                  }
 292  
 293                  if( ! save_to_file( $file_contents, $tmpfile_name, 'w' ) )
 294                  {
 295                      unlink($tmpfile_name);
 296                      $failedFiles[$k] = sprintf( 'Could not write to temporary file (%s).', $tmpfile_name );
 297                      continue;
 298                  }
 299  
 300                  // Fake/inject info into PHP's array of uploaded files.
 301                  // fp> TODO! This is a nasty dirty hack. That kind of stuff always breaks somewhere down the line. Needs cleanup.
 302                  // This allows us to treat it (nearly) the same way as regular uploads, apart from
 303                  // is_uploaded_file(), which we skip and move_uploaded_file() (where we use rename()).
 304                  $_FILES['uploadfile']['name'][$k] = rawurldecode(basename($parsed_url['path']));
 305                  $_FILES['uploadfile']['size'][$k] = evo_bytes($file_contents);
 306                  $_FILES['uploadfile']['error'][$k] = 0;
 307                  $_FILES['uploadfile']['tmp_name'][$k] = $tmpfile_name;
 308                  $_FILES['uploadfile']['_evo_fetched_url'][$k] = $url; // skip is_uploaded_file and keep info
 309                  unset($file_contents);
 310              }
 311              else
 312              {
 313                  $failedFiles[$k] = sprintf(
 314                      'Could not retrieve file. Error: %s (status %s). Used method: %s.',
 315                      $info['error'],
 316                      isset($info['status']) ? $info['status'] : '-',
 317                      isset($info['used_method']) ? $info['used_method'] : '-');
 318              }
 319          }
 320      }
 321  }
 322  
 323  // Process renaming/replacing of old versions:
 324  if( ! empty($renamedFiles) )
 325  {
 326      foreach( $renamedFiles as $rKey => $rData )
 327      {
 328          $replace_old = param( 'Renamed_'.$rKey, 'string', null );
 329          if( $replace_old == "Yes" )
 330          { // replace the old file with the new one
 331              $FileCache = & get_FileCache();
 332              $newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$renamedFiles[$rKey]['newName'], true );
 333              $oldFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$renamedFiles[$rKey]['oldName'], true );
 334              $new_filename = $newFile->get_name();
 335              $old_filename = $oldFile->get_name();
 336              $dir = $newFile->get_dir();
 337              $oldFile->rm_cache();
 338              $newFile->rm_cache();
 339              $error_message = '';
 340  
 341              // rename new uploaded file to temp file name
 342              $index = 0;
 343              $temp_filename = 'temp'.$index.'-'.$new_filename;
 344              while( file_exists( $dir.$temp_filename ) )
 345              { // find an unused filename
 346                  $index++;
 347                  $temp_filename = 'temp'.$index.'-'.$new_filename;
 348              }
 349  
 350              // @rename will overwrite a file with the same name if exists. In this case it shouldn't be a problem.
 351              if( ! @rename( $newFile->get_full_path(), $dir.$temp_filename ) )
 352              { // rename new file to temp file name failed
 353                  $error_message = $Messages->add( sprintf( T_('The new file could not be renamed to %s'), $temp_filename ), 'error' );
 354              }
 355  
 356              if( empty( $error_message ) && ( ! @rename( $oldFile->get_full_path(), $dir.$new_filename ) ) )
 357              { // rename original file to the new file name failed
 358                  $error_message = sprintf( T_( "The original file could not be renamed to %s. The new file is now named %s." ), $new_filename, $temp_filename );
 359              }
 360  
 361              if( empty( $error_message ) && ( ! @rename( $dir.$temp_filename, $dir.$old_filename ) ) )
 362              { // rename new file to the original file name failed
 363                  $error_message = sprintf( T_( "The new file could not be renamed to %s. It is now named %s." ), $old_filename, $temp_filename );
 364              }
 365  
 366              if( empty( $error_message ) )
 367              {
 368                  $Messages->add( sprintf( T_('%s has been replaced with the new version!'), $old_filename ), 'success' );
 369              }
 370              else
 371              {
 372                  $Messages->add( $error_message, 'error' );
 373              }
 374          }
 375      }
 376      forget_param( 'renamedFiles' );
 377      unset( $renamedFiles );
 378  
 379      if( $upload_quickmode )
 380      {
 381          header_redirect( regenerate_url( 'ctrl', 'ctrl=files', '', '&' ) );
 382      }
 383  }
 384  
 385  // Process uploaded files:
 386  if(  ( $action != 'switchtab' ) && isset($_FILES) && count( $_FILES ) )
 387  {
 388      // Check that this action request is not a CSRF hacked request:
 389      $Session->assert_received_crumb( 'file' );
 390  
 391      $upload_result = process_upload( $fm_FileRoot->ID, $path, false, false, $upload_quickmode );
 392      if( isset( $upload_result ) )
 393      {
 394          $failedFiles = $upload_result['failedFiles'];
 395          $uploadedFiles = $upload_result['uploadedFiles'];
 396          $renamedFiles = $upload_result['renamedFiles'];
 397          $renamedMessages = $upload_result['renamedMessages'];
 398  
 399          foreach( $uploadedFiles as $uploadedFile )
 400          {
 401              $success_msg = sprintf( T_('The file &laquo;%s&raquo; has been successfully uploaded to the server.'), $uploadedFile->dget('name') );
 402  
 403              // Allow to insert/link new upload into currently edited link object:
 404              if( $mode == 'upload' && !empty( $link_object_ID ) && !empty( $link_type ) )
 405              {    // The filemanager has been opened from a link owner object, offer to insert an img tag into original object.
 406                  $LinkOwner = get_link_owner( $link_type, $link_object_ID );
 407                  // TODO: Add plugin hook to allow generating JS insert code(s)
 408                  $img_tag = format_to_output( $uploadedFile->get_tag(), 'formvalue' );
 409                  if( $uploadedFile->is_image() )
 410                  {
 411                      $link_msg = $LinkOwner->translate( 'Link this image to your owner' );
 412                      $link_note = T_('recommended - allows automatic resizing');
 413                  }
 414                  else
 415                  {
 416                      $link_msg = $LinkOwner->translate( 'Link this file to your owner' );
 417                      $link_note = $LinkOwner->translate( 'The file will be linked for download at the end of the owner' );
 418                  }
 419                  $success_msg .= '<ul>'
 420                          .'<li>'.action_icon( T_('Link this file!'), 'link',
 421                                      regenerate_url( 'fm_selected,ctrl', 'ctrl=files&amp;action=link_inpost&amp;fm_selected[]='.rawurlencode($uploadedFile->get_rdfp_rel_path()).'&amp;'.url_crumb('file') ),
 422                                      ' '.$link_msg, 5, 5, array( 'target' => $iframe_name ) )
 423                          .' ('.$link_note.')</li>'
 424  
 425                          .'<li>'.T_('or').' <a href="#" onclick="if( window.focus && window.opener ){'
 426                          .'window.opener.focus(); textarea_wrap_selection( window.opener.document.getElementById(\''.$LinkOwner->type.'form_post_content\'), \''
 427                          .format_to_output( $uploadedFile->get_tag(), 'formvalue' ).'\', \'\', 1, window.opener.document ); } return false;">'
 428                          .$LinkOwner->translate( 'Insert the following code snippet into your owner' ).'</a> : <input type="text" value="'.$img_tag.'" size="60" /></li>'
 429                          // 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
 430                      .'</ul>';
 431              }
 432  
 433              $Messages->add( $success_msg, 'success' );
 434          }
 435      }
 436  
 437      if( $upload_quickmode && !empty($failedFiles) )
 438      {    // Transmit file error to next page!
 439          $Messages->add( $failedFiles[0], 'error' );
 440          unset($failedFiles);
 441      }
 442  
 443      if( empty($failedFiles) && empty($renamedFiles) )
 444      { // quick mode or no failed files, Go back to Browsing
 445          // header_redirect( $dispatcher.'?ctrl=files&root='.$fm_FileRoot->ID.'&path='.rawurlencode($path) );
 446          header_redirect( regenerate_url( 'ctrl', 'ctrl=files', '', '&' ) );
 447      }
 448  }
 449  
 450  
 451  file_controller_build_tabs();
 452  
 453  $AdminUI->set_path( 'files', 'upload', $tab3 );
 454  
 455  // fp> TODO: this here is a bit sketchy since we have Blog & fileroot not necessarilly in sync. Needs investigation / propositions.
 456  // Note: having both allows to post from any media dir into any blog.
 457  $AdminUI->breadcrumbpath_init( false );
 458  $AdminUI->breadcrumbpath_add( T_('Files'), '?ctrl=files&amp;blog=$blog$' );
 459  if( !isset($Blog) || $fm_FileRoot->type != 'collection' || $fm_FileRoot->in_type_ID != $Blog->ID )
 460  {    // Display only if we're not browsing our home blog
 461      $AdminUI->breadcrumbpath_add( $fm_FileRoot->name, '?ctrl=files&amp;blog=$blog$&amp;root='.$fm_FileRoot->ID,
 462              (isset($Blog) && $fm_FileRoot->type == 'collection') ? sprintf( T_('You are ready to post files from %s into %s...'),
 463              $fm_FileRoot->name, $Blog->get('shortname') ) : '' );
 464  }
 465  $AdminUI->breadcrumbpath_add( /* TRANS: noun */ T_('Upload'), '?ctrl=upload&amp;blog=$blog$&amp;root='.$fm_FileRoot->ID );
 466  switch( $tab3 )
 467  {
 468      case 'quick':
 469          $AdminUI->breadcrumbpath_add( T_('Quick'), '?ctrl=upload&amp;tab3='.$tab3 );
 470          break;
 471  
 472      case 'standard':
 473          $AdminUI->breadcrumbpath_add( T_('Standard'), '?ctrl=upload&amp;tab3='.$tab3 );
 474          break;
 475  
 476      case 'advanced':
 477          $AdminUI->breadcrumbpath_add( T_('Advanced'), '?ctrl=upload&amp;tab3='.$tab3 );
 478          break;
 479  }
 480  
 481  // Display <html><head>...</head> section! (Note: should be done early if actions do not redirect)
 482  $AdminUI->disp_html_head();
 483  
 484  // Display title, menu, messages, etc. (Note: messages MUST be displayed AFTER the actions)
 485  $AdminUI->disp_body_top();
 486  
 487  
 488  /*
 489   * Display payload:
 490   */
 491  if( $tab3 == 'quick' )
 492  {
 493      $AdminUI->disp_view( 'files/views/_file_quick_upload.view.php' );
 494  }
 495  else
 496  {
 497      $AdminUI->disp_view( 'files/views/_file_upload.view.php' );
 498  }
 499  
 500  // Display body bottom, debug info and close </html>:
 501  $AdminUI->disp_global_footer();
 502  
 503  ?>

title

Description

title

Description

title

Description

title

title

Body