You are here

class AjaxController in S3 File System CORS Upload 8

Default controller for the s3fs_cors module.

Hierarchy

Expanded class hierarchy of AjaxController

File

src/Controller/AjaxController.php, line 19

Namespace

Drupal\s3fs_cors\Controller
View source
class AjaxController extends ControllerBase {

  /**
   * S3 Client Interface.
   *
   * @var \Aws\S3\S3ClientInterface
   */
  protected $s3Client;

  /**
   * Database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * Mime Type Guesser Interface.
   *
   * @var \Drupal\Core\File\MimeType\MimeTypeGuesser
   */
  protected $mimeType;

  /**
   * Logger Channel Interface.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * AjaxController constructor.
   *
   * @param \Drupal\s3fs\S3fsServiceInterface $s3fs
   *   The S3fs service interface.
   * @param \Drupal\Core\Database\Connection $database
   *   The Drupal database connection service.
   * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mimeType
   *   The mime type guesser service.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   The core logger factory service.
   *
   * @throws \Drupal\s3fs\S3fsException
   *   The S3fs exception.
   */
  public function __construct(S3fsServiceInterface $s3fs, Connection $database, MimeTypeGuesserInterface $mimeType, LoggerChannelFactoryInterface $loggerFactory) {
    $s3_config = $this
      ->config('s3fs.settings')
      ->get();
    $this->s3Client = $s3fs
      ->getAmazonS3Client($s3_config);
    $this->database = $database;
    $this->mimeType = $mimeType;
    $this->logger = $loggerFactory
      ->get('s3fs');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('s3fs'), $container
      ->get('database'), $container
      ->get('file.mime_type.guesser'), $container
      ->get('logger.factory'));
  }

  /**
   * Return the file key (i.e. the path and name).
   *
   * The values $file_size and $file_index are just values to be passed through
   * and returned to the javaScript function.
   */
  public function getKey($directory, $file_name, $file_size, $file_index, $replace = FileSystemInterface::EXISTS_RENAME) {

    // Strip control characters (ASCII value < 32). Though these are allowed in
    // some filesystems, not many applications handle them well.
    $file_name = preg_replace('/[\\x00-\\x1F]/u', '_', $file_name);

    // Also replace forbidden chars if this is a Windows envrionment.
    if (substr(PHP_OS, 0, 3) == 'WIN') {

      // These characters are not allowed in Windows filenames.
      $file_name = str_replace([
        ':',
        '*',
        '?',
        '"',
        '<',
        '>',
        '|',
      ], '_', $file_name);
    }

    // Decode the "/" chars in the directory and build an initial file key.
    // Note: this will include the s3fs root folder, if specified.
    $directory = str_replace('::', '/', $directory);
    $file_key = $directory . '/' . $file_name;

    // Check if a file with this key already exists on S3.
    $file_exists = $this
      ->s3FileExists($file_key);
    if ($file_exists) {
      switch ($replace) {
        case FileSystemInterface::EXISTS_REPLACE:

          // Do nothing here, we want to overwrite the existing file.
          break;
        case FileSystemInterface::EXISTS_RENAME:
          $file_key = $this
            ->createFileKey($directory, $file_name);
          break;
        case FileSystemInterface::EXISTS_ERROR:

          // Error reporting handled by calling function.
          return FALSE;
      }
    }

    // Core file_destination is not able to check remoe file existience.
    return new JsonResponse([
      'file_key' => $file_key,
      'file_name' => $file_name,
      'file_size' => $file_size,
      'file_index' => $file_index,
    ]);
  }

  /**
   * Create a new file key if the original one already exists.
   */
  private function createFileKey($directory, $file_name) {

    // Remove the root folder from the file directory if specified.
    $root_folder = '';
    $config = $this
      ->config('s3fs.settings');
    if (!empty($config
      ->get('root_folder'))) {
      $root_folder = $config
        ->get('root_folder') . '/';
      $directory = str_replace($root_folder, '', $directory);
    }
    $separator = '/';

    // A URI or path may already have a trailing slash or look like "public://".
    if (substr($directory, -1) == '/') {
      $separator = '';
    }

    // Extract the file base name and the file extension (with leading period).
    $base_name = substr($file_name, 0, strrpos($file_name, '.'));
    $extension = substr($file_name, strrpos($file_name, '.'));
    $key_base = $root_folder . $directory . $separator . $base_name;

    // Look in the s3fs cache to find files with a key like this.
    $uri_base = 's3://' . $directory . $separator . $base_name;
    $records = $this->database
      ->select('s3fs_file', 's')
      ->fields('s', [
      'uri',
    ])
      ->condition('uri', $this->database
      ->escapeLike($uri_base) . '%', 'LIKE')
      ->execute()
      ->fetchCol();

    // Process the results array to extract the suffix values.
    $results = [];
    foreach ($records as $record) {
      $suffix = str_replace([
        $uri_base,
        $extension,
      ], '', $record);
      if ($suffix) {

        // Drop the leading underscore char.
        $suffix = (int) substr($suffix, 1);
        $results[$suffix] = $record;
      }
    }

    // Find a key suffix that can be used by looking for a gap in suffix values.
    for ($suffix = 0; $suffix < count($results); $suffix++) {
      if (!isset($results[$suffix])) {
        break;
      }
    }

    // If we drop out the bottom then suffix will be one greater then largest
    // existing value.  Create a trial key and test.
    $trial_key = $key_base . '_' . $suffix . $extension;
    if ($this
      ->s3FileExists($trial_key)) {

      // Destination file already exists, then cache is stale. Rebuild required.
      $this->logger
        ->info('S3fs cache table rebuild required (key %key missing)', [
        '%key' => $trial_key,
      ]);

      // Look for a new suffix value greater then the largest already known.
      $suffix = max(array_keys($results));
      do {
        $trial_key = $key_base . '_' . ++$suffix . $extension;
      } while ($this
        ->s3FileExists($trial_key));
    }
    return $trial_key;
  }

  /**
   * Check whehter a passed file name exists (using the file key).
   */
  private function s3FileExists($key) {
    $config = $this
      ->config('s3fs.settings');
    $bucket = $config
      ->get('bucket');
    return $this->s3Client
      ->doesObjectExist($bucket, $key);
  }

  /**
   * Save the file details to the managed file table.
   */
  public function saveFile($file_path, $file_name, $file_size, $field_name) {
    $user = $this
      ->currentUser();

    // Decode the "/" chars from file path.
    $file_path = str_replace('::', '/', $file_path);

    // Remove the root folder from the file path if specified.
    $config = $this
      ->config('s3fs.settings');
    if (!empty($config
      ->get('root_folder'))) {
      $root_folder = $config
        ->get('root_folder');
      $file_path = str_replace($root_folder . '/', '', $file_path);
    }
    $file_uri = 's3://' . $file_path;

    // Record the uploaded file in the s3fs cache. This needs to be done before
    // the file is saved so the the filesize can be found from the cache.
    $wrapper = new S3fsStream();
    $wrapper
      ->writeUriToCache($file_uri);

    // Convert URLs back to their proper (original) file stream names.
    $public_folder = 's3://' . ($config
      ->get('public_folder') ?: 's3fs-public');
    $private_folder = 's3://' . ($config
      ->get('private_folder') ?: 's3fs-private');
    if (strpos($file_uri, $public_folder) === 0 || strpos($file_uri, $private_folder) === 0) {
      $file_uri = str_replace([
        $public_folder,
        $private_folder,
      ], [
        'public:/',
        'private:/',
      ], $file_uri);
    }
    $file_mime = $this->mimeType
      ->guess($file_name);
    $values = [
      'uid' => $user
        ->id(),
      'status' => 0,
      'filename' => $file_name,
      'uri' => $file_uri,
      'filesize' => $file_size,
      'filemime' => $file_mime,
      'source' => $field_name,
    ];
    $file = File::create($values);
    $errors = [];
    $errors = array_merge($errors, $this
      ->moduleHandler()
      ->invokeAll('file_validate', [
      $file,
    ]));
    if (empty($errors)) {
      $file
        ->save();
      $values['fid'] = $file
        ->id();
      $values['uuid'] = $file
        ->uuid();
    }
    else {
      $file
        ->delete();
      $values['errmsg'] = implode("\n", $errors);
    }
    return new JsonResponse($values);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AjaxController::$database protected property Database connection.
AjaxController::$logger protected property Logger Channel Interface.
AjaxController::$mimeType protected property Mime Type Guesser Interface.
AjaxController::$s3Client protected property S3 Client Interface.
AjaxController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
AjaxController::createFileKey private function Create a new file key if the original one already exists.
AjaxController::getKey public function Return the file key (i.e. the path and name).
AjaxController::s3FileExists private function Check whehter a passed file name exists (using the file key).
AjaxController::saveFile public function Save the file details to the managed file table.
AjaxController::__construct public function AjaxController constructor.
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityManager protected property The entity manager.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityManager Deprecated protected function Retrieves the entity manager service.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
ControllerBase::state protected function Returns the state storage service.
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.