View source
<?php
namespace Drupal\path_redirect_import;
use Drupal\redirect\Entity\Redirect;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Url;
use Drupal\Core\Language\Language;
class ImporterService {
public static $messages = [];
public static $options = [];
public static function import($file, array $options) {
$data = self::read($file, $options);
self::$options = $options;
$data = array_filter($data, [
'self',
'preSave',
]);
if ($options['suppress_messages'] != 1 && !empty(self::$messages['warning'])) {
foreach (self::$messages['warning'] as $warning) {
\Drupal::messenger()
->addWarning($warning, 'warning');
}
}
if (empty($data)) {
\Drupal::messenger()
->addWarning(t('The uploaded file contains no rows with compatible redirect data. No redirects have imported. Compare your file to <a href=":sample">this sample data.</a>', [
':sample' => '/' . drupal_get_path('module', 'path_redirect_import') . '/redirect-example-file.csv',
]));
}
else {
if (PHP_SAPI == 'cli' && function_exists('drush_main')) {
foreach ($data as $redirect_array) {
self::save($redirect_array, $options['override'], []);
}
}
else {
foreach ($data as $row) {
$operations[] = [
[
'\\Drupal\\path_redirect_import\\ImporterService',
'save',
],
[
$row,
$options['override'],
],
];
}
$batch = [
'title' => t('Saving Redirects'),
'operations' => $operations,
'finished' => [
'\\Drupal\\path_redirect_import\\ImporterService',
'finish',
],
'file' => drupal_get_path('module', 'path_redirect_import') . '/path_redirect_import.module',
];
batch_set($batch);
}
}
}
public static function finish($success, $results, $operations) {
if ($success) {
$message = t('Redirects processed.');
}
else {
$message = t('Finished with an error.');
}
\Drupal::messenger()
->addStatus($message);
}
protected static function read($file, array $options = []) {
if (PHP_SAPI == 'cli' && function_exists('drush_main')) {
$filepath = $file;
}
else {
$filepath = \Drupal::service('file_system')
->realpath($file
->getFileUri());
}
if (!($f = fopen($filepath, 'r'))) {
return [
'success' => FALSE,
'message' => [
t('Unable to read the file'),
],
];
}
$options += [
'delimiter' => ',',
'no_headers' => FALSE,
'override' => FALSE,
'status_code' => '301',
'language' => Language::LANGCODE_NOT_SPECIFIED,
];
$line_no = 0;
$messages = [];
$success = FALSE;
$data = [];
while ($line = fgetcsv($f, 0, $options['delimiter'])) {
$message = [];
$line_no++;
if ($line_no == 1 && !$options['no_headers']) {
\Drupal::messenger()
->addMessage(t('Skipping the header row.'));
continue;
}
if (!is_array($line)) {
self::$messages['warning'][] = t('Line @line_no is invalid; bypassed.', [
'@line_no' => $line_no,
]);
continue;
}
if (empty($line[0]) || empty($line[1])) {
self::$messages['warning'][] = t('Line @line_no contains invalid data; bypassed.', [
'@line_no' => $line_no,
]);
continue;
}
if (empty($line[2])) {
$line[2] = $options['status_code'];
}
else {
$redirect_options = redirect_status_code_options();
if (!isset($redirect_options[$line[2]])) {
self::$messages['warning'][] = t('Line @line_no contains invalid status code; bypassed.', [
'@line_no' => $line_no,
]);
continue;
}
}
if (empty($line[3]) || !\Drupal::moduleHandler()
->moduleExists('language')) {
$line[3] = $options['language'];
}
elseif (!self::isValidLanguage($line[3])) {
self::$messages['warning'][] = t('Line @line_no contains an invalid language code; bypassed.', [
'@line_no' => $line_no,
]);
continue;
}
$data[$line_no] = [
'source' => self::stripLeadingSlash($line[0]),
'redirect' => isset($line[1]) ? self::stripLeadingSlash($line[1]) : NULL,
'status_code' => $line[2],
'language' => isset($line[3]) ? $line[3] : $options['language'],
];
}
fclose($f);
return $data;
}
public static function preSave(array $row) {
if ($row['source'] == '<front>') {
self::$messages['warning'][] = t('You cannot create a redirect from the front page. Bypassing "@source".', [
'@source' => $row['source'],
]);
return FALSE;
}
if (strpos($row['source'], '#') !== FALSE) {
self::$messages['warning'][] = t('Redirects from anchor fragments (i.e., with "#) are not allowed. Bypassing "@source".', [
'@source' => $row['source'],
]);
return FALSE;
}
if (self::internalPathMissing($row['redirect']) && self::$options['allow_nonexistent'] == 0) {
self::$messages['warning'][] = t('The destination path "@redirect" does not exist on the site. Redirect from "@source" bypassed.', [
'@redirect' => $row['redirect'],
'@source' => $row['source'],
]);
return FALSE;
}
if (self::sourceIsDestination($row)) {
self::$messages['warning'][] = t('You are attempting to redirect "@redirect" to itself. Bypassed, as this will result in an infinite loop.', [
'@redirect' => $row['redirect'],
]);
return FALSE;
}
return TRUE;
}
public static function save(array $redirect_array, $override) {
if ($redirects = self::redirectExists($redirect_array)) {
if ($override == 1) {
$redirect = reset($redirects);
$message_type = 'Updated';
}
else {
return;
}
}
else {
$message_type = 'Added';
$parsed_url = UrlHelper::parse(trim($redirect_array['source']));
$path = isset($parsed_url['path']) ? $parsed_url['path'] : NULL;
$query = isset($parsed_url['query']) ? $parsed_url['query'] : NULL;
$redirectEntityManager = \Drupal::service('entity.manager')
->getStorage('redirect');
$redirect = $redirectEntityManager
->create();
$redirect
->setSource($path, $query);
}
if (parse_url($redirect_array['redirect'], PHP_URL_SCHEME)) {
$redirect->redirect_redirect
->set(0, [
'uri' => $redirect_array['redirect'],
]);
}
else {
$redirect
->setRedirect($redirect_array['redirect']);
}
$redirect
->setStatusCode($redirect_array['status_code']);
$redirect
->setLanguage($redirect_array['language']);
$redirect
->save();
\Drupal::messenger()
->addStatus(t('@message_type redirect from @source to @redirect', [
'@message_type' => $message_type,
'@source' => $redirect_array['source'],
'@redirect' => $redirect_array['redirect'],
]));
}
protected static function stripLeadingSlash($path) {
if (strpos($path, '/') === 0) {
return substr($path, 1);
}
return $path;
}
protected static function addLeadingSlash($path) {
if (strpos($path, '/') !== 0) {
return '/' . $path;
}
return $path;
}
protected static function internalPathMissing($destination) {
if ($destination == '<front>') {
return FALSE;
}
$parsed = parse_url($destination);
if (!isset($parsed['scheme'])) {
$alias = self::addLeadingSlash($destination);
$normal_path = \Drupal::service('path.alias_manager')
->getPathByAlias($alias);
if ($alias != $normal_path) {
return FALSE;
}
if (isset($parsed['path'])) {
$alias = self::addLeadingSlash($parsed['path']);
$normal_path = \Drupal::service('path.alias_manager')
->getPathByAlias($alias);
if ($alias != $normal_path) {
return FALSE;
}
}
try {
$router = \Drupal::service('router');
$route = $router
->match($alias);
} catch (\Exception $e) {
return TRUE;
}
}
return FALSE;
}
protected static function sourceIsDestination(array $row) {
if ($row['source'] == $row['redirect']) {
return TRUE;
}
try {
$parsed = parse_url($row['redirect']);
if (!isset($parsed['scheme'])) {
$row['redirect'] = 'internal:' . self::addLeadingSlash($row['redirect']);
}
$source_url = Url::fromUri('internal:/' . $row['source']);
$redirect_url = Url::fromUri($row['redirect']);
if ($source_url
->toString() == $redirect_url
->toString()) {
return TRUE;
}
$host = \Drupal::request()
->getSchemeAndHttpHost();
if ($host . $source_url
->toString() == $redirect_url
->toString()) {
return TRUE;
}
} catch (\InvalidArgumentException $e) {
}
return FALSE;
}
protected static function redirectExists(array $row) {
$parsed_url = UrlHelper::parse(trim($row['source']));
$path = isset($parsed_url['path']) ? $parsed_url['path'] : NULL;
$query = isset($parsed_url['query']) ? $parsed_url['query'] : NULL;
$hash = Redirect::generateHash($path, $query, $row['language']);
$redirects = \Drupal::entityTypeManager()
->getStorage('redirect')
->loadByProperties([
'hash' => $hash,
]);
if (!empty($redirects)) {
return $redirects;
}
return FALSE;
}
protected static function isValidLanguage($langcode) {
if (\Drupal::moduleHandler()
->moduleExists('language')) {
if (!empty($langcode) && in_array($langcode, self::validLanguages())) {
return TRUE;
}
}
return FALSE;
}
protected static function validLanguages() {
$languages = \Drupal::languageManager()
->getLanguages();
$languages = array_keys($languages);
$defaultLockedLanguages = \Drupal::languageManager()
->getDefaultLockedLanguages();
$defaultLockedLanguages = array_keys($defaultLockedLanguages);
return array_merge($languages, $defaultLockedLanguages);
}
}