Version 1
[yaffs-website] / web / modules / contrib / redirect / modules / redirect_404 / src / SqlRedirectNotFoundStorage.php
1 <?php
2
3 namespace Drupal\redirect_404;
4
5 use Drupal\Component\Utility\Unicode;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\Database\Connection;
8
9 /**
10  * Provides an SQL implementation for redirect not found storage.
11  *
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.
15  */
16 class SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface {
17
18   /**
19    * Maximum column length for invalid paths.
20    */
21   const MAX_PATH_LENGTH = 191;
22
23   /**
24    * Active database connection.
25    *
26    * @var \Drupal\Core\Database\Connection
27    */
28   protected $database;
29
30   /**
31    * The configuration factory.
32    *
33    * @var \Drupal\Core\Config\ConfigFactoryInterface
34    */
35   protected $configFactory;
36
37   /**
38    * Constructs a new SqlRedirectNotFoundStorage.
39    *
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.
44    */
45   public function __construct(Connection $database, ConfigFactoryInterface $config_factory) {
46     $this->database = $database;
47     $this->configFactory = $config_factory;
48   }
49
50   /**
51    * {@inheritdoc}
52    */
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
57       // new redirect.
58       return;
59     }
60     // Ignore invalid UTF-8, which can't be logged.
61     if (!Unicode::validateUtf8($path)) {
62       return;
63     }
64
65     // If the request is not new, update its count and timestamp.
66     $this->database->merge('redirect_404')
67       ->key('path', $path)
68       ->key('langcode', $langcode)
69       ->expression('count', 'count + 1')
70       ->fields([
71         'timestamp' => REQUEST_TIME,
72         'count' => 1,
73         'resolved' => 0,
74       ])
75       ->execute();
76   }
77
78   /**
79    * {@inheritdoc}
80    */
81   public function resolveLogRequest($path, $langcode) {
82     $this->database->update('redirect_404')
83       ->fields(['resolved' => 1])
84       ->condition('path', $path)
85       ->condition('langcode', $langcode)
86       ->execute();
87   }
88
89   /**
90    * {@inheritdoc}
91    */
92   public function purgeOldRequests() {
93     $row_limit = $this->configFactory->get('redirect_404.settings')->get('row_limit');
94
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
99     // 10-99 and so on.
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');
103     }
104     $query->orderBy('timestamp', 'DESC');
105     $cutoff = $query
106       ->range($row_limit, 1)
107       ->execute()
108       ->fetchAssoc();
109
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');
114
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'], '<=');
120
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);
126       }
127       else {
128         $delete_query->condition('timestamp', $cutoff['timestamp'], '<=');
129       }
130       $delete_query->execute();
131     }
132   }
133
134   /**
135    * {@inheritdoc}
136    */
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')
143       ->limit(25)
144       ->fields('r404');
145
146     if ($search) {
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');
151     }
152     $results = $query->condition('resolved', 0, '=')->execute()->fetchAll();
153
154     return $results;
155   }
156
157 }