diff --git a/config/install/domain_301_redirect.settings.yml b/config/install/domain_301_redirect.settings.yml index df519d6..dc5eda3 100644 --- a/config/install/domain_301_redirect.settings.yml +++ b/config/install/domain_301_redirect.settings.yml @@ -1,7 +1,9 @@ enabled: 0 -check_period: 60 * 60 * 3 +check_period: 10800 domain_check_retries: 3 domain_check_reenable: true disabled_by_check: false -enabled: 0 last_checked: 0 +loop_max_redirects: 3 +applicability: 0 +pages: '' diff --git a/config/schema/domain_301_redirect.schema.yml b/config/schema/domain_301_redirect.schema.yml new file mode 100644 index 0000000..519709b --- /dev/null +++ b/config/schema/domain_301_redirect.schema.yml @@ -0,0 +1,37 @@ +# Schema for the configuration files of the Domain 301 Redirect module. + +domain_301_redirect.settings: + type: config_object + label: 'Domain 301 redirect settings' + mapping: + enabled: + type: integer + label: 'Enable to start 301 redirects to the domain' + domain: + type: string + label: 'Domain' + check_period: + type: integer + label: 'Selects the period between domain' + domain_check_retries: + type: integer + label: 'Number of times to check domain availability' + domain_check_reenable: + type: boolean + label: 'Re-enable domain redirection' + disabled_by_check: + type: boolean + label: 'Disable domain redirection' + last_checked: + type: integer + label: 'Redirect is disabled by cron' + applicability: + type: path_settings + label: 'Specify pages' + mapping: + applicability: + type: integer + label: 'Redirect on specific pages' + pages: + type: string + label: 'Pages to include or exclude from redirection' diff --git a/domain_301_redirect.links.menu.yml b/domain_301_redirect.links.menu.yml new file mode 100644 index 0000000..ae8972d --- /dev/null +++ b/domain_301_redirect.links.menu.yml @@ -0,0 +1,5 @@ +domain_301_redirect.settings: + title: 'Domain 301 Redirect' + parent: system.admin_config_search + description: 'Configure Domain 301 Redirect.' + route_name: domain_301_redirect.settings diff --git a/domain_301_redirect.module b/domain_301_redirect.module index f614170..2da98ad 100644 --- a/domain_301_redirect.module +++ b/domain_301_redirect.module @@ -17,3 +17,42 @@ function domain_301_redirect_help($route_name, RouteMatchInterface $route_match) return ['#markup' => $output]; } } + +/** + * Implements hook_cron(). + */ +function domain_301_redirect_cron() { + $config = $config = \Drupal::config('domain_301_redirect.settings'); + $enabled = $config->get('enabled'); + $check_period = $config->get('check_period'); + $last_checked = $config->get('last_checked'); + $reenable = $config->get('check_reenable'); + $disabled_by_check = $config->get('disabled_by_check'); + + // If the redirect is enabled (or has been previously disabled) and we are + // checking for domain availability on cron, then attempt to request the test + // url using the redirect domain. + if (($enabled || ($disabled_by_check && $reenable)) && !empty($check_period) && $last_checked < time() - $check_period) { + $domain = variable_get('domain_301_redirect_domain', ''); + if (!preg_match('|^https?://|', $domain)) { + $domain = 'http://' . $domain; + } + $domain_parts = parse_url($domain); + $domain = $domain_parts['scheme'] . '://' . $domain_parts['host']; + if (!domain_301_redirect_check_domain($domain)) { + variable_set('domain_301_redirect_enabled', 0); + variable_set('domain_301_redirect_disabled_by_check', TRUE); + watchdog('Domain 301 Redirect', 'The domain %domain no longer points to this site. Domain 301 redirection was disabled.', array('%domain' => $domain), WATCHDOG_ERROR); + } + else { + watchdog('Domain 301 Redirect', 'Domain 301 redirect check passed.'); + // If the redirect was disabled by cron, and it has now passed, re-enable it. + if (!$enabled && $reenable && $disabled_by_check) { + variable_set('domain_301_redirect_enabled', 1); + variable_set('domain_301_redirect_disabled_by_check', FALSE); + watchdog('Domain 301 Redirect', 'The domain %domain has become available again. Domain 301 redirection was re-enabled.', array('%domain' => $domain), WATCHDOG_ERROR); + } + } + variable_set('domain_301_redirect_last_checked', time()); + } +} diff --git a/domain_301_redirect.permissions.yml b/domain_301_redirect.permissions.yml new file mode 100644 index 0000000..59ed00a --- /dev/null +++ b/domain_301_redirect.permissions.yml @@ -0,0 +1,4 @@ +bypass domain 301 redirect: + title: 'Bypass domain 301 redirect' + description: 'If this permission is set, the user will not be 301 redirected if the domain does not match the main domain.' + restrict access: true diff --git a/domain_301_redirect.services.yml b/domain_301_redirect.services.yml new file mode 100644 index 0000000..d9c6d61 --- /dev/null +++ b/domain_301_redirect.services.yml @@ -0,0 +1,11 @@ +services: + domain_301_redirect.event_subscriber: + class: Drupal\domain_301_redirect\EventSubscriber\DomainRedirectEventSubscriber + arguments: ['@config.factory', '@request_stack', '@current_user'] + tags: + - { name: event_subscriber } + + domain_301_redirect.manager: + class: Drupal\domain_301_redirect\Domain301RedirectManager + tags: + - {name: domain_301_redirect_manager} diff --git a/src/Domain301RedirectManager.php b/src/Domain301RedirectManager.php new file mode 100644 index 0000000..3351e53 --- /dev/null +++ b/src/Domain301RedirectManager.php @@ -0,0 +1,82 @@ +config = $config_factory->get('domain_301_redirect.settings'); + } + + /** + * {@inheritdoc} + */ + public function checkRedirectLoop($domain) { + // Get host from configured domain. + $redirect_host = Unicode::strtolower(parse_url($domain, PHP_URL_HOST)); + $host = Unicode::strtolower(parse_url($GLOBALS['base_url'], PHP_URL_HOST)); + + // Redirecting back to this site actually is actively ignored in hook_init, + // so it makes no sense to allow users to set this as a value. On the other + // hand when the admin is on the redirected domain he should still be able + // to alter other settings without first disabling redirection. So let's + // just accept the current host. + if ($redirect_host == $host) { + return FALSE; + } + + $redirect_loop = FALSE; + $redirects_to_check = $this->config->get('loop_max_redirects'); + $checked = 0; + + // Make a request to the domain that is being configured, following a + // configured number of redirects. This has to be done individually, because + // if checking all 3 levels at once, we might happen to get the wrong one + // back (if the redirect loop has multiple levels). + do { + $client = \Drupal::httpClient(); + $args = [ + 'max_redirects' => 0, + 'method' => 'HEAD', + 'headers' => [ + 'Accept' => 'text/plain', + ], + ]; + $response = $client->get($domain, $args); + + if (!empty($response->redirect_url)) { + // Request target for the next request loop. + $domain = $response->redirect_url; + // Check if any host names match the redirect host name. + $location_host = Unicode::strtolower(parse_url($response->redirect_url, PHP_URL_HOST)); + if ($redirect_host == $location_host || $host == $location_host) { + $redirect_loop = TRUE; + } + } + $checked++; + // Don't check the redirect code, as it's possible there may be another + // redirect service in operation that does not use 301. + } while (!$redirect_loop && !empty($response->redirect_url) && $checked < $redirects_to_check); + + return $redirect_loop; + } + +} diff --git a/src/Domain301RedirectManagerInterface.php b/src/Domain301RedirectManagerInterface.php new file mode 100644 index 0000000..7be044e --- /dev/null +++ b/src/Domain301RedirectManagerInterface.php @@ -0,0 +1,18 @@ +configFactory = $config_factory; + $this->request = $request_stack->getCurrentRequest(); + $this->userAccount = $user_account; + } + + /** + * {@inheritdoc} + */ + static public function getSubscribedEvents() { + $events['kernel.request'] = ['requestHandler']; + + return $events; + } + + /** + * This method is called whenever the kernel.request event is dispatched. + * + * @todo Needs a service which will handle the exclusion/inclusion of + * the mentioned path/page. + * + * @param GetResponseEvent $event + * The response event. + */ + public function requestHandler(GetResponseEvent $event) { + // If user has 'bypass' permission, then no need to process further. + if ($this->userAccount->hasPermission('bypass domain 301 redirect')) { + return; + } + + // Get config settings of domain_301_redirect module. + $domain_config = $this->configFactory->get('domain_301_redirect.settings')->getRawData(); + // If domain redirection is not enabled, then no need to process further. + if (!$domain_config['enabled']) { + return; + } + + // If domain doesn't contain http/https, then add those to domain. + if (!preg_match('|^https?://|', $domain_config['domain'])) { + $domain_config['domain'] = 'http://' . $domain_config['domain']; + } + + // Parse the domain to get various settings like port. + $domain_parts = parse_url($domain_config['domain']); + $parsed_domain = $domain_parts['host']; + $parsed_domain .= !empty($domain_parts['port']) ? ':' . $domain_parts['port'] : ''; + + // If we're not on the same host, the user has access and this page isn't + // an exception, redirect. + if (($parsed_domain != $this->request->server->get('HTTP_HOST'))) { + $uri = $this->request->getRequestUri(); + $response = new RedirectResponse($domain_config['domain'] . $uri, 301); + $response->send(); + } + } + +} diff --git a/src/Form/Domain301RedirectConfigForm.php b/src/Form/Domain301RedirectConfigForm.php index 440bac3..5ad471b 100644 --- a/src/Form/Domain301RedirectConfigForm.php +++ b/src/Form/Domain301RedirectConfigForm.php @@ -64,7 +64,7 @@ class Domain301RedirectConfigForm extends ConfigFormBase { $form['check_period'] = [ '#type' => 'select', '#title' => $this->t('Domain Check'), - '#description' => $this->t('This option selects the period between domain validation checks. If the Domain no longer points to this site, Domain 301 Redirection will be disabled.'), + '#description' => $this->t('This option selects the period between domain validation checks. If the Domain no longer points to this site, Domain 301 Redirection will be disabled. *NOT IMPLEMENTED*'), '#options' => [ 0 => $this->t('Disabled'), 3600 => $this->t('1 hour'), @@ -79,14 +79,14 @@ class Domain301RedirectConfigForm extends ConfigFormBase { $form['domain_check_retries'] = [ '#type' => 'select', '#title' => $this->t('Domain retries'), - '#description' => $this->t('Number of times to check domain availability before disabling redirects.'), + '#description' => $this->t('Number of times to check domain availability before disabling redirects. *NOT IMPLEMENTED*'), '#options' => [1 => 1, 2 => 2, 3 => 3], '#default_value' => $config->get('domain_check_retries'), ]; $form['domain_check_reenable'] = [ '#type' => 'checkbox', '#title' => $this->t('Re-enable domain redirection'), - '#description' => $this->t('Turn domain redirection on when the domain becomes available.'), + '#description' => $this->t('Turn domain redirection on when the domain becomes available. *NOT IMPLEMENTED*'), '#default_value' => $config->get('domain_check_reenable'), ]; @@ -94,16 +94,16 @@ class Domain301RedirectConfigForm extends ConfigFormBase { $form['applicability']['path'] = [ '#type' => 'fieldset', '#title' => $this->t('Pages'), - '#collapsible' => TRUE, - '#collapsed' => FALSE, + '#description' => $this->t('*NOT IMPLEMENTED*'), '#weight' => 0, + '#open' => TRUE, ]; $options = [ - BLOCK_VISIBILITY_NOTLISTED => $this->t('All pages except those listed'), - BLOCK_VISIBILITY_LISTED => $this->t('Only the listed pages'), + 0 => $this->t('All pages except those listed'), + 1 => $this->t('Only the listed pages'), ]; - $description = $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", [ + $description = $this->t('Specify pages by using their paths. Enter one path per line. The \'*\' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.', [ '%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '',