/** * Upload the file * * @param &object &$attachment the partially constructed attachment object * @param &object &$parent An Attachments plugin parent object with partial parent info including: * $parent->new : True if the parent has not been created yet * (like adding attachments to an article before it has been saved) * $parent->title : Title/name of the parent object * @param int $attachment_id false if this is a new attachment * @param string $save_type 'update' or 'update' * * @return a message indicating succes or failure * * NOTE: The caller should set up all the parent info in the record before calling this * (see $parent->* below for necessary items) */ public static function upload_file(&$attachment, &$parent, $attachment_id = false, $save_type = 'update') { $user = JFactory::getUser(); $db = JFactory::getDBO(); $from = JRequest::getWord('from'); // Figure out if the user may publish this attachment $may_publish = $parent->userMayChangeAttachmentState($attachment->parent_id, $attachment->parent_entity, $attachment->created_by); // Get the component parameters jimport('joomla.application.component.helper'); $params = JComponentHelper::getParams('com_attachments'); // Make sure the attachments directory exists $upload_dir = JPATH_SITE . '/' . AttachmentsDefines::$ATTACHMENTS_SUBDIR; $secure = $params->get('secure', false); if (!AttachmentsHelper::setup_upload_directory($upload_dir, $secure)) { $errmsg = JText::sprintf('ATTACH_ERROR_UNABLE_TO_SETUP_UPLOAD_DIR_S', $upload_dir) . ' (ERR 33)'; JError::raiseError(500, $errmsg); } // If we are updating, note the name of the old filename $old_filename = null; $old_filename_sys = null; $old_uri_type = $attachment->uri_type; if ($old_uri_type) { $old_filename = $attachment->filename; $old_filename_sys = $attachment->filename_sys; } // Get the new filename // (Note: The following replacement is necessary to allow // single quotes in filenames to work correctly.) // Trim of any trailing period (to avoid exploits) $filename = rtrim(JString::str_ireplace("\\'", "'", $_FILES['upload']['name']), '.'); $ftype = $_FILES['upload']['type']; // Check the file size $max_upload_size = (int) ini_get('upload_max_filesize'); $max_attachment_size = (int) $params->get('max_attachment_size', 0); if ($max_attachment_size == 0) { $max_attachment_size = $max_upload_size; } $max_size = min($max_upload_size, $max_attachment_size); $file_size = filesize($_FILES['upload']['tmp_name']) / 1048576.0; if ($file_size > $max_size) { $errmsg = JText::sprintf('ATTACH_ERROR_FILE_S_TOO_BIG_N_N_N', $filename, $file_size, $max_attachment_size, $max_upload_size); JError::raiseError(500, '<b>' . $errmsg . '</b>'); } // Get the maximum allowed filename length (for the filename display) $max_filename_length = (int) $params->get('max_filename_length', 0); if ($max_filename_length == 0) { $max_filename_length = AttachmentsDefines::$MAXIMUM_FILENAME_LENGTH; } else { $max_filename_length = min($max_filename_length, AttachmentsDefines::$MAXIMUM_FILENAME_LENGTH); } // Truncate the filename, if necessary and alert the user if (JString::strlen($filename) > $max_filename_length) { $filename = AttachmentsHelper::truncate_filename($filename, $max_filename_length); $msg = JText::_('ATTACH_WARNING_FILENAME_TRUNCATED'); $app = JFactory::getApplication(); if ($app->isAdmin()) { $lang = JFactory::getLanguage(); if ($lang->isRTL()) { $msg = "'{$filename}' " . $msg; } else { $msg = $msg . " '{$filename}'"; } $app->enqueueMessage($msg, 'warning'); } else { $msg .= "\\n \\'{$filename}\\'"; echo "<script type=\"text/javascript\">alert('{$msg}')</script>"; } } // Check the filename for bad characters $bad_chars = false; $forbidden_chars = $params->get('forbidden_filename_characters', '#=?%&'); for ($i = 0; $i < strlen($forbidden_chars); $i++) { $char = $forbidden_chars[$i]; if (strpos($filename, $char) !== false) { $bad_chars = true; break; } } // Check for double-extension exploit $bad_filename = false; if (AttachmentsHelper::is_double_extension_exploit($filename)) { $bad_filename = true; } // Set up the entity name for display $parent_entity = $parent->getCanonicalEntityId($attachment->parent_entity); $parent_entity_name = JText::_('ATTACH_' . $parent_entity); // A little formatting $msgbreak = '<br />'; $app = JFactory::getApplication(); if ($app->isAdmin()) { $msgbreak = ''; } // Make sure a file was successfully uploaded if ($_FILES['upload']['size'] == 0 && $_FILES['upload']['tmp_name'] == '' || $bad_chars || $bad_filename) { // Guess the type of error if ($bad_chars) { $error = 'bad_chars'; $error_msg = JText::sprintf('ATTACH_ERROR_BAD_CHARACTER_S_IN_FILENAME_S', $char, $filename); if ($app->isAdmin()) { $result = new JObject(); $result->error = true; $result->error_msg = $error_msg; return $result; } } elseif ($bad_filename) { $error = 'illegal_file_extension'; $format = JString::strtolower(JFile::getExt($filename)); $error_msg = JText::_('ATTACH_ERROR_ILLEGAL_FILE_EXTENSION') . " .php.{$format}"; if ($app->isAdmin()) { $result = new JObject(); $result->error = true; $result->error_msg = $error_msg; return $result; } } elseif ($filename == '') { $error = 'no_file'; $error_msg = JText::sprintf('ATTACH_ERROR_UPLOADING_FILE_S', $filename); $error_msg .= $msgbreak . ' (' . JText::_('ATTACH_YOU_MUST_SELECT_A_FILE_TO_UPLOAD') . ')'; if ($app->isAdmin()) { $result = new JObject(); $result->error = true; $result->error_msg = $error_msg; return $result; } } else { $error = 'file_too_big'; $error_msg = JText::sprintf('ATTACH_ERROR_UPLOADING_FILE_S', $filename); $error_msg .= $msgbreak . '(' . JText::_('ATTACH_ERROR_MAY_BE_LARGER_THAN_LIMIT') . ' '; $error_msg .= get_cfg_var('upload_max_filesize') . ')'; if ($app->isAdmin()) { $result = new JObject(); $result->error = true; $result->error_msg = $error_msg; return $result; } } // Set up the view to redisplay the form with warnings if ($save_type == 'update') { require_once JPATH_COMPONENT_SITE . '/views/update/view.html.php'; $view = new AttachmentsViewUpdate(); AttachmentsHelper::add_view_urls($view, 'update', $attachment->parent_id, $attachment->parent_type, $attachment_id, $from); $view->update = JRequest::getWord('update'); } else { require_once JPATH_COMPONENT_SITE . '/views/upload/view.html.php'; $view = new AttachmentsViewUpload(); AttachmentsHelper::add_view_urls($view, 'upload', $attachment->parent_id, $attachment->parent_type, null, $from); } // Suppress the display filename if we are changing from file to url $display_name = $attachment->display_name; if ($save_type == 'update') { $new_uri_type = JRequest::getWord('update'); if ($new_uri_type && ($new_uri_type == 'file' || $new_uri_type != $attachment->uri_type)) { $attachment->display_name = ''; } } // Set up the view $view->attachment = $attachment; $view->new_parent = $parent->new; $view->parent = $parent; $view->params = $params; $view->from = $from; $view->Itemid = JRequest::getInt('Itemid', 1); $view->error = $error; $view->error_msg = $error_msg; // Display the view $view->display(); exit; } // Make sure the file type is okay (respect restrictions imposed by media manager) $cmparams = JComponentHelper::getParams('com_media'); // Check to make sure the extension is allowed jimport('joomla.filesystem.file'); $allowable = explode(',', $cmparams->get('upload_extensions')); $ignored = explode(',', $cmparams->get('ignore_extensions')); $extension = JString::strtolower(JFile::getExt($filename)); $error = false; $error_msg = false; if (!in_array($extension, $allowable) && !in_array($extension, $ignored)) { $error = 'illegal_file_extension'; $error_msg = JText::sprintf('ATTACH_ERROR_UPLOADING_FILE_S', $filename); $error_msg .= "<br />" . JText::_('ATTACH_ERROR_ILLEGAL_FILE_EXTENSION') . " {$extension}"; if ($user->authorise('core.admin')) { $error_msg .= "<br />" . JText::_('ATTACH_ERROR_CHANGE_IN_MEDIA_MANAGER'); } } // Check to make sure the mime type is okay if ($cmparams->get('restrict_uploads', true)) { if ($cmparams->get('check_mime', true)) { $allowed_mime = explode(',', $cmparams->get('upload_mime')); $illegal_mime = explode(',', $cmparams->get('upload_mime_illegal')); if (JString::strlen($ftype) && !in_array($ftype, $allowed_mime) && in_array($ftype, $illegal_mime)) { $error = 'illegal_mime_type'; $error_msg = JText::sprintf('ATTACH_ERROR_UPLOADING_FILE_S', $filename); $error_msg .= ', ' . JText::_('ATTACH_ERROR_ILLEGAL_FILE_MIME_TYPE') . " {$ftype}"; if ($user->authorise('core.admin')) { $error_msg .= "\t <br />" . JText::_('ATTACH_ERROR_CHANGE_IN_MEDIA_MANAGER'); } } } } // If it is an image file, make sure it is a valid image file (and not some kind of exploit) if (AttachmentsHelper::is_image_file($filename)) { if (!AttachmentsHelper::is_valid_image_file($_FILES['upload']['tmp_name'])) { $error = 'illegal_file_extension'; $error_msg = JText::sprintf('ATTACH_ERROR_UPLOADING_FILE_S', $filename); $error_msg .= "<br />" . JText::_('ATTACH_ERROR_ILLEGAL_FILE_EXTENSION') . " (corrupted image file)"; // ??? Need new error message } } // Handle PDF mime types if ($extension == 'pdf') { require_once JPATH_COMPONENT_SITE . '/file_types.php'; if (in_array($ftype, AttachmentsFileTypes::$attachments_pdf_mime_types)) { $ftype = 'application/pdf'; } } // If there was an error, refresh the form with a warning if ($error) { if ($app->isAdmin()) { $result = new JObject(); $result->error = true; $result->error_msg = $error_msg; return $result; } // Set up the view to redisplay the form with warnings if ($save_type == 'update') { require_once JPATH_COMPONENT_SITE . '/views/update/view.html.php'; $view = new AttachmentsViewUpdate(); AttachmentsHelper::add_view_urls($view, 'update', $attachment->parent_id, $attachment->parent_type, $attachment_id, $from); $view->update = JRequest::getWord('update'); } else { require_once JPATH_COMPONENT_SITE . '/views/upload/view.html.php'; $view = new AttachmentsViewUpload(); AttachmentsHelper::add_view_urls($view, 'upload', $attachment->parent_id, $attachment->parent_type, null, $from); } // Suppress the display filename if we are changing from file to url $display_name = $attachment->display_name; if ($save_type == 'update') { $new_uri_type = JRequest::getWord('update'); if ($new_uri_type && ($new_uri_type == 'file' || $new_uri_type != $attachment->uri_type)) { $attachment->display_name = ''; } } // Set up the view $view->attachment = $attachment; $view->new_parent = $parent->new; $view->parent = $parent; $view->params = $params; $view->from = $from; $view->Itemid = JRequest::getInt('Itemid', 1); $view->error = $error; $view->error_msg = $error_msg; // Display the view $view->display(); exit; } // Define where the attachments go $upload_url = AttachmentsDefines::$ATTACHMENTS_SUBDIR; $upload_dir = JPATH_SITE . '/' . $upload_url; // Figure out the system filename $path = $parent->getAttachmentPath($attachment->parent_entity, $attachment->parent_id, null); $fullpath = $upload_dir . '/' . $path; // Make sure the directory exists if (!JFile::exists($fullpath)) { jimport('joomla.filesystem.folder'); if (!JFolder::create($fullpath)) { $errmsg = JText::sprintf('ATTACH_ERROR_UNABLE_TO_SETUP_UPLOAD_DIR_S', $upload_dir) . ' (ERR 34)'; JError::raiseError(500, $errmsg); } AttachmentsHelper::write_empty_index_html($fullpath); } // Get ready to save the file $filename_sys = $fullpath . $filename; $url = $upload_url . '/' . $path . $filename; // If we are on windows, fix the filename and URL if (DIRECTORY_SEPARATOR != '/') { $filename_sys = str_replace('/', DIRECTORY_SEPARATOR, $filename_sys); $url = str_replace(DIRECTORY_SEPARATOR, '/', $url); } // Check on length of filename_sys if (JString::strlen($filename_sys) > AttachmentsDefines::$MAXIMUM_FILENAME_SYS_LENGTH) { $errmsg = JText::sprintf('ATTACH_ERROR_FILEPATH_TOO_LONG_N_N_S', JString::strlen($filename_sys), AttachmentsDefines::$MAXIMUM_FILENAME_SYS_LENGTH, $filename) . '(ERR 35)'; JError::raiseError(500, $errmsg); } // Make sure the system filename doesn't already exist $error = false; $duplicate_filename = false; if ($save_type == 'upload' && JFile::exists($filename_sys)) { // Cannot overwrite an existing file when creating a new attachment! $duplicate_filename = true; } if ($save_type == 'update' && JFile::exists($filename_sys)) { // If updating, we may replace the existing file but may not overwrite any other existing file $query = $db->getQuery(true); $query->select('id')->from('#__attachments'); $query->where('filename_sys=' . $db->quote($filename_sys) . ' AND id != ' . (int) $attachment->id); $db->setQuery($query, 0, 1); if ($db->loadResult() > 0) { $duplicate_filename = true; } } // Handle duplicate filename error if ($duplicate_filename) { $error = 'file_already_on_server'; $error_msg = JText::sprintf('ATTACH_ERROR_FILE_S_ALREADY_ON_SERVER', $filename); if ($app->isAdmin()) { $result = new JObject(); $result->error = true; $result->error_msg = $error_msg; return $result; } $save_url = JRoute::_("index.php?option=com_attachments&task=save&tmpl=component"); // Set up the view to redisplay the form with warnings require_once JPATH_COMPONENT_SITE . '/views/upload/view.html.php'; $view = new AttachmentsViewUpload(); AttachmentsHelper::add_view_urls($view, 'upload', $attachment->parent_id, $attachment->parent_type, null, $from); // Set up the view $view->attachment = $attachment; $view->save_url = $save_url; $view->new_parent = $parent->new; $view->parent = $parent; $view->params = $params; $view->from = $from; $view->Itemid = JRequest::getInt('Itemid', 1); $view->error = $error; $view->error_msg = $error_msg; // Display the view $view->display(); exit; } // Create a display filename, if needed (for long filenames) if ($max_filename_length > 0 && JString::strlen($attachment->display_name) == 0 && JString::strlen($filename) > $max_filename_length) { $attachment->display_name = AttachmentsHelper::truncate_filename($filename, $max_filename_length); } // Copy the info about the uploaded file into the new record $attachment->uri_type = 'file'; $attachment->filename = $filename; $attachment->filename_sys = $filename_sys; $attachment->url = $url; $attachment->file_type = $ftype; $attachment->file_size = $_FILES['upload']['size']; // If the user is not authorised to change the state (eg, publish/unpublish), // ignore the form data and make sure the publish state is is set correctly. if (!$may_publish) { if ($save_type == 'upload') { // Use the default publish state (ignore form info) jimport('joomla.application.component.helper'); $params = JComponentHelper::getParams('com_attachments'); $attachment->state = $params->get('publish_default', false); } else { // Restore the old state (ignore form info) $db = JFactory::getDBO(); $query = $db->getQuery(true); $query->select('state')->from('#__attachments')->where('id = ' . (int) $attachment->id); $db->setQuery($query, 0, 1); $old_state = $db->loadResult(); if ($db->getErrorNum()) { $errmsg = $db->stderr() . ' (ERR 36)'; JError::raiseError(500, $errmsg); } $attachment->state = $old_state; } } // Set the create/modify dates $now = JFactory::getDate(); $now = $now->toSql(); // Update the create/modify info if ($save_type == 'upload') { $attachment->created = $now; } $attachment->modified = $now; // Add the icon file type require_once JPATH_COMPONENT_SITE . '/file_types.php'; $attachment->icon_filename = AttachmentsFileTypes::icon_filename($filename, $ftype); // Save the updated attachment if (!$attachment->store()) { $errmsg = JText::_('ATTACH_ERROR_SAVING_FILE_ATTACHMENT_RECORD') . $attachment->getError() . ' (ERR 37)'; JError::raiseError(500, $errmsg); } // Get the attachment id // If we're updating we may not get an insertid, so don't blindly overwrite the old // attachment_id just in case (Thanks to Franz-Xaver Geiger for a bug fix on this) $new_attachment_id = $db->insertid(); if (!empty($new_attachment_id)) { $attachment_id = (int) $new_attachment_id; } // Move the file $msg = ""; if (JFile::upload($_FILES['upload']['tmp_name'], $filename_sys)) { $file_size = (int) ($attachment->file_size / 1024.0); $file_size_str = JText::sprintf('ATTACH_S_KB', $file_size); if ($file_size_str == 'ATTACH_S_KB') { // Work around until all translations are updated ??? $file_size_str = $file_size . ' kB'; } chmod($filename_sys, 0644); // ??? The following items need to be updated for RTL if ($save_type == 'update') { $msg = JText::_('ATTACH_UPDATED_ATTACHMENT') . ' ' . $filename . ' (' . $file_size_str . ')!'; } else { $msg = JText::_('ATTACH_UPLOADED_ATTACHMENT') . ' ' . $filename . ' (' . $file_size_str . ')!'; } } else { $query = $db->getQuery(true); $query->delete('#__attachments')->where('id = ' . (int) $attachment_id); $db->setQuery($query); $result = $db->query(); if ($db->getErrorNum()) { $errmsg = $db->stderr() . ' (ERR 38)'; JError::raiseError(500, $errmsg); } $msg = JText::_('ATTACH_ERROR_MOVING_FILE') . " {$_FILES['upload']['tmp_name']} -> {$filename_sys})"; } // If we are updating, we may need to delete the old file if ($save_type == 'update') { if ($filename_sys != $old_filename_sys && JFile::exists($old_filename_sys)) { JFile::delete($old_filename_sys); AttachmentsHelper::clean_directory($old_filename_sys); } } return $msg; }