You are here

class ImceFM in IMCE 8

Same name and namespace in other branches
  1. 8.2 src/ImceFM.php \Drupal\imce\ImceFM

Imce File Manager.

Hierarchy

Expanded class hierarchy of ImceFM

6 files declare their use of ImceFM
Core.php in src/Plugin/ImcePlugin/Core.php
Delete.php in src/Plugin/ImcePlugin/Delete.php
ImceFolderTest.php in tests/src/Kernel/ImceFolderTest.php
Newfolder.php in src/Plugin/ImcePlugin/Newfolder.php
Resize.php in src/Plugin/ImcePlugin/Resize.php

... See full list

File

src/ImceFM.php, line 17

Namespace

Drupal\imce
View source
class ImceFM {
  use StringTranslationTrait;

  /**
   * File manager configuration.
   *
   * @var array
   */
  public $conf;

  /**
   * Active user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  public $user;

  /**
   * Active request.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  public $request;

  /**
   * Current validation status for the configuration.
   *
   * @var bool
   */
  public $validated;

  /**
   * Currently selected items.
   *
   * @var array
   */
  public $selection = [];

  /**
   * Folder tree.
   *
   * @var array
   */
  public $tree = [];

  /**
   * Active folder.
   *
   * @var \Drupal\imce\ImceFolder
   */
  public $activeFolder;

  /**
   * Response data.
   *
   * @var array
   */
  public $response = [];

  /**
   * Status messages.
   *
   * @var array
   */
  public $messages = [];

  /**
   * Image style for thumbnails.
   *
   * @var \Drupal\image\Entity\ImageStyle
   */
  private $thumbnailStyle;

  /**
   * Constructs the file manager.
   *
   * @param array $conf
   *   File manager configuration.
   * @param \Drupal\Core\Session\AccountProxyInterface $user
   *   The active user.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The active request that contains parameters for file manager operations.
   */
  public function __construct(array $conf, AccountProxyInterface $user = NULL, Request $request = NULL) {
    $this->conf = $conf;
    $this->user = $user ?: \Drupal::currentUser();
    $this->request = $request;
    $this
      ->init();
  }

  /**
   * Initializes the file manager.
   *
   * Initializes the file manager by validating
   *   the current configuration and request.
   */
  protected function init() {
    if (!isset($this->validated)) {

      // Create the root.
      $root = $this
        ->createItem('folder', '.');
      $root
        ->setPath('.');

      // Check initialization error.
      if ($error = $this
        ->getInitError()) {
        $this
          ->setMessage($error);
      }
      else {
        $this
          ->initSelection();
      }
      $this->validated = $error === FALSE;
    }
  }

  /**
   * Performs the initialization and returns the first error message.
   */
  protected function getInitError() {
    $conf =& $this->conf;

    // Check configuration options.
    $keys = [
      'folders',
      'root_uri',
    ];
    foreach ($keys as $key) {
      if (empty($conf[$key])) {
        return $this
          ->t('Missing configuration %key.', [
          '%key' => $key,
        ]);
      }
    }

    // Check root.
    $root_uri = $conf['root_uri'];
    if (!is_dir($root_uri)) {
      if (!mkdir($root_uri, $this
        ->getConf('chmod_directory', 0775), TRUE)) {
        return $this
          ->t('Missing root folder.');
      }
    }

    // Check and add predefined folders.
    foreach ($conf['folders'] as $path => $folder_conf) {
      $path = (string) $path;
      $uri = $this
        ->createUri($path);
      if (is_dir($uri) || mkdir($uri, $this
        ->getConf('chmod_directory', 0775), TRUE)) {
        $this
          ->addFolder($path, $folder_conf);
      }
      else {
        unset($conf['folders'][$path]);
      }
    }
    if (!$conf['folders']) {
      return $this
        ->t('No valid folder definitions found.');
    }

    // Check and set active folder if provided.
    $path = $this
      ->getPost('active_path');
    if (isset($path) && $path !== '') {
      if ($folder = $this
        ->checkFolder($path)) {
        $this->activeFolder = $folder;

        // Remember active path.
        if ($this->user
          ->isAuthenticated()) {
          if (!isset($conf['folders'][$path]) || count($conf['folders']) > 1 || $folder
            ->getPermission('browse_subfolders')) {
            $this->request
              ->getSession()
              ->set('imce_active_path', $path);
          }
        }
      }
      else {
        return $this
          ->t('Invalid active folder path: %path.', [
          '%path' => $path,
        ]);
      }
    }
    return FALSE;
  }

  /**
   * Initiates the selection with a list of user provided item paths.
   */
  protected function initSelection() {
    $paths = $this
      ->getPost('selection');
    if ($paths && is_array($paths)) {
      foreach ($paths as $path) {
        if ($item = $this
          ->checkItem($path)) {
          $item
            ->select();
        }
        else {
          $this
            ->removePathFromJs($path);
        }
      }
    }
  }

  /**
   * Runs an operation on the file manager.
   */
  public function run($op = NULL) {
    if (!$this->validated) {
      return FALSE;
    }

    // Check operation.
    if (!isset($op)) {
      $op = $this
        ->getOp();
    }
    if (!$op || !is_string($op)) {
      return FALSE;
    }

    // Validate security token.
    $token = $this
      ->getPost('token');
    if (!$token || $token !== $this
      ->getConf('token')) {
      $this
        ->setMessage($this
        ->t('Invalid security token.'));
      return FALSE;
    }

    // Let plugins handle the operation.
    $return = \Drupal::service('plugin.manager.imce.plugin')
      ->handleOperation($op, $this);
    if ($return === FALSE) {
      $this
        ->setMessage($this
        ->t('Invalid operation %op.', [
        '%op' => $op,
      ]));
    }
    return $return;
  }

  /**
   * Adds a folder to the tree.
   */
  public function addFolder($path, array $conf = NULL) {

    // Existing.
    if ($folder = $this
      ->getFolder($path)) {
      if (isset($conf)) {
        $folder
          ->setConf($conf);
      }
      return $folder;
    }

    // New. Append to the parent.
    if ($parts = Imce::splitPath($path)) {
      list($parent_path, $name) = $parts;
      if ($parent = $this
        ->addFolder($parent_path)) {
        return $parent
          ->addSubfolder($name, $conf);
      }
    }
  }

  /**
   * Get folder.
   *
   * @param string $path
   *   The patchs folder.
   *
   * @return mixed
   *   Returns a folder from the tree.
   */
  public function getFolder($path) {
    return isset($this->tree[$path]) ? $this->tree[$path] : NULL;
  }

  /**
   * Checks if the user provided folder path is accessible.
   *
   * @param string $path
   *   The patchs folder.
   *
   * @return object
   *   Returns the folder object with the path.
   */
  public function checkFolder($path) {
    if (is_array(Imce::folderInConf($path, $this->conf))) {
      return $this
        ->addFolder($path);
    }
  }

  /**
   * Checks if the user provided file path is accessible.
   *
   * Returns the file object with the path.
   */
  public function checkFile($path) {
    $item = $this
      ->checkItem($path);
    if ($item && $item->type === 'file') {
      return $item;
    }
  }

  /**
   * Checks the existence of a user provided item path.
   *
   * Scans the parent folder and returns the item object if it is accessible.
   */
  public function checkItem($path) {
    if ($parts = Imce::splitPath($path)) {
      if ($folder = $this
        ->checkFolder($parts[0])) {
        return $folder
          ->checkItem($parts[1]);
      }
    }
  }

  /**
   * Creates and returns an imce file/folder object by name.
   */
  public function createItem($type, $name, $conf = NULL) {
    $item = $type === 'folder' ? new ImceFolder($name, $conf) : new ImceFile($name);
    $item
      ->setFm($this);
    return $item;
  }

  /**
   * Creates an uri from a relative path.
   */
  public function createUri($path) {
    return Imce::joinPaths($this
      ->getConf('root_uri'), $path);
  }

  /**
   * Returns the current operation in the request.
   */
  public function getOp() {
    return $this
      ->getPost('jsop');
  }

  /**
   * Returns value of a posted parameter.
   */
  public function getPost($key, $default = NULL) {
    return $this->request ? $this->request->request
      ->get($key, $default) : $default;
  }

  /**
   * Returns a configuration option.
   */
  public function getConf($key, $default = NULL) {
    return isset($this->conf[$key]) ? $this->conf[$key] : $default;
  }

  /**
   * Sets a configuration option.
   */
  public function setConf($key, $value) {
    $this->conf[$key] = $value;
  }

  /**
   * Checks if a permission exists in any of the predefined folders.
   */
  public function hasPermission($permission) {
    return Imce::permissionInConf($permission, $this->conf);
  }

  /**
   * Returns the contents of a directory.
   */
  public function scanDir($diruri, array $options = []) {
    $options += [
      'name_filter' => $this
        ->getNameFilter(),
    ];
    $scanner = $this
      ->getConf('scanner', 'Drupal\\imce\\Imce::scanDir');
    return call_user_func($scanner, $diruri, $options);
  }

  /**
   * Returns name filtering regexp.
   */
  public function getNameFilter() {
    return Imce::nameFilterInConf($this->conf);
  }

  /**
   * Returns currently selected items.
   */
  public function getSelection() {
    return $this->selection;
  }

  /**
   * Returns selected items grouped by parent folder path.
   */
  public function groupSelection() {
    return $this
      ->groupItems($this->selection);
  }

  /**
   * Groups the items by parent path and type.
   */
  public function groupItems(array $items) {
    $group = [];
    foreach ($items as $item) {
      $path = $item->parent
        ->getPath();
      $type = $item->type == 'folder' ? 'subfolders' : 'files';
      $group[$path][$type][$item->name] = $item;
    }
    return $group;
  }

  /**
   * Selects an item.
   */
  public function selectItem(ImceItem $item) {
    if (!$item->selected && $item->parent) {
      $item->selected = TRUE;
      $this->selection[] = $item;
    }
  }

  /**
   * Deselects an item.
   */
  public function deselectItem(ImceItem $item) {
    if ($item->selected) {
      $item->selected = FALSE;
      $index = array_search($item, $this->selection);
      if ($index !== FALSE) {
        array_splice($this->selection, $index, 1);
      }
    }
  }

  /**
   * Add an item to the response.
   */
  public function addItemToJs(ImceItem $item) {
    if ($parent = $item->parent) {
      if ($path = $parent
        ->getPath()) {
        $name = $item->name;
        $uri = $item
          ->getUri();
        if ($item->type === 'folder') {
          $this->response['added'][$path]['subfolders'][$name] = $this
            ->getFolderProperties($uri);
        }
        else {
          $props = $this
            ->getFileProperties($uri);
          if (isset($item->uuid)) {
            $props['uuid'] = $item->uuid;
          }
          $this->response['added'][$path]['files'][$name] = $props;
        }
      }
    }
  }

  /**
   * Sets an item as removed in the response.
   */
  public function removeItemFromJs(ImceItem $item) {
    $this
      ->removePathFromJs($item
      ->getPath());
  }

  /**
   * Sets a path as removed in the response.
   */
  public function removePathFromJs($path) {
    if (isset($path)) {
      $this->response['removed'][] = $path;
    }
  }

  /**
   * Returns js properties of a file.
   */
  public function getFileProperties($uri) {
    $properties = [
      'date' => filemtime($uri),
      'size' => filesize($uri),
    ];
    if (preg_match('/\\.(jpe?g|png|gif)$/i', $uri) && ($info = getimagesize($uri))) {
      $properties['width'] = $info[0];
      $properties['height'] = $info[1];
      $style = $this
        ->getThumbnailStyle();
      if ($style && strpos($uri, '/styles/') === FALSE) {
        $properties['thumbnail'] = $style
          ->buildUrl($uri);
      }
    }
    return $properties;
  }

  /**
   * Returns thumbnail style.
   */
  public function getThumbnailStyle() {
    if (!isset($this->thumbnailStyle)) {
      $this->thumbnailStyle = FALSE;
      if ($style_name = $this
        ->getConf('thumbnail_style')) {
        $this->thumbnailStyle = \Drupal::entityTypeManager()
          ->getStorage('image_style')
          ->load($style_name);
      }
    }
    return $this->thumbnailStyle;
  }

  /**
   * Returns js properties of a folder.
   */
  public function getFolderProperties($uri) {
    return [
      'date' => filemtime($uri),
    ];
  }

  /**
   * Returns the response data.
   */
  public function getResponse() {
    $defaults = [
      'jsop' => $this
        ->getOp(),
    ];
    if ($messages = $this
      ->getMessages()) {
      $defaults['messages'] = $messages;
    }
    if ($folder = $this->activeFolder) {
      $defaults['active_path'] = $folder
        ->getPath();
    }
    return $this->response + $defaults;
  }

  /**
   * Adds response data.
   */
  public function addResponse($key, $value) {
    return $this->response[$key] = $value;
  }

  /**
   * Returns the status messages.
   */
  public function getMessages() {

    // Get drupal messages.
    $messenger = \Drupal::messenger();
    $messages = $messenger
      ->all();
    $messenger
      ->deleteAll();
    foreach ($messages as &$group) {
      foreach ($group as &$message) {
        $message = $message instanceof MarkupInterface ? $message . '' : Html::escape($message);
      }
    }

    // Merge with file manager messages.
    return array_merge_recursive($messages, $this->messages);
  }

  /**
   * Sets a status message.
   */
  public function setMessage($message, $type = 'error') {
    $this->messages[$type][] = $message;
  }

  /**
   * Checks parent folder permissions of the given items.
   */
  public function validatePermissions(array $items, $file_perm = NULL, $subfolder_perm = NULL) {
    foreach ($this
      ->groupItems($items) as $path => $content) {
      $parent = $this
        ->getFolder($path);

      // Parent contains files but does not have the file permission.
      if (!empty($content['files'])) {
        if (!isset($file_perm) || !$parent
          ->getPermission($file_perm)) {
          return FALSE;
        }
      }

      // Parent contains subfolders but does not have the subfolder permission.
      if (!empty($content['subfolders'])) {
        if (!isset($subfolder_perm) || !$parent
          ->getPermission($subfolder_perm)) {
          return FALSE;
        }
      }
    }
    return TRUE;
  }

  /**
   * Checks the existence of a predefined path.
   */
  public function validatePredefinedPath(array $items, $silent = FALSE) {
    foreach ($items as $item) {
      if ($item->type === 'folder' && ($folder = $item
        ->hasPredefinedPath())) {
        if (!$silent) {
          $this
            ->setMessage($this
            ->t('%path is a predefined path and can not be modified.', [
            '%path' => $folder
              ->getPath(),
          ]));
        }
        return FALSE;
      }
    }
    return TRUE;
  }

  /**
   * Validates a file name.
   */
  public function validateFileName($filename, $silent = FALSE) {

    // Basic validation.
    if ($filename === '.' || $filename === '..' || !($len = strlen($filename)) || $len > 240) {
      return FALSE;
    }

    // Test name filters.
    if ($name_filter = $this
      ->getNameFilter()) {
      if (preg_match($name_filter, $filename)) {
        if (!$silent) {
          $this
            ->setMessage($this
            ->t('%filename is not allowed.', [
            '%filename' => $filename,
          ]));
        }
        return FALSE;
      }
    }

    // Test chars forbidden in various operating systems.
    if (preg_match('@^\\s|\\s$|[/\\\\:\\*\\?"<>\\|\\x00-\\x1F]@', $filename)) {
      if (!$silent) {
        $this
          ->setMessage($this
          ->t('%filename contains invalid characters. Use only alphanumeric characters for better portability.', [
          '%filename' => $filename,
        ]));
      }
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Validates min/max image dimensions.
   */
  public function validateDimensions(array $items, $width, $height, $silent = FALSE) {

    // Check min dimensions.
    if ($width < 1 || $height < 1) {
      return FALSE;
    }

    // Check max dimensions.
    $maxwidth = $this
      ->getConf('maxwidth');
    $maxheight = $this
      ->getConf('maxheight');
    if ($maxwidth && $width > $maxwidth || $maxheight && $height > $maxheight) {
      if (!$silent) {
        $this
          ->setMessage($this
          ->t('Image dimensions must be smaller than %dimensions pixels.', [
          '%dimensions' => $maxwidth . 'x' . $maxwidth,
        ]));
      }
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Checks if all the selected items are images.
   */
  public function validateImageTypes(array $items, $silent = FALSE) {
    $regex = '/\\.(' . preg_replace('/ +/', '|', preg_quote(trim($this
      ->getConf('image_extensions', 'jpg jpeg png gif')))) . ')$/i';
    foreach ($items as $item) {
      if ($item->type === 'folder' || !preg_match($regex, $item->name)) {
        if (!$silent) {
          $this
            ->setMessage($this
            ->t('%name is not an image.', [
            '%name' => $item->name,
          ]));
        }
        return FALSE;
      }
    }
    return TRUE;
  }

  /**
   * Builds file manager page.
   */
  public function buildPage() {
    $page = [];
    $page['#attached']['library'][] = 'imce/drupal.imce';

    // Add meta for robots.
    $robots = [
      '#tag' => 'meta',
      '#attributes' => [
        'name' => 'robots',
        'content' => 'noindex,nofollow',
      ],
    ];
    $page['#attached']['html_head'][] = [
      $robots,
      'robots',
    ];

    // Disable cache.
    $page['#cache']['max-age'] = 0;

    // Run builders of available plugins.
    \Drupal::service('plugin.manager.imce.plugin')
      ->buildPage($page, $this);

    // Add active path to the conf.
    $conf = $this->conf;
    if (!isset($conf['active_path'])) {
      if ($folder = $this->activeFolder) {
        $conf['active_path'] = $folder
          ->getPath();
      }
      elseif ($this->request) {

        // Check $_GET['init_path'].
        if (($path = $this->request->query
          ->get('init_path')) && $this
          ->checkFolder($path)) {
          $conf['active_path'] = $path;
        }
        elseif ($this->user
          ->isAuthenticated() && ($path = $this->request
          ->getSession()
          ->get('imce_active_path'))) {
          if ($this
            ->checkFolder($path)) {
            $conf['active_path'] = $path;
          }
        }
      }
    }

    // Set initial messages.
    if ($messages = $this
      ->getMessages()) {
      $conf['messages'] = $messages;
    }
    $page['#attached']['drupalSettings']['imce'] = $conf;
    return $page;
  }

  /**
   * Builds and renders the file manager page.
   */
  public function buildRenderPage() {
    $page = $this
      ->buildPage();
    return \Drupal::service('bare_html_page_renderer')
      ->renderBarePage($page, $this
      ->t('File manager'), 'imce_page', [
      '#show_messages' => FALSE,
    ])
      ->getContent();
  }

  /**
   * Returns a page response based on the current request.
   */
  public function pageResponse() {
    if ($request = $this->request) {

      // Json request.
      if ($request->request
        ->has('jsop')) {
        $this
          ->run();
        $data = $this
          ->getResponse();

        // Return html response if the flag is set.
        if ($request->request
          ->get('return_html')) {
          return new Response('<html><body><textarea>' . Json::encode($data) . '</textarea></body></html>');
        }
        return new JsonResponse($data);
      }

      // Build and render the main page.
      return new Response($this
        ->buildRenderPage());
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ImceFM::$activeFolder public property Active folder.
ImceFM::$conf public property File manager configuration.
ImceFM::$messages public property Status messages.
ImceFM::$request public property Active request.
ImceFM::$response public property Response data.
ImceFM::$selection public property Currently selected items.
ImceFM::$thumbnailStyle private property Image style for thumbnails.
ImceFM::$tree public property Folder tree.
ImceFM::$user public property Active user.
ImceFM::$validated public property Current validation status for the configuration.
ImceFM::addFolder public function Adds a folder to the tree.
ImceFM::addItemToJs public function Add an item to the response.
ImceFM::addResponse public function Adds response data.
ImceFM::buildPage public function Builds file manager page.
ImceFM::buildRenderPage public function Builds and renders the file manager page.
ImceFM::checkFile public function Checks if the user provided file path is accessible.
ImceFM::checkFolder public function Checks if the user provided folder path is accessible.
ImceFM::checkItem public function Checks the existence of a user provided item path.
ImceFM::createItem public function Creates and returns an imce file/folder object by name.
ImceFM::createUri public function Creates an uri from a relative path.
ImceFM::deselectItem public function Deselects an item.
ImceFM::getConf public function Returns a configuration option.
ImceFM::getFileProperties public function Returns js properties of a file.
ImceFM::getFolder public function Get folder.
ImceFM::getFolderProperties public function Returns js properties of a folder.
ImceFM::getInitError protected function Performs the initialization and returns the first error message.
ImceFM::getMessages public function Returns the status messages.
ImceFM::getNameFilter public function Returns name filtering regexp.
ImceFM::getOp public function Returns the current operation in the request.
ImceFM::getPost public function Returns value of a posted parameter.
ImceFM::getResponse public function Returns the response data.
ImceFM::getSelection public function Returns currently selected items.
ImceFM::getThumbnailStyle public function Returns thumbnail style.
ImceFM::groupItems public function Groups the items by parent path and type.
ImceFM::groupSelection public function Returns selected items grouped by parent folder path.
ImceFM::hasPermission public function Checks if a permission exists in any of the predefined folders.
ImceFM::init protected function Initializes the file manager.
ImceFM::initSelection protected function Initiates the selection with a list of user provided item paths.
ImceFM::pageResponse public function Returns a page response based on the current request.
ImceFM::removeItemFromJs public function Sets an item as removed in the response.
ImceFM::removePathFromJs public function Sets a path as removed in the response.
ImceFM::run public function Runs an operation on the file manager.
ImceFM::scanDir public function Returns the contents of a directory.
ImceFM::selectItem public function Selects an item.
ImceFM::setConf public function Sets a configuration option.
ImceFM::setMessage public function Sets a status message.
ImceFM::validateDimensions public function Validates min/max image dimensions.
ImceFM::validateFileName public function Validates a file name.
ImceFM::validateImageTypes public function Checks if all the selected items are images.
ImceFM::validatePermissions public function Checks parent folder permissions of the given items.
ImceFM::validatePredefinedPath public function Checks the existence of a predefined path.
ImceFM::__construct public function Constructs the file manager.
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.