class SqlRedirectNotFoundStorage in Redirect 8
Provides an SQL implementation for redirect not found storage.
To keep a limited amount of relevant records, we compute a relevancy based on the amount of visits for each row, deleting the less visited record and sorted by timestamp.
Hierarchy
- class \Drupal\redirect_404\SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface
Expanded class hierarchy of SqlRedirectNotFoundStorage
4 files declare their use of SqlRedirectNotFoundStorage
- RedirectFix404Form.php in modules/
redirect_404/ src/ Form/ RedirectFix404Form.php - redirect_404.install in modules/
redirect_404/ redirect_404.install - Update hooks for the redirect_404 module.
- redirect_404.views.inc in modules/
redirect_404/ redirect_404.views.inc - Provide views data for redirect_404.module.
- SqlRedirectNotFoundStorageTest.php in modules/
redirect_404/ tests/ src/ Unit/ SqlRedirectNotFoundStorageTest.php
1 string reference to 'SqlRedirectNotFoundStorage'
- redirect_404.services.yml in modules/
redirect_404/ redirect_404.services.yml - modules/redirect_404/redirect_404.services.yml
1 service uses SqlRedirectNotFoundStorage
- redirect.not_found_storage in modules/
redirect_404/ redirect_404.services.yml - Drupal\redirect_404\SqlRedirectNotFoundStorage
File
- modules/
redirect_404/ src/ SqlRedirectNotFoundStorage.php, line 16
Namespace
Drupal\redirect_404View source
class SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface {
/**
* Maximum column length for invalid paths.
*/
const MAX_PATH_LENGTH = 191;
/**
* Active database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs a new SqlRedirectNotFoundStorage.
*
* @param \Drupal\Core\Database\Connection $database
* A Database connection to use for reading and writing database data.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public function __construct(Connection $database, ConfigFactoryInterface $config_factory) {
$this->database = $database;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function logRequest($path, $langcode) {
if (mb_strlen($path) > static::MAX_PATH_LENGTH) {
// Don't attempt to log paths that would result in an exception. There is
// no point in logging truncated paths, as they cannot be used to build a
// new redirect.
return;
}
// Ignore invalid UTF-8, which can't be logged.
if (!Unicode::validateUtf8($path)) {
return;
}
// If the request is not new, update its count and timestamp.
$this->database
->merge('redirect_404')
->key('path', $path)
->key('langcode', $langcode)
->expression('count', 'count + 1')
->expression('daily_count', 'daily_count + 1')
->fields([
'timestamp' => \Drupal::time()
->getRequestTime(),
'count' => 1,
'daily_count' => 1,
'resolved' => 0,
])
->execute();
}
/**
* {@inheritdoc}
*/
public function resolveLogRequest($path, $langcode) {
$this->database
->update('redirect_404')
->fields([
'resolved' => 1,
])
->condition('path', $path)
->condition('langcode', $langcode)
->execute();
}
/**
* {@inheritdoc}
*/
public function purgeOldRequests() {
$row_limit = $this->configFactory
->get('redirect_404.settings')
->get('row_limit');
// In admin form 0 used as value for 'All' label.
if ($row_limit == 0) {
return;
}
$query = $this->database
->select('redirect_404', 'r404');
$query
->fields('r404', [
'timestamp',
]);
// On databases known to support log(), use it to calculate a logarithmic
// scale of the count, to delete records with count of 1-9 first, then
// 10-99 and so on.
if ($this->database
->driver() == 'mysql' || $this->database
->driver() == 'pgsql') {
$query
->addExpression('floor(log(10, count))', 'count_log');
$query
->orderBy('count_log', 'DESC');
}
$query
->orderBy('timestamp', 'DESC');
$cutoff = $query
->range($row_limit, 1)
->execute()
->fetchAssoc();
if (!empty($cutoff)) {
// Delete records having older timestamp and less visits (on a logarithmic
// scale) than cutoff.
$delete_query = $this->database
->delete('redirect_404');
if ($this->database
->driver() == 'mysql' || $this->database
->driver() == 'pgsql') {
// Delete rows with same count_log AND older timestamp than cutoff.
$and_condition = $delete_query
->andConditionGroup()
->where('floor(log(10, count)) = :count_log2', [
':count_log2' => $cutoff['count_log'],
])
->condition('timestamp', $cutoff['timestamp'], '<=');
// And delete all the rows with count_log less than the cutoff.
$condition = $delete_query
->orConditionGroup()
->where('floor(log(10, count)) < :count_log1', [
':count_log1' => $cutoff['count_log'],
])
->condition($and_condition);
$delete_query
->condition($condition);
}
else {
$delete_query
->condition('timestamp', $cutoff['timestamp'], '<=');
}
$delete_query
->execute();
}
}
/**
* {@inheritdoc}
*/
public function listRequests(array $header = [], $search = NULL) {
$query = $this->database
->select('redirect_404', 'r404')
->extend('Drupal\\Core\\Database\\Query\\TableSortExtender')
->orderByHeader($header)
->extend('Drupal\\Core\\Database\\Query\\PagerSelectExtender')
->limit(25)
->fields('r404');
if ($search) {
// Replace wildcards with PDO wildcards.
// @todo Find a way to write a nicer pattern.
$wildcard = '%' . trim(preg_replace('!\\*+!', '%', $this->database
->escapeLike($search)), '%') . '%';
$query
->condition('path', $wildcard, 'LIKE');
}
$results = $query
->condition('resolved', 0, '=')
->execute()
->fetchAll();
return $results;
}
/**
* {@inheritdoc}
*/
public function resetDailyCount() {
$this->database
->update('redirect_404')
->fields([
'daily_count' => 0,
])
->execute();
}
}