qtools_profiler-8.x-1.x-dev/src/PerformanceService.php

src/PerformanceService.php
<?php

namespace Drupal\qtools_profiler;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\State\State;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\qtools_common\QToolsCryptHelper;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Drupal\Core\Path\PathMatcherInterface;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;

/**
 * Class PerformanceService.
 *
 * @package Drupal\qtools_profiler
 */
class PerformanceService {

  use StringTranslationTrait;

  /**
   * Drupal\Core\Session\AccountProxyInterface.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

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

  /**
   * Drupal\Component\Datetime\TimeInterface.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * Drupal\Core\Extension\ModuleHandlerInterface.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Drupal\Core\State\State.
   *
   * @var \Drupal\Core\State\State
   */
  protected $state;

  /**
   * Drupal\Core\Path\PathMatcher.
   *
   * @var \Drupal\Core\Path\PathMatcherInterface
   */
  protected $pathMatcher;

  /**
   * Id of the request record.
   *
   * @var int
   */
  protected $requestId;

  /**
   * Id of the client.
   *
   * @var string
   */
  protected $cookieClient;

  /**
   * Holds active extension or null.
   *
   * @var \Drupal\qtools_profiler\Extension\ExtensionInterface
   */
  protected $activeExtension;

  /**
   * Drupal\Core\File\FileSystemInterface.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * Configuration.
   *
   * @var array
   */
  protected $configuration = [
    'enabled' => FALSE,
    'preview_theme' => 'seven',
    'preview_mode' => 0,
    'cleanup' => 15 * 60,
    'flush' => FALSE,
    'monitoring' => [
      'rules' => '',
      'exclude' => '',
    ],
    'profiling' => [
      'extension' => '',
      'options' => [],
      'rules' => '',
      'cookie' => '',
    ],
  ];

  /**
   * Profile plugin manager.
   *
   * @var \Drupal\qtools_profiler\ProfilerPluginManager
   */
  protected $profilerPluginManager;

  /**
   * Cache backend.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cacheBackend;

  // Preview modes.
  const PREVIEW_MODE_SILENT = 0;
  const PREVIEW_MODE_IFRAME = 1;

  // Db tracking modes.
  const DB_MODE_NONE = 0;
  const DB_MODE_TOTALS = 1;
  const DB_MODE_SUMMARY = 2;
  const DB_MODE_DETAILS = 3;

  const DB_LOG_KEY = 'qtools_profiler';

  // Table names.
  const TABLE_ROUTE = 'qtools_profiler_route';
  const TABLE_REQUEST = 'qtools_profiler_request';
  const TABLE_RESPONSE = 'qtools_profiler_response';

  // Hook names.
  const HOOK_FINISH = 'qtools_profiler_finish';
  const HOOK_START = 'qtools_profiler_start';
  const HOOK_FLUSH = 'qtools_profiler_flush';
  const HOOK_CLEANUP = 'qtools_profiler_cleanup';
  const HOOK_REQUEST_SUMMARY_ALTER = 'qtools_profiler_request_summary';
  const HOOK_REQUEST_SUMMARY_RESPONSE_ALTER = 'qtools_profiler_request_summary_response';

  // Cookies.
  const COOKIE_PROFILER = 'qtools_profiler';
  const COOKIE_PROFILER_CLIENT = 'qtools_profiler_client';

  // Config key.
  const STATE_CONFIGURATION_KEY = 'qtools_profiler_configuration';
  const STATE_LAST_FLUSH_KEY = 'qtools_profiler_last_flush';

  const ROUTE_NA = 'n/a';

  /**
   * Constructor.
   */
  public function __construct(
    AccountProxyInterface $currentUser,
    Connection $connection,
    TimeInterface $time,
    ModuleHandlerInterface $moduleHandler,
    State $state,
    PathMatcherInterface $pathMatcher,
    ProfilerPluginManager $profilerPluginManager,
    CacheBackendInterface $cacheBackend,
    FileSystemInterface $fileSystem
  ) {
    $this->currentUser = $currentUser;
    $this->connection = $connection;
    $this->time = $time;
    $this->moduleHandler = $moduleHandler;
    $this->state = $state;
    $this->pathMatcher = $pathMatcher;
    $this->profilerPluginManager = $profilerPluginManager;
    $this->cacheBackend = $cacheBackend;
    $this->fileSystem = $fileSystem;

    // Read configuration from state and merge with defaults.
    $activeConfiguration = $this->state->get(static::STATE_CONFIGURATION_KEY, []);
    foreach ($this->configuration as $key => $value) {
      if (isset($activeConfiguration[$key])) {
        $this->configuration[$key] = $activeConfiguration[$key];
      }
    }

  }

  /**
   * Returns list of supported profilers.
   */
  public function getProfilerOptions() {
    $options = ['' => $this->t('None')];

    foreach ($this->profilerPluginManager->getDefinitions() as $plugin) {
      $classname = $plugin['class'];
      $options[$plugin['id']] = $classname::isLoaded() ?
        $plugin['label'] :
        $this->t('@label (extension not loaded)', ['@label' => $plugin['label']]);
      ;
    }

    return $options;
  }

  /**
   * Returns settings form for profilers.
   */
  public function getProfilerOptionsForm() {
    $form_items = [];
    foreach ($this->profilerPluginManager->getDefinitions() as $plugin) {
      $profiler = $this->profilerPluginManager->createInstance($plugin['id']);
      $form_items[$plugin['id']] = [
        '#type' => 'fieldset',
        '#title' => $this->t('@name settings', ['@name' => $plugin['label']]),
        '#tree' => TRUE,
      ];
      $form_items[$plugin['id']] += $profiler->getSettingsForm($this->confGet());
    }

    return $form_items;
  }

  /**
   * Build profile.
   */
  public function buildProfilePage(Request $request, $id) {
    $query = $this->connection->select(static::TABLE_RESPONSE, 'r');
    $query->fields('r', ['report']);

    $query->condition('r.rid', $id);
    $extension = $query->execute()->fetchField();

    try {
      $this->activeExtension = $this->profilerPluginManager->createInstance($extension);
    }
    catch (\Exception $e) {
      // Php extention is not loaded.
    }

    if (!empty($this->activeExtension)) {
      return $this->activeExtension->buildProfilePage($request, $id);
    }

    return [];
  }

  /**
   * Returns current configuration.
   *
   * @return array
   *   Configuration.
   */
  public function confGet($key = NULL) {
    return empty($key) ? $this->configuration : $this->configuration[$key];
  }

  /**
   * Returns current configuration.
   */
  public function confSet($newConfiguration) {
    foreach ($this->configuration as $key => $value) {
      if (isset($newConfiguration[$key])) {
        $this->configuration[$key] = $newConfiguration[$key];
      }
    }
    $this->state->set(static::STATE_CONFIGURATION_KEY, $this->configuration);
  }

  /**
   * Erase all collected data.
   */
  public function flush() {
    // Flush response data.
    $this->connection->truncate(static::TABLE_RESPONSE)->execute();
    $this->connection->truncate(static::TABLE_REQUEST)->execute();
    $this->requestId = NULL;

    // Flush profiling data.
    $this->flushProfilingData();

    // Ping other modules erase additional data.
    $this->moduleHandler->invokeAll(static::HOOK_FLUSH);
  }

  /**
   * Erase collected data up until given timestamp.
   *
   * @return int
   *   Amount of elements that were cleaned up.
   */
  public function cleanup($timestamp) {
    // If this is first call today - flush data if configured.
    if ($this->confGet('flush') == TRUE) {
      $last_flush = $this->state->get(static::STATE_LAST_FLUSH_KEY);
      $today = date('Y-m-d');
      if ($last_flush != $today) {
        $this->flush();
        $this->state->set(static::STATE_LAST_FLUSH_KEY, $today);
        return -1;
      }
    }

    // Find request that match given time best.
    $query = $this->connection->select(static::TABLE_REQUEST, 'r')
      ->fields('r', ['id'])
      ->orderBy('r.timestamp', 'ASC')
      ->range(0, 1);

    $query->condition('r.timestamp', $timestamp, '>=');
    $id = $query->execute()->fetchField();

    // If such record exists we clean up everything before it.
    if (!empty($id)) {
      // Disable logging if we will remove current record too.
      if ($id >= $this->requestId) {
        $this->requestId = NULL;
      }

      // Count amount of records to be cleaned.
      $query = $this->connection->select(static::TABLE_REQUEST, 'r')
        ->orderBy('r.timestamp', 'ASC');
      $query->condition('id', $id, '<');
      $count = $query->countQuery()->execute()->fetchField();

      // Clean up own tables.
      $this->connection->delete(static::TABLE_REQUEST)
        ->condition('id', $id, '<')
        ->execute();
      $this->connection->delete(static::TABLE_RESPONSE)
        ->condition('rid', $id, '<')
        ->execute();

      // Cleanup profiling files.
      $this->cleanupProfilingData($id);

      // Signal other modules to clean up their data.
      $this->moduleHandler->invokeAll(static::HOOK_CLEANUP, [$id]);
    }
    else {
      $count = 0;
    }

    return $count;
  }

  /**
   * Push redirect id to storage.
   */
  public function storeRedirectId($id) {
    // We can only store them is we have a client cookie.
    if (!empty($this->cookieClient)) {
      $cache = $this->cacheBackend->get($this->cookieClient);
      $data = !empty($cache) && !empty($cache->data) ? $cache->data : [];
      $data[] = $id;
      $this->cacheBackend->set($this->cookieClient, $data, time() + 60);
    }
  }

  /**
   * Read redirect ids.
   */
  public function getRedirectIds($clear = TRUE) {
    // We can only get them is we have a client cookie.
    if (empty($this->cookieClient)) {
      return [];
    }

    $cache = $this->cacheBackend->get($this->cookieClient);
    $data = !empty($cache) && !empty($cache->data) ? $cache->data : [];

    if ($clear) {
      $this->cacheBackend->delete($this->cookieClient);
    }
    return $data;
  }

  /**
   * Handle request termination.
   */
  public function monitoringFinish(PostResponseEvent $event) {
    // Only act if we have valid requestID.
    if (!$this->monitoringActive()) {
      $this->finishProfiling(FALSE);
      return;
    }

    // Check if table still exists.
    if (!$this->connection->schema()->tableExists(static::TABLE_RESPONSE)) {
      $this->finishProfiling(FALSE);
      return;
    }

    // If this is redirect response, store its id.
    if ($event->getResponse() instanceof RedirectResponse) {
      $this->storeRedirectId($this->requestId);
    }

    // Get database monitoring stats.
    $db_time = $this->finishDblogging(TRUE);

    // Finish profiling.
    $this->finishProfiling(TRUE);

    // Build response data.
    $fields = [
      'rid' => $this->requestId,
      'memory' => memory_get_peak_usage(),
      'time' => $this->time->getCurrentMicroTime() - $this->time->getRequestMicroTime(),
      'db_time' => $db_time,
      'code' => $event->getResponse()->getStatusCode(),
      'report' => !empty($this->activeExtension) ? $this->activeExtension->getPluginId() : 0,
    ];

    // Save response data.
    $this->connection->insert(static::TABLE_RESPONSE)
      ->fields($fields)
      ->execute();

    // Ping other modules to call their routine.
    $this->moduleHandler->invokeAll(static::HOOK_FINISH, [$this->requestId]);
  }

  /**
   * Handle request completion.
   */
  public function monitoringComplete(FilterResponseEvent $event) {
    // Only act if we have valid requestID.
    if (!$this->monitoringActive()) {
      $this->finishProfiling(FALSE);
      return;
    }

    // Check if table still exists.
    if (!$this->connection->schema()->tableExists(static::TABLE_RESPONSE)) {
      $this->finishProfiling(FALSE);
      return;
    }

    // Get database monitoring stats.
    $db_time = $this->finishDblogging(FALSE);

    // Build response stats.
    $in_progress_stats = [
      'rid' => $this->requestId,
      'memory' => memory_get_peak_usage(),
      'time' => $this->time->getCurrentMicroTime() - $this->time->getRequestMicroTime(),
      'db_time' => $db_time,
      'code' => $event->getResponse()->getStatusCode(),
      'report' => !empty($this->activeExtension) ? $this->activeExtension->getPluginId() : 0,
    ];

    // Place tracking data in the response.
    $this->addTrackingInformation($event, $this->requestId, $in_progress_stats);
  }

  /**
   * Add tracking information to supported responses.
   */
  public function addTrackingInformation(FilterResponseEvent $event, $requestId, $in_progress_stats = NULL) {
    // Add header to any request.
    $summary = $this->getRequestSummary($requestId, $in_progress_stats);
    $event->getResponse()->headers->set('X-QTools-Profiler-RequestId', $requestId);
    $event->getResponse()->headers->set('X-QTools-Profiler-RequestSummary', json_encode($summary));
  }

  /**
   * Gets dblogging stats.
   */
  public function finishDblogging($save = FALSE) {
    if (!$this->dbloggingAllowed(static::DB_MODE_TOTALS)) {
      return NULL;
    }

    $queries = &drupal_static('qtols_profiler_dblogging_queries', NULL);
    if ($queries === NULL) {
      $queries = Database::getLog(static::DB_LOG_KEY);
      // Remove caller args.
      if (!empty($queries)) {
        foreach ($queries as &$query) {
          unset($query['caller']['args']);
        }
      }
    }

    $total = 0;
    if (!empty($queries)) {
      foreach ($queries as $query) {
        $total += $query['time'];
      }
    }

    return $total;
  }

  /**
   * Get Request name.
   *
   * @return string|null
   *   Request name.
   */
  public function getRequestName($requestId) {
    // Check if table still exists.
    if (!$this->connection->schema()->tableExists(static::TABLE_RESPONSE)) {
      return NULL;
    }

    // Base table.
    $query = $this->connection->select(static::TABLE_REQUEST, 'r');

    // Join additional resources.
    $query->leftJoin(static::TABLE_RESPONSE, 'response', 'r.id = response.rid');

    // Response details.
    $query->fields('r');
    $query->fields('response');
    $query->condition('id', $requestId);
    $result = $query->range(0, 1)->execute()->fetchObject();

    $name = $this->t('-> @code @method:@uri', [
      '@code' => $result->code,
      '@method' => $result->method,
      '@uri' => $result->uri,
    ])->render();

    return $name;
  }

  /**
   * Get Request details.
   *
   * @return array|null
   *   Summary definitions array.
   */
  public function getRequestSummary($requestId, $in_progress_stats = NULL) {
    // Check if table still exists.
    if (!$this->connection->schema()->tableExists(static::TABLE_RESPONSE)) {
      return NULL;
    }

    // If stats are supplied no need to request them.
    if ($in_progress_stats !== NULL) {
      $response_stats = (object) $in_progress_stats;
    }
    else {
      // Response details.
      $query = $this->connection->select(static::TABLE_RESPONSE, 'r');
      $query->fields('r');
      $query->condition('rid', $requestId);
      $response_stats = $query->range(0, 1)->execute()->fetchObject();
    }

    if (!empty($response_stats)) {
      if ($response_stats->db_time !== NULL) {
        $db_time = round($response_stats->db_time, 3);
      }
      else {
        $db_time = t('N/A')->render();
      }
      $summary = [
        ['wall', round($response_stats->time, 3), 'sec'],
        ['db', $db_time, 'sec'],
        ['mem', round($response_stats->memory / 1024 / 1024), 'mb'],
      ];
    }
    else {
      $response_stats = (object) [];
      $summary = [];
    }

    // Allow other modules to modify summary.
    $response_stats->in_progress = ($in_progress_stats != NULL);
    $this->moduleHandler->alter(static::HOOK_REQUEST_SUMMARY_ALTER, $summary, $requestId, $response_stats);

    return $summary;
  }

  /**
   * Make sure we have monitoring cookie set.
   */
  public function monitoringEnsureCookie(GetResponseEvent $event) {
    $cookie = $event->getRequest()->cookies->get(static::COOKIE_PROFILER_CLIENT);
    if (empty($cookie) || QToolsCryptHelper::check($cookie) !== QToolsCryptHelper::CHECK_VALID) {
      $cookie = QToolsCryptHelper::sign(uniqid(), QToolsCryptHelper::SIGN_SITE);
      setrawcookie(static::COOKIE_PROFILER_CLIENT, rawurlencode($cookie), 0, '/');
    }

    $this->cookieClient = $cookie;
  }

  /**
   * Checks if monitoring has started.
   *
   * @return bool
   *   TRUE if monitoring has started.
   */
  public function monitoringActive() {
    return !empty($this->requestId);
  }

  /**
   * Sets current route.
   */
  public function setRoute($route) {
    // If we already started then this is subrequest,
    // that we merge into main one.
    if (!$this->monitoringActive()) {
      return;
    }

    // Check if table still exists (if we uninstalling module,
    // it will be empty for current request).
    if (!$this->connection->schema()->tableExists(static::TABLE_REQUEST)) {
      return;
    }

    $this->connection->update(static::TABLE_REQUEST)
      ->fields(['rid' => $this->getRouteId($route)])
      ->condition('id', $this->requestId)
      ->execute();
  }

  /**
   * Start collection routine.
   */
  public function monitoringStart(GetResponseEvent $event) {
    // If we already started then this is subrequest,
    // that we merge into main one.
    if ($this->monitoringActive()) {
      return;
    }

    // Check if table still exists (if we uninstalling module,
    // it will be empty for current request).
    if (!$this->connection->schema()->tableExists(static::TABLE_REQUEST)) {
      return;
    }

    // Build request data.
    $request = $event->getRequest();
    $fields = [
      'uid' => $this->currentUser->id(),
      'rid' => $this->getRouteId(static::ROUTE_NA),
      'timestamp' => $this->time->getRequestTime(),
      'uri' => substr($request->getRequestUri(), 0, 256),
      'method' => substr($request->getMethod(), 0, 7),
    ];

    // Insert initial request data and get request id.
    $this->requestId = $this->connection->insert(static::TABLE_REQUEST)
      ->fields($fields)
      ->execute();

    // Exit if wasn't able to start monitoring.
    if (empty($this->requestId)) {
      $this->requestId = NULL;
      return;
    }

    // Start profiler run if we have active extension selected.
    if ($this->profilingAllowed($event)) {
      $this->startProfiling();
    }

    // Start dblogging.
    if ($this->dbloggingAllowed(static::DB_MODE_TOTALS)) {
      Database::startLog(static::DB_LOG_KEY);
    }

    // Ping other modules to call their routine.
    $this->moduleHandler->invokeAll(static::HOOK_START, [$this->requestId]);
  }

  /**
   * Returns current requestId.
   *
   * @return int
   *   Id of the request.
   */
  public function getRequestId() {
    return $this->requestId;
  }

  /**
   * Gets route id or add new one in the database.
   *
   * @return int
   *   Id of the route.
   */
  protected function getRouteId($route) {
    // Lookup for route in database.
    $result = $this->connection->select(static::TABLE_ROUTE, 'r')
      ->fields('r', ['id'])
      ->condition('route', $route)
      ->range(0, 1)
      ->execute()
      ->fetchObject();

    if (!empty($result)) {
      $id = $result->id;
    }
    else {
      $id = $this->connection->insert(static::TABLE_ROUTE)
        ->fields(['route' => substr($route, 0, 256)])
        ->execute();
    }

    return $id;
  }

  /**
   * Return TRUE if monitoring for this request is allowed.
   *
   * @return bool
   *   Result of the allowed check.
   */
  public function monitoringAllowed(GetResponseEvent $event) {
    // Don't log if we not supposed to.
    $conf = $this->confGet();
    if (!empty($conf['enabled'])) {
      // Always ignore report view pages.
      $path = $event->getRequest()->getRequestUri();
      if (strpos($path, '/qtools_profiler/') === 0) {
        return FALSE;
      }

      // Check by rules.
      $excluded = $this->rulesValid($conf['monitoring']['exclude'], $event);
      if (!$excluded) {
        return $this->rulesValid($conf['monitoring']['rules'], $event);
      }
    }

    return FALSE;
  }

  /**
   * Return TRUE if profiling for this request is allowed.
   *
   * @return bool
   *   Result of the allowed check.
   */
  protected function profilingAllowed(GetResponseEvent $event) {
    $conf = $this->confGet();

    // Get profiling rules.
    if (!empty($conf['profiling']['extension'])) {
      // Check by cookie.
      if (strlen($conf['profiling']['cookie']) > 10) {
        $cookie = $event->getRequest()->cookies->get(static::COOKIE_PROFILER);
        if ($cookie == $conf['profiling']['cookie']) {
          return TRUE;
        }
      }

      // Check by rules.
      return $this->rulesValid($conf['profiling']['rules'], $event);
    }

    return FALSE;
  }

  /**
   * Return TRUE if requested level of dblogging is allowed.
   *
   * @return bool
   *   Result of the allowed check.
   */
  protected function dbloggingAllowed($mode) {
    $conf = $this->confGet();

    // Get profiling rules.
    $dblogging = $conf['monitoring']['dblogging'] ?? static::DB_MODE_NONE;
    return $dblogging >= $mode;
  }

  /**
   * Returns TRUE if profiling started.
   */
  public function profilingStarted() {
    return !empty($this->activeExtension);
  }

  /**
   * Starts profiling session.
   */
  protected function startProfiling() {
    $conf = $this->confGet();

    // Choose extension.
    $extension = $conf['profiling']['extension'];

    try {
      $this->activeExtension = $this->profilerPluginManager->createInstance($extension);
    }
    catch (\Exception $e) {
      // Php extention is not loaded.
    }

    // Start profiling if we have active extension.
    if (!empty($this->activeExtension)) {
      $options = $conf['profiling']['profilers'][$extension] ?? [];
      $this->activeExtension->enable($options);
    }
  }

  /**
   * Ends profiling session.
   */
  protected function finishProfiling($save) {
    if ($this->activeExtension) {
      $data = $this->activeExtension->disable();

      // Save profiling data.
      if ($save) {
        $this->saveProfilingData($this->requestId, $data);
      }
    }
  }

  /**
   * Flush profiling files.
   */
  protected function flushProfilingData() {
    $dir = $this->getProfilingDataDirectory();
    if (!empty($dir)) {
      // Erase epoch record.
      $this->getProfilingEpoch(TRUE);

      // Remove all files in the pool.
      $this->fileSystem->deleteRecursive($dir);
    }
  }

  /**
   * Cleanup profiling files up until given record.
   */
  protected function cleanupProfilingData($rid = NULL) {
    $dir = $this->getProfilingDataDirectory();
    if (!empty($dir)) {
      // Remove all records up until $rid.
      $epoch = $this->getProfilingEpoch();
      $files = $this->fileSystem->scanDirectory($dir, '/\d*-\d*\.php/');
      foreach ($files as $filepath => $info) {
        [$frid, $fepoch] = explode('-', $info->name);
        if ($epoch <> $fepoch || $frid < $rid) {
          $this->fileSystem->delete($filepath);
        }
      }
    }
  }

  /**
   * Saves profiling data to file.
   */
  protected function saveProfilingData($id, $data) {
    $data = serialize($data);

    $file_name = $this->getProfilingDataFileName($id);
    if ($file_name) {
      $file = fopen($file_name, 'w');

      if ($file) {
        fwrite($file, $data);
        fclose($file);
      }
    }
  }

  /**
   * Returns reports directory.
   *
   * @return string|null
   *   Filename.
   */
  public function getProfilingDataDirectory() {
    $dir = $this->fileSystem->getTempDirectory();
    $dir .= '/qtools_profiler/' . Settings::get('qtools_profiler_pool', 'default');
    if ($this->fileSystem->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
      return $dir;
    }
    return NULL;
  }

  /**
   * Returns report full filename.
   *
   * @return string|null
   *   Filename.
   */
  public function getProfilingDataFileName($id) {
    $dir = $this->getProfilingDataDirectory();
    if (!empty($dir)) {
      return $dir . '/' . $id . '-' . $this->getProfilingEpoch() . '.php';
    }
    return NULL;
  }

  /**
   * Returns profiling epoch.
   *
   * @return int
   *   Profiling epoch.
   */
  public function getProfilingEpoch($reset = FALSE) {
    $epoch = $this->state->get('qtools_profiler.epoch', NULL);
    if (empty($epoch) || $reset) {
      $epoch = time();
      $this->state->set('qtools_profiler.epoch', $epoch);
    }

    return $epoch;
  }

  /**
   * Check if rule is valid.
   */
  protected function rulesValid($rules_text, GetResponseEvent $event) {
    // Check by rules.
    $rules = explode(PHP_EOL, $rules_text);
    $validation_context = $this->getRuleValidaionContext($event);
    foreach ($rules as $rule) {
      $fraction = $this->ruleValidate($rule, $validation_context);
      if ($fraction !== NULL) {
        $roll = ($fraction > 0) ? mt_rand(1, $fraction) : 0;
        return ($roll == 1);
      }
    }

    return FALSE;
  }

  /**
   * Check if rule is valid.
   */
  protected function ruleValidate($rule, $context) {
    $parts = explode('|', trim($rule));
    $fraction = array_shift($parts);
    foreach ($parts as $part) {
      [$key, $value] = explode('=', $part);

      // Check if context is array and our value is in.
      if (is_array($context[$key])) {
        if (!in_array($value, $context[$key])) {
          return NULL;
        }
      }
      // For path we use pathmatcher.
      elseif ($key == 'path') {
        if (!$this->pathMatcher->matchPath($context['path'], $value)) {
          return NULL;
        }
      }
      // Simplest case we just check if those equal.
      else {
        if ($context[$key] != $value) {
          return NULL;
        }
      }
    }

    // If we get here all is valid.
    return (int) $fraction;
  }

  /**
   * Check if rule is valid.
   */
  protected function getRuleValidaionContext(GetResponseEvent $event) {
    $context = [
      'path' => $event->getRequest()->getRequestUri(),
      'uid' => $this->currentUser->id(),
      'role' => $this->currentUser->getRoles(),
    ];
    return $context;
  }

}

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

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