/** * {@inheritdoc} */ public function validate($value, Constraint $constraint) { // Get the file to execute validators. $asset = $value->get('entity')->getTarget()->getValue(); // Get the validators. $validators = $value->getUploadValidators(); // Checks that a file meets the criteria specified by the validators. if ($errors = embridge_asset_validate($asset, $validators)) { foreach ($errors as $error) { $this->context->addViolation($error); } } }
/** * Uploads the file to EMDB. * * @param string $form_field_name * A string that is the associative array key of the upload form element in * the form array. * @param string $catalog_id * The catalog id for the catalog we are uploading to. * @param array $metadata * Additional properties that can be passed into EMDB and stored as metadata * on the file. These values are not stored locally in Drupal. * @param array $validators * An optional, associative array of callback functions used to validate the * file. * @param string|bool $destination_dir * A string containing the URI that the file should be copied to. This must * be a stream wrapper URI. If this value is omitted, Drupal's temporary * files scheme will be used ("temporary://"). * @param int $delta * Delta of the file to save or NULL to save all files. Defaults to NULL. * @param int $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE: Replace the existing file. * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR: Do nothing and return FALSE. * * @return \Drupal\embridge\EmbridgeAssetEntityInterface[] * Function returns array of files or a single file object if $delta * != NULL. Each file object contains the file information if the * upload succeeded or FALSE in the event of an error. Function * returns NULL if no file was uploaded. * * The docs for the "File interface" group, which you can find under * Related topics, or the header at the top of this file, documents the * components of a file entity. In addition to the standard components, * this function adds: * - source: Path to the file before it is moved. * - destination: Path to the file after it is moved (same as 'uri'). */ public static function saveUpload($form_field_name, $catalog_id, $metadata = array(), $validators = array(), $destination_dir = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) { $user = \Drupal::currentUser(); static $upload_cache; $file_upload = \Drupal::request()->files->get("files[{$form_field_name}]", NULL, TRUE); // Make sure there's an upload to process. if (empty($file_upload)) { return NULL; } // Return cached objects without processing since the file will have // already been processed and the paths in $_FILES will be invalid. if (isset($upload_cache[$form_field_name])) { if (isset($delta)) { return $upload_cache[$form_field_name][$delta]; } return $upload_cache[$form_field_name]; } // Prepare uploaded files info. Representation is slightly different // for multiple uploads and we fix that here. /** @var \Symfony\Component\HttpFoundation\File\UploadedFile[] $uploaded_files */ $uploaded_files = $file_upload; if (!is_array($file_upload)) { $uploaded_files = array($file_upload); } $assets = array(); foreach ($uploaded_files as $i => $file_info) { // Check for file upload errors and return FALSE for this file if a lower // level system error occurred. For a complete list of errors: // See http://php.net/manual/features.file-upload.errors.php. switch ($file_info->getError()) { case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size()))), 'error'); $assets[$i] = FALSE; continue; case UPLOAD_ERR_PARTIAL: case UPLOAD_ERR_NO_FILE: drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $file_info->getFilename())), 'error'); $assets[$i] = FALSE; continue; case UPLOAD_ERR_OK: // Final check that this is a valid upload, if it isn't, use the // default error handler. if (is_uploaded_file($file_info->getRealPath())) { break; } default: // Unknown error. drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $file_info->getFilename())), 'error'); $assets[$i] = FALSE; continue; } // Begin building file entity. $values = array('uid' => $user->id(), 'filename' => $file_info->getClientOriginalName(), 'filesize' => $file_info->getSize(), 'catalog_id' => $catalog_id); $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']); // Create our Embridge Entity. /** @var \Drupal\embridge\EmbridgeAssetEntityInterface $asset */ $asset = EmbridgeAssetEntity::create($values); $extensions = ''; if (isset($validators['embridge_asset_validate_file_extensions'])) { if (isset($validators['embridge_asset_validate_file_extensions'][0])) { // Build the list of non-munged exts if the caller provided them. $extensions = $validators['embridge_asset_validate_file_extensions'][0]; } else { // If 'file_validate_extensions' is set and the list is empty then the // caller wants to allow any extension. In this case we have to remove // the validator or else it will reject all extensions. unset($validators['embridge_asset_validate_file_extensions']); } } else { // No validator was provided, so add one using the default list. // Build a default non-munged safe list for file_munge_filename(). $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; $validators['embridge_asset_validate_file_extensions'] = array(); $validators['embridge_asset_validate_file_extensions'][0] = $extensions; } if (!empty($extensions)) { // Munge the filename to protect against possible malicious extension // hiding within an unknown file type (ie: filename.html.foo). $asset->setFilename(file_munge_filename($asset->getFilename(), $extensions)); } // Rename potentially executable files, to help prevent exploits. if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\\.(php|pl|py|cgi|asp|js)(\\.|$)/i', $asset->getFilename()) && substr($asset->getFilename(), -4) != '.txt') { $asset->setMimeType('text/plain'); // The destination filename will also later be used to create the URI. $asset->setFilename($asset->getFilename() . '.txt'); // The .txt extension may not be in the allowed list of extensions. // We have to add it here or else the file upload will fail. if (!empty($extensions)) { $validators['embridge_asset_validate_file_extensions'][0] .= ' txt'; drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $asset->getFilename()))); } } // If the destination is not provided, use the temporary directory. if (empty($destination_dir)) { $destination_dir = 'temporary://'; } // Assert that the destination contains a valid stream. $destination_scheme = file_uri_scheme($destination_dir); if (!file_stream_wrapper_valid_scheme($destination_scheme)) { drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination_dir)), 'error'); $assets[$i] = FALSE; continue; } // A file URI may already have a trailing slash or look like "public://". if (substr($destination_dir, -1) != '/') { $destination_dir .= '/'; } $asset_destination = file_destination($destination_dir . $asset->getFilename(), $replace); // If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR // and there's an existing file so we need to bail. if ($asset_destination === FALSE) { drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination_dir)), 'error'); $assets[$i] = FALSE; continue; } // Add in our check of the file name length. // TODO: Do we need this? // $validators['file_validate_name_length'] = array(); // Call the validation functions specified by this function's caller. $errors = embridge_asset_validate($asset, $validators); // Check for errors. if (!empty($errors)) { $message = array('error' => array('#markup' => t('The specified file %name could not be uploaded.', array('%name' => $asset->getFilename()))), 'item_list' => array('#theme' => 'item_list', '#items' => $errors)); // @todo Add support for render arrays in drupal_set_message()? See // https://www.drupal.org/node/2505497. drupal_set_message(\Drupal::service('renderer')->renderPlain($message), 'error'); $assets[$i] = FALSE; continue; } // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary // directory. This overcomes open_basedir restrictions for future file // operations. $asset->setSourcePath($asset_destination); if (!drupal_move_uploaded_file($file_info->getRealPath(), $asset->getSourcePath())) { drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error'); \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $asset->getFilename(), '%destination' => $asset->getSourcePath())); $assets[$i] = FALSE; continue; } // Set the permissions on the new file. drupal_chmod($asset->getSourcePath()); // If we are replacing an existing file re-use its database record. // @todo Do not create a new entity in order to update it. See // https://www.drupal.org/node/2241865. if ($replace == FILE_EXISTS_REPLACE) { $existing_files = entity_load_multiple_by_properties('embridge_asset_entity', array('uri' => $asset->getSourcePath())); if (count($existing_files)) { $existing = reset($existing_files); $asset->setOriginalId($existing->id()); } } /** @var \Drupal\embridge\EnterMediaDbClientInterface $embridge_client */ $embridge_client = \Drupal::getContainer()->get('embridge.client'); try { $embridge_client->upload($asset, $metadata); } catch (\Exception $e) { $message = $e->getMessage(); drupal_set_message(t('Uploading the file "%file" to EnterMedia failed with the message "%message".', array('%file' => $asset->getFilename(), '%message' => $message)), 'error'); $assets[$i] = FALSE; continue; } // If we made it this far it's safe to record this file in the database. $asset->save(); $assets[$i] = $asset; } // Add files to the cache. $upload_cache[$form_field_name] = $assets; return isset($delta) ? $assets[$delta] : $assets; }