dfm-8.x-1.16/src/Dfm.php

src/Dfm.php
<?php

namespace Drupal\dfm;

use Drupal\Core\DrupalKernel;
use Drupal\Core\File\FileSystem;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\file\FileInterface;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Dfm container class for helper methods.
 */
class Dfm implements TrustedCallbackInterface {

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['preRenderTextarea'];
  }

  /**
   * Checks if a user has a dfm profile assigned for a file scheme.
   */
  public static function access(AccountProxyInterface $user = NULL, $scheme = NULL) {
    return (bool) static::userProfile($user, $scheme);
  }

  /**
   * Returns a response for a dfm request.
   */
  public static function response(Request $request, AccountProxyInterface $user = NULL, $scheme = NULL) {
    // Ajax request.
    if ($request->request->has('ajaxOp')) {
      $fm = static::userFm($user, $scheme, $request);
      if ($fm) {
        return $fm->run();
      }
    }
    // Return dfm page.
    $page = ['#theme' => 'dfm_page'];
    return new Response(\Drupal::service('renderer')->render($page));
  }

  /**
   * Returns a file manager instance for a user.
   */
  public static function userFm(AccountProxyInterface $user = NULL, $scheme = NULL, Request $request = NULL) {
    $conf = static::userConf($user, $scheme);
    if ($conf) {
      if (!isset($conf['ajaxOp']) && $request) {
        $op = $request->request->get('ajaxOp');
        if ($op) {
          $conf['ajaxOp'] = $op;
        }
      }
      require_once $conf['scriptDirPath'] . '/core/Dfm.php';
      return new \Dfm($conf);
    }
  }

  /**
   * Returns dfm configuration profile for a user.
   */
  public static function userProfile(AccountProxyInterface $user = NULL, $scheme = NULL) {
    $profiles = &drupal_static(__METHOD__, []);
    $user = $user ?: \Drupal::currentUser();
    $scheme = $scheme ?? \Drupal::config('system.file')->get('default_scheme');
    $profile = &$profiles[$user->id()][$scheme];
    if (!isset($profile)) {
      // Check stream wrapper.
      if (\Drupal::service('stream_wrapper_manager')->getViaScheme($scheme)) {
        // Give user #1 admin profile.
        $storage = \Drupal::entityTypeManager()->getStorage('dfm_profile');
        if ($user->id() == 1) {
          $profile = $storage->load('admin');
          if ($profile) {
            return $profile;
          }
        }
        $roles_profiles = \Drupal::config('dfm.settings')->get('roles_profiles', []);
        $user_roles = array_flip($user->getRoles());
        // Order roles from more permissive to less permissive.
        $roles = array_reverse(Role::loadMultiple());
        foreach ($roles as $rid => $role) {
          if (isset($user_roles[$rid]) && !empty($roles_profiles[$rid][$scheme])) {
            $profile = $storage->load($roles_profiles[$rid][$scheme]);
            if ($profile) {
              return $profile;
            }
          }
        }
      }
      $profile = FALSE;
    }
    return $profile;
  }

  /**
   * Returns processed profile configuration for a user.
   */
  public static function userConf(AccountProxyInterface $user = NULL, $scheme = NULL) {
    $user = $user ?: \Drupal::currentUser();
    $scheme = $scheme ?? \Drupal::config('system.file')->get('default_scheme');
    $profile = static::userProfile($user, $scheme);
    if ($profile) {
      $conf = $profile->getConf();
      $conf['pid'] = $profile->id();
      $conf['scheme'] = $scheme;
      return static::processUserConf($conf, $user);
    }
  }

  /**
   * Processes raw profile configuration of a user.
   */
  public static function processUserConf(array $conf, AccountProxyInterface $user) {
    // Convert MB to bytes.
    $conf['uploadMaxSize'] = (int) ((float) $conf['uploadMaxSize'] * 1048576);
    $conf['uploadQuota'] = (int) ((float) $conf['uploadQuota'] * 1048576);
    // Set extensions.
    $conf['uploadExtensions'] = array_values(array_filter(explode(' ', $conf['uploadExtensions'])));
    if (isset($conf['imgExtensions'])) {
      $conf['imgExtensions'] = $conf['imgExtensions']
        ? array_values(array_filter(explode(' ', $conf['imgExtensions'])))
        : FALSE;
    }
    // Set variables.
    $val = \Drupal::config('system.file')->get('allow_insecure_uploads');
    if ($val) {
      $conf['uploadInsecure'] = $val;
    }
    $val = \Drupal::config('dfm.settings')->get('abs_urls');
    if ($val) {
      $conf['absUrls'] = $val;
    }
    // Set paths/urls.
    $url_gen = \Drupal::service('file_url_generator');
    $conf['rootDirPath'] = $conf['scheme'] . '://';
    $url = $url_gen->generateAbsoluteString($conf['rootDirPath'] . 'dfm111');
    $conf['rootDirUrl'] = preg_replace('@/dfm111.*$@i', '', $url);
    if (empty($conf['absUrls'])) {
      $conf['rootDirUrl'] = $url_gen->transformRelative($conf['rootDirUrl']);
    }
    $conf['scriptDirPath'] = static::scriptPath();
    $conf['baseUrl'] = base_path();
    $conf['securityKey'] = $user->isAnonymous() ? 'anonymous' : \Drupal::csrfToken()->get('dfm');
    $conf['jsCssSuffix'] = \Drupal::state()->get('system.css_js_query_string', '0');
    $conf['drupalUid'] = $user->id();
    $conf['fileMode'] = Settings::get('file_chmod_file', FileSystem::CHMOD_FILE);
    $conf['directoryMode'] = Settings::get('file_chmod_directory', FileSystem::CHMOD_DIRECTORY);
    $conf['imgJpegQuality'] = \Drupal::config('system.image.gd')->get('jpeg_quality', 85);
    $conf['lang'] = \Drupal::service('language_manager')->getCurrentLanguage()->getId();
    // Set thumbnail URL.
    if (!empty($conf['thumbStyle']) && function_exists('image_style_options')) {
      $conf['thumbUrl'] = Url::fromRoute('image.style_public', [
        'image_style' => $conf['thumbStyle'],
        'scheme' => $conf['scheme'],
      ])->toString();
      if (!\Drupal::config('image.settings')->get('allow_insecure_derivatives')) {
        $conf['thumbUrlQuery'] = 'dfm_itok=' . static::itok($conf['thumbStyle']);
      }
    }
    // Add drupal plugin to call dfm_drupal_plugin_register().
    $conf['plugins'][] = 'drupal';
    // Set custom realpath function.
    $conf['realpathFunc'] = 'Drupal\dfm\Dfm::realpath';
    // Set custom url function.
    if (!empty($conf['urlAlter'])) {
      $conf['urlFunc'] = 'Drupal\dfm\Dfm::fileUrl';
    }
    // Process folder configurations. Make raw data available to alterers.
    $conf['dirConfRaw'] = $conf['dirConf'];
    $conf['dirConf'] = static::processConfFolders($conf, $user, \Drupal::config('dfm.settings')->get('merge_folders'));
    // Run alterers.
    \Drupal::moduleHandler()->alter('dfm_conf', $conf, $user);
    unset($conf['dirConfRaw']);
    // Apply chroot jail.
    if (!empty($conf['chrootJail'])) {
      static::chrootJail($conf);
    }
    return $conf;
  }

  /**
   * Processes folders in a user configuration.
   */
  public static function processConfFolders(array $conf, AccountProxyInterface $user, $merge = FALSE) {
    $ret = static::processUserFolders($conf['dirConf'], $user);
    if (!$merge) {
      return $ret;
    }
    $scheme = $conf['scheme'];
    $user_roles = array_flip($user->getRoles());
    $storage = \Drupal::entityTypeManager()->getStorage('dfm_profile');
    $rid_profiles = \Drupal::config('dfm.settings')->get('roles_profiles', []);
    foreach ($rid_profiles as $rid => $profiles) {
      $pid = $profiles[$scheme] ?? NULL;
      if (!isset($user_roles[$rid]) || !$pid || $pid == $conf['pid']) {
        continue;
      }
      /** @var \Drupal\dfm\Entity\DfmProfile $profile */
      $profile = $storage->load($pid);
      if (!$profile) {
        continue;
      }
      $conf = $profile->getConf();
      $dirconfs = static::processUserFolders($conf['dirConf'], $user);
      $new = [];
      // Inherit permissions from base to new.
      foreach ($dirconfs as $dirname => $dirconf) {
        $new[$dirname] = static::mergeFolderConfs($dirconf, static::inheritedFolderConf($dirname, $ret));
      }
      // Inherit permissions from the new to base.
      // We perform bidirectional inheritance because users are usually
      // confused about the order of role-profile assignments.
      foreach (array_diff_key($ret, $dirconfs) as $dirname => $dirconf) {
        $ret[$dirname] = static::mergeFolderConfs($dirconf, static::inheritedFolderConf($dirname, $dirconfs));
      }
      $ret = array_merge($ret, $new);
    }
    return $ret;
  }

  /**
   * Merge multiple folder configurations into one.
   */
  public static function mergeFolderConfs(array $dirconf, array ...$dirconfs) {
    $subs = empty($dirconf['subdirConf']['inherit']) ? [] : NULL;
    foreach ($dirconfs as $dirconf2) {
      if (empty($dirconf['perms']['all'])) {
        if (!empty($dirconf2['perms']['all'])) {
          $dirconf['perms']['all'] = TRUE;
        }
        else {
          foreach (($dirconf2['perms'] ?? []) as $perm => $value) {
            if ($value) {
              $dirconf['perms'][$perm] = TRUE;
            }
          }
        }
      }
      if (isset($subs) && !empty($dirconf2['subdirConf'])) {
        $sub = $dirconf2['subdirConf'];
        if (!empty($sub['inherit'])) {
          $sub['perms'] = $dirconf2['perms'] ?? [];
        }
        if (!empty($sub['perms'])) {
          $subs[] = $sub;
        }
      }
    }
    if ($subs) {
      $dirconf['subdirConf'] = static::mergeFolderConfs($dirconf['subdirConf'] ?? [], ...$subs);
    }
    return $dirconf;
  }

  /**
   * Returns the closest inheritable conf for a folder from a set of confs.
   */
  public static function inheritedFolderConf($dirname, array $dirconfs) {
    // Inherit from self.
    if (isset($dirconfs[$dirname])) {
      return $dirconfs[$dirname];
    }
    // Root.
    if ($dirname === '.') {
      return [];
    }
    $inherited = [];
    $inherited_depth = -2;
    foreach ($dirconfs as $parent => $parent_conf) {
      $subdirconf = $parent_conf['subdirConf'] ?? [];
      if (!$subdirconf) {
        continue;
      }
      $root = $parent === '.';
      if (!$root && strpos($dirname . '/', $parent . '/') !== 0) {
        continue;
      }
      $found = NULL;
      $depth = $root ? -1 : substr_count($parent, '/');
      // Parent is checked to apply permissions to subfolders. Use parent conf.
      if (!empty($subdirconf['inherit'])) {
        $found = $parent_conf;
      }
      // Direct parent or a grand parent with inheritance. Use sub folder conf.
      elseif (!empty($subdirconf['subdirConf']['inherit']) || substr_count($dirname, '/') - $depth < 2) {
        $found = $subdirconf;
        $depth += 0.5;
      }
      // Inherit from the deepest.
      if ($found && $depth > $inherited_depth) {
        $inherited = $found;
        $inherited_depth = $depth;
      }
    }
    return $inherited;
  }

  /**
   * Processes user folders.
   */
  public static function processUserFolders(array $folders, AccountProxyInterface $user) {
    $ret = [];
    $token_service = \Drupal::token();
    $token_data = ['user' => User::load($user->id())];
    foreach ($folders as $folder) {
      $dirname = $folder['dirname'];
      // Replace tokens.
      if (strpos($dirname, '[') !== FALSE) {
        $dirname = $token_service->replace($dirname, $token_data);
        // Unable to resolve a token.
        if (strpos($dirname, ':') !== FALSE) {
          continue;
        }
      }
      if (static::regularPath($dirname)) {
        $ret[$dirname] = $folder;
        unset($ret[$dirname]['dirname']);
      }
    }
    return $ret;
  }

  /**
   * Applies chroot jail to the topmost directory in a profile configuration.
   */
  public static function chrootJail(array &$conf) {
    if (isset($conf['dirConf']['.'])) {
      return;
    }
    // Set the first one as topdir.
    $dirnames = array_keys($conf['dirConf']);
    $topdir = array_shift($dirnames);
    // Check the rest.
    foreach ($dirnames as $dirname) {
      // This is a subdirectory of the topdir. No change.
      if (strpos($dirname . '/', $topdir . '/') === 0) {
        continue;
      }
      // This is a parent directory of the topdir. Make it the topdir.
      if (strpos($topdir . '/', $dirname . '/') === 0) {
        $topdir = $dirname;
        continue;
      }
      // Not a part of the same branch with topdir
      // which means there is no top-most directory.
      return;
    }
    // Create the new dir conf starting from the top.
    $newdirconf = [];
    $newdirconf['.'] = $conf['dirConf'][$topdir];
    unset($conf['dirConf'][$topdir]);
    // Add the rest.
    $pos = strlen($topdir) + 1;
    foreach ($conf['dirConf'] as $dirname => $set) {
      $newdirconf[substr($dirname, $pos)] = $set;
    }
    $conf['dirConf'] = $newdirconf;
    $conf['rootDirPath'] .= (substr($conf['rootDirPath'], -1) == '/' ? '' : '/') . $topdir;
    $topdirurl = str_replace('%2F', '/', rawurlencode($topdir));
    $conf['rootDirUrl'] .= '/' . $topdirurl;
    // Also alter thumnail prefix.
    if (isset($conf['thumbUrl'])) {
      $conf['thumbUrl'] .= '/' . $topdirurl;
    }
    return $topdir;
  }

  /**
   * Checks the structure of a folder path.
   *
   * Forbids current/parent directory notations.
   */
  public static function regularPath($path) {
    return is_string($path) && ($path === '.' || !preg_match('@\\\\|(^|/)\.*(/|$)@', $path));
  }

  /**
   * Returns a managed file entity by uri.
   *
   * Optionally creates it.
   *
   * @return \Drupal\file\FileInterface
   *   Drupal File entity.
   */
  public static function getFileEntity($uri, $create = FALSE, $save = FALSE) {
    $file = FALSE;
    $files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $uri]);
    if ($files) {
      $file = reset($files);
    }
    elseif ($create) {
      $file = static::createFileEntity($uri, $save);
    }
    return $file;
  }

  /**
   * Creates a file entity with an uri.
   *
   * @return \Drupal\file\FileInterface
   *   Drupal File entity.
   */
  public static function createFileEntity($uri, $save = FALSE) {
    // Set defaults. Mime and name are set by File::preCreate()
    $values = is_array($uri) ? $uri : ['uri' => $uri];
    $values += ['uid' => \Drupal::currentUser()->id(), 'status' => 1];
    if (!isset($values['filesize'])) {
      $values['filesize'] = filesize($values['uri']);
    }
    $file = \Drupal::entityTypeManager()->getStorage('file')->create($values);
    if ($save) {
      $file->save();
    }
    return $file;
  }

  /**
   * Returns all references to a file except own references.
   */
  public static function getFileUsage($file, $include_own = FALSE) {
    $usage = \Drupal::service('file.usage')->listUsage($file);
    // Remove own usage and also imce usage.
    if (!$include_own) {
      unset($usage['dfm'], $usage['imce']);
    }
    return $usage;
  }

  /**
   * Checks if the selected relative paths are accessible by a user with Dfm.
   *
   * Returns the accessible file uris.
   */
  public static function checkFilePaths(array $paths, AccountProxyInterface $user = NULL, $scheme = NULL) {
    $ret = [];
    $fm = static::userFm($user, $scheme);
    if ($fm) {
      foreach ($paths as $path) {
        $uri = $fm->checkFile($path);
        if ($uri) {
          $ret[$path] = $uri;
        }
      }
    }
    return $ret;
  }

  /**
   * Checks if a file uri is accessible by a user with Dfm.
   */
  public static function checkFileUri($uri, AccountProxyInterface $user = NULL) {
    [$scheme, $path] = explode('://', $uri, 2);
    if ($scheme && $path) {
      $fm = static::userFm($user, $scheme);
      if ($fm) {
        return $fm->checkFileUri($uri);
      }
    }
  }

  /**
   * Returns Dfm script path.
   */
  public static function scriptPath($subpath = NULL) {
    static $libpath;
    if (!isset($libpath)) {
      $libpath = \Drupal::service('extension.list.module')->getPath('dfm') . '/library';
      if (!is_dir($libpath)) {
        $request = \Drupal::request();
        $dirs = ['', DrupalKernel::findSitePath($request), 'sites/all'];
        foreach ($dirs as $dir) {
          $libpath = ($dir ? "$dir/" : '') . 'libraries/dfm_lite';
          if (is_dir($libpath)) {
            break;
          }
        }
      }
    }
    return isset($subpath) ? $libpath . '/' . $subpath : $libpath;
  }

  /**
   * Preprocessor for Dfm page.
   */
  public static function preprocessDfmPage(&$vars) {
    $vars += ['title' => t('File Browser'), 'head' => ''];
    $vars['lang'] = \Drupal::languageManager()->getCurrentLanguage()->getId();
    $vars['libUrl'] = base_path() . static::scriptPath();
    $vars['qs'] = '?' . \Drupal::state()->get('system.css_js_query_string', '0');
    $vars['cssUrls']['core'] = $vars['libUrl'] . '/core/misc/dfm.css' . $vars['qs'];
    $vars['jsUrls']['jquery'] = $vars['libUrl'] . '/core/misc/jquery.js';
    $vars['jsUrls']['core'] = $vars['libUrl'] . '/core/misc/dfm.js' . $vars['qs'];
    $vars['scriptConf']['url'] = Url::fromRoute('<current>')->toString();
  }

  /**
   * Resolves a file uri.
   */
  public static function realpath($uri) {
    return \Drupal::service('file_system')->realpath($uri);
  }

  /**
   * Custom URL handler that is called when urlAlter option is enabled.
   */
  public static function fileUrl($uri) {
    return \Drupal::service('file_url_generator')->generateAbsoluteString($uri);
  }

  /**
   * Returns image token for thumbnail styles.
   */
  public static function itok($style) {
    $uid = \Drupal::currentUser()->id();
    $key = \Drupal::service('private_key')->get();
    return substr(md5(md5("$style:$uid:$key")), 8, 8);
  }

  /**
   * Pre renders a textarea element for Dfm integration.
   */
  public static function preRenderTextarea($element) {
    $static = &drupal_static(__FUNCTION__, []);
    $regexp = &$static['regexp'];
    if (!isset($regexp)) {
      $regexp = str_replace(' ', '', \Drupal::config('dfm.settings')->get('textareas', ''));
      if ($regexp) {
        $regexp = '@^(' . str_replace(',', '|', implode('.*', array_map('preg_quote', explode('*', $regexp)))) . ')$@';
      }
    }
    if ($regexp && preg_match($regexp, $element['#id'])) {
      if (!isset($static['access'])) {
        $static['access'] = static::access();
      }
      if ($static['access']) {
        $element['#attached']['library'][] = 'dfm/drupal.dfm.textarea';
        $element['#attributes']['class'][] = 'dfm-textarea';
      }
    }
    return $element;
  }

  /**
   * Runs file validators and returns errors.
   */
  public static function runValidators(FileInterface $file, $validators = []) {
    if (!\Drupal::hasService('file.validator')) {
      $func = 'file_validate';
      return $func($file, $validators);
    }
    $errors = [];
    foreach (\Drupal::service('file.validator')->validate($file, $validators) as $violation) {
      $errors[] = $violation->getMessage();
    }
    return $errors;
  }

  /**
   * Formats file size.
   */
  public static function formatSize($size) {
    $func = 'Drupal\Core\StringTranslation\ByteSizeMarkup::create';
    if (!is_callable($func)) {
      $func = 'format_size';
    }
    return $func($size);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc