3 namespace Drupal\redirect_404;
5 use Drupal\Component\Utility\Unicode;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\Database\Connection;
10 * Provides an SQL implementation for redirect not found storage.
12 * To keep a limited amount of relevant records, we compute a relevancy based
13 * on the amount of visits for each row, deleting the less visited record and
14 * sorted by timestamp.
16 class SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface {
19 * Maximum column length for invalid paths.
21 const MAX_PATH_LENGTH = 191;
24 * Active database connection.
26 * @var \Drupal\Core\Database\Connection
31 * The configuration factory.
33 * @var \Drupal\Core\Config\ConfigFactoryInterface
35 protected $configFactory;
38 * Constructs a new SqlRedirectNotFoundStorage.
40 * @param \Drupal\Core\Database\Connection $database
41 * A Database connection to use for reading and writing database data.
42 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
43 * The configuration factory.
45 public function __construct(Connection $database, ConfigFactoryInterface $config_factory) {
46 $this->database = $database;
47 $this->configFactory = $config_factory;
53 public function logRequest($path, $langcode) {
54 if (Unicode::strlen($path) > static::MAX_PATH_LENGTH) {
55 // Don't attempt to log paths that would result in an exception. There is
56 // no point in logging truncated paths, as they cannot be used to build a
60 // Ignore invalid UTF-8, which can't be logged.
61 if (!Unicode::validateUtf8($path)) {
65 // If the request is not new, update its count and timestamp.
66 $this->database->merge('redirect_404')
68 ->key('langcode', $langcode)
69 ->expression('count', 'count + 1')
71 'timestamp' => REQUEST_TIME,
81 public function resolveLogRequest($path, $langcode) {
82 $this->database->update('redirect_404')
83 ->fields(['resolved' => 1])
84 ->condition('path', $path)
85 ->condition('langcode', $langcode)
92 public function purgeOldRequests() {
93 $row_limit = $this->configFactory->get('redirect_404.settings')->get('row_limit');
95 $query = $this->database->select('redirect_404', 'r404');
96 $query->fields('r404', ['timestamp']);
97 // On databases known to support log(), use it to calculate a logarithmic
98 // scale of the count, to delete records with count of 1-9 first, then
100 if ($this->database->driver() == 'mysql' || $this->database->driver() == 'pgsql') {
101 $query->addExpression('floor(log(10, count))', 'count_log');
102 $query->orderBy('count_log', 'DESC');
104 $query->orderBy('timestamp', 'DESC');
106 ->range($row_limit, 1)
110 if (!empty($cutoff)) {
111 // Delete records having older timestamp and less visits (on a logarithmic
112 // scale) than cutoff.
113 $delete_query = $this->database->delete('redirect_404');
115 if ($this->database->driver() == 'mysql' || $this->database->driver() == 'pgsql') {
116 // Delete rows with same count_log AND older timestamp than cutoff.
117 $and_condition = $delete_query->andConditionGroup()
118 ->where('floor(log(10, count)) = :count_log2', [':count_log2' => $cutoff['count_log']])
119 ->condition('timestamp', $cutoff['timestamp'], '<=');
121 // And delete all the rows with count_log less than the cutoff.
122 $condition = $delete_query->orConditionGroup()
123 ->where('floor(log(10, count)) < :count_log1', [':count_log1' => $cutoff['count_log']])
124 ->condition($and_condition);
125 $delete_query->condition($condition);
128 $delete_query->condition('timestamp', $cutoff['timestamp'], '<=');
130 $delete_query->execute();
137 public function listRequests(array $header = [], $search = NULL) {
138 $query = $this->database
139 ->select('redirect_404', 'r404')
140 ->extend('Drupal\Core\Database\Query\TableSortExtender')
141 ->orderByHeader($header)
142 ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
147 // Replace wildcards with PDO wildcards.
148 // @todo Find a way to write a nicer pattern.
149 $wildcard = '%' . trim(preg_replace('!\*+!', '%', $this->database->escapeLike($search)), '%') . '%';
150 $query->condition('path', $wildcard, 'LIKE');
152 $results = $query->condition('resolved', 0, '=')->execute()->fetchAll();