diff --git a/modules/redirect_domain/config/install/redirect_domain.domains.yml b/modules/redirect_domain/config/install/redirect_domain.domains.yml new file mode 100644 index 0000000..05c0310 --- /dev/null +++ b/modules/redirect_domain/config/install/redirect_domain.domains.yml @@ -0,0 +1 @@ +domain_redirects: { } diff --git a/modules/redirect_domain/config/schema/redirect_domain.schema.yml b/modules/redirect_domain/config/schema/redirect_domain.schema.yml new file mode 100644 index 0000000..5e29a4c --- /dev/null +++ b/modules/redirect_domain/config/schema/redirect_domain.schema.yml @@ -0,0 +1,18 @@ +redirect_domain.domains: + type: config_object + label: 'Redirect domains' + mapping: + domain_redirects: + type: sequence + label: 'Domain redirects' + sequence: + type: sequence + sequence: + type: mapping + mapping: + sub_path: + type: string + label: 'Sub path' + destination: + type: string + label: 'Destination' diff --git a/modules/redirect_domain/redirect_domain.info.yml b/modules/redirect_domain/redirect_domain.info.yml new file mode 100644 index 0000000..d144543 --- /dev/null +++ b/modules/redirect_domain/redirect_domain.info.yml @@ -0,0 +1,7 @@ +name: 'Redirect Domain' +type: module +description: 'Allows users to redirect between domains.' +core: 8.x + +dependencies: + - redirect diff --git a/modules/redirect_domain/redirect_domain.links.task.yml b/modules/redirect_domain/redirect_domain.links.task.yml new file mode 100644 index 0000000..1dcee94 --- /dev/null +++ b/modules/redirect_domain/redirect_domain.links.task.yml @@ -0,0 +1,5 @@ +redirect_domain.domain_list: + title: 'Domain redirects' + base_route: redirect.list + route_name: redirect_domain.domain_list + weight: 60 diff --git a/modules/redirect_domain/redirect_domain.module b/modules/redirect_domain/redirect_domain.module new file mode 100644 index 0000000..4747119 --- /dev/null +++ b/modules/redirect_domain/redirect_domain.module @@ -0,0 +1,20 @@ +' . t('About') . ''; + $output .= '

' . t('The Redirect domain module allows users to redirect between domains.') . '

'; + $output .= '

' . t('Uses') . '

'; + $output .= '
' . t('Manage domain redirects') . '
'; + $output .= '
' . t('The domain redirect is accessed through Domain Redirects. The user can add the domain redirects through the domain redirect table which consists of the domain from which it needs to be redirected, the sub path and the complete url destination to which it needs to be redirected. The module also supports the usage of a wildcard redirecting, thus many requests can be handled with one instance of domain redirect.', [':domainlist' => Url::fromRoute('redirect_domain.domain_list')->toString()]) . '
'; + return $output; + } +} diff --git a/modules/redirect_domain/redirect_domain.routing.yml b/modules/redirect_domain/redirect_domain.routing.yml new file mode 100644 index 0000000..ad38a07 --- /dev/null +++ b/modules/redirect_domain/redirect_domain.routing.yml @@ -0,0 +1,7 @@ +redirect_domain.domain_list: + path: '/admin/config/search/redirect/domain' + defaults: + _title: 'Domain redirects' + _form: '\Drupal\redirect_domain\Form\RedirectDomainForm' + requirements: + _permission: 'administer redirects' diff --git a/modules/redirect_domain/redirect_domain.services.yml b/modules/redirect_domain/redirect_domain.services.yml new file mode 100644 index 0000000..94503c6 --- /dev/null +++ b/modules/redirect_domain/redirect_domain.services.yml @@ -0,0 +1,6 @@ +services: + redirect_domain.request_subscriber: + class: Drupal\redirect_domain\EventSubscriber\DomainRedirectRequestSubscriber + arguments: ['@config.factory', '@redirect.checker', '@path.matcher'] + tags: + - { name: event_subscriber } diff --git a/modules/redirect_domain/src/EventSubscriber/DomainRedirectRequestSubscriber.php b/modules/redirect_domain/src/EventSubscriber/DomainRedirectRequestSubscriber.php new file mode 100644 index 0000000..9f8590f --- /dev/null +++ b/modules/redirect_domain/src/EventSubscriber/DomainRedirectRequestSubscriber.php @@ -0,0 +1,121 @@ +domainConfig = $config_factory->get('redirect_domain.domains'); + $this->redirectChecker = $redirect_checker; + $this->pathMatcher = $path_matcher; + } + + /** + * Handles the domain redirect if any found. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The event to process. + */ + public function onKernelRequestCheckDomainRedirect(GetResponseEvent $event) { + $request = clone $event->getRequest(); + + if (!$this->redirectChecker->canRedirect($request)) { + return; + } + + // Redirect between domains configuration. + $domains = $this->domainConfig->get('domain_redirects'); + if (!empty($domains)) { + $host = $request->getHost(); + $path = $request->getPathInfo(); + $protocol = $request->getScheme() . '://'; + $destination = NULL; + + // Checks if there is a redirect domain in the configuration. + if (isset($domains[str_replace('.', ':', $host)])) { + foreach ($domains[str_replace('.', ':', $host)] as $item) { + if ($this->pathMatcher->matchPath($path, $item['sub_path'])) { + $destination = $item['destination']; + break; + } + } + if ($destination) { + $response = new TrustedRedirectResponse($protocol . $destination); + $event->setResponse($response); + return; + } + } + } + } + + /** + * Prior to set the response it check if we can redirect. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * The event object. + * @param \Drupal\Core\Url $url + * The Url where we want to redirect. + */ + protected function setResponse(GetResponseEvent $event, Url $url) { + $request = $event->getRequest(); + + parse_str($request->getQueryString(), $query); + $url->setOption('query', $query); + $url->setAbsolute(TRUE); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // This needs to run before RouterListener::onKernelRequest(), which has + // a priority of 32 and + // RedirectRequestSubscriber::onKernelRequestCheckRedirect(), which has + // a priority of 33. Otherwise, that aborts the request if no matching + // route is found. + $events[KernelEvents::REQUEST][] = ['onKernelRequestCheckDomainRedirect', 34]; + return $events; + } + +} diff --git a/modules/redirect_domain/src/Form/RedirectDomainForm.php b/modules/redirect_domain/src/Form/RedirectDomainForm.php new file mode 100644 index 0000000..5c40b68 --- /dev/null +++ b/modules/redirect_domain/src/Form/RedirectDomainForm.php @@ -0,0 +1,165 @@ +has('maximum_domains')) { + $form_state->set('maximum_domains', 1); + } + + $form['redirects'] = [ + '#type' => 'table', + '#tree' => TRUE, + '#header' => [ + $this->t('From domain'), + $this->t('Sub path'), + $this->t('Destination') + ], + '#prefix' => '
', + '#suffix' => '
', + ]; + + $rows = []; + // Obtain domain redirects from configuration. + if ($domain_redirects = $this->config('redirect_domain.domains')->get('domain_redirects')) { + foreach ($domain_redirects as $key => $value) { + foreach ($value as $item) { + $form['redirects'][] = [ + 'from' => [ + '#type' => 'textfield', + '#value' => str_replace(':','.',$key), + ], + 'sub_path' => [ + '#type' => 'textfield', + '#value' => $item['sub_path'], + ], + 'destination' => [ + '#type' => 'textfield', + '#value' => $item['destination'], + ], + ]; + } + } + } + + // Fields for the new domain redirects. + for ($i = 0; $i < $form_state->get('maximum_domains'); $i++) { + $form['redirects'][] = [ + 'from' => [ + '#type' => 'textfield', + ], + 'sub_path' => [ + '#type' => 'textfield', + '#value' => '/', + ], + 'destination' => [ + '#type' => 'textfield', + ], + ]; + } + + $form['add'] = [ + '#type' => 'submit', + '#value' => $this->t('Add another'), + '#submit' => ['::addAnotherSubmit'], + '#ajax' => [ + 'callback' => '::ajaxAddAnother', + 'wrapper' => 'redirect-domain-wrapper', + ], + ]; + $form['submit'] = [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Save'), + ]; + return $form; + } + + /** + * Ajax callback for adding another domain redirect. + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * The new domain redirect form part. + */ + public function ajaxAddAnother(array $form, FormStateInterface $form_state) { + return $form['redirects']; + } + + /** + * Submit callback for adding a new domain field. + */ + public function addAnotherSubmit(array $form, FormStateInterface $form_state) { + $form_state->set('maximum_domains', $form_state->get('maximum_domains') + 1); + $form_state->setRebuild(TRUE); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + if ($redirects = $form_state->getValue('redirects')) { + foreach ($redirects as $redirect) { + if (strpos($redirect['from'], '://') !== FALSE) { + $form_state->setErrorByName('redirects', t('No protocol should be included in the redirect domain.')); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $domain_redirects = []; + $domain_config = $this->config('redirect_domain.domains'); + + if ($redirects = $form_state->getValue('redirects')) { + foreach ($redirects as $redirect) { + if (!empty($redirect['from']) && !empty($redirect['destination'])) { + // Replace '.' with ':' for an eligible key. + $redirect['from'] = str_replace('.', ':', $redirect['from']); + $domain_redirects[$redirect['from']][] = [ + 'sub_path' => ltrim($redirect['sub_path'], '/') . '/', + 'destination' => $redirect['destination'] + ]; + } + } + } + $domain_config->set('domain_redirects', $domain_redirects); + $domain_config->save(); + drupal_set_message(t('The domain redirects have been saved.')); + } +} diff --git a/modules/redirect_domain/src/Tests/RedirectDomainUITest.php b/modules/redirect_domain/src/Tests/RedirectDomainUITest.php new file mode 100644 index 0000000..a8bd463 --- /dev/null +++ b/modules/redirect_domain/src/Tests/RedirectDomainUITest.php @@ -0,0 +1,63 @@ +drupalCreateUser([ + 'administer site configuration', + 'access administration pages', + 'administer redirects' + ]); + $this->drupalLogin($user); + $this->drupalGet('/admin/config/search/redirect/domain'); + + // Assert that there are 2 domain redirect fields. + $this->assertFieldByName('redirects[0][from]'); + $this->assertFieldByName('redirects[0][sub_path]'); + $this->assertFieldByName('redirects[0][destination]'); + + // Add another field for new domain redirect. + $this->drupalPostAjaxForm(NULL, [], ['op' => t('Add another')]); + + // Add two new domain redirects, one without sub path. + $edit = [ + 'redirects[0][from]' => 'foo.example.org', + 'redirects[0][destination]' => 'www.example.org/foo', + 'redirects[1][from]' => 'bar.example.org', + 'redirects[1][sub_path]' => '', + 'redirects[1][destination]' => 'www.example.org/bar', + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + + // Check the new domain redirects. + $this->assertFieldByName('redirects[0][from]', 'foo.example.org'); + $this->assertFieldByName('redirects[0][destination]', 'www.example.org/foo'); + $this->assertFieldByName('redirects[1][from]', 'bar.example.org'); + $this->assertFieldByName('redirects[1][destination]', 'www.example.org/bar'); + + // Ensure that the sub paths are correct. + $this->assertFieldByName('redirects[0][sub_path]', '/'); + $this->assertFieldByName('redirects[1][sub_path]', '/'); + } +} diff --git a/modules/redirect_domain/tests/src/Unit/DomainRedirectRequestSubscriberTest.php b/modules/redirect_domain/tests/src/Unit/DomainRedirectRequestSubscriberTest.php new file mode 100644 index 0000000..ab942ac --- /dev/null +++ b/modules/redirect_domain/tests/src/Unit/DomainRedirectRequestSubscriberTest.php @@ -0,0 +1,138 @@ + [ + 'domain_redirects' => [ + 'foo:com' => [ + [ + 'sub_path' => '/fixedredirect', + 'destination' => 'bar.com/fixedredirect' + ], + [ + 'sub_path' => '/*', + 'destination' => 'bar.com/example' + ], + ], + 'example:com' => [ + [ + 'sub_path' => '/foo/*/bar', + 'destination' => 'example.com/bar/foo', + ] + ], + 'simpleexample:com' => [ + [ + 'sub_path' => '/redirect', + 'destination' => 'redirected.com/redirect', + ] + ], + ] + ], + 'system.site' => [ + 'page.front' => '/', + ], + ]; + + // Create a mock redirect checker. + $checker = $this->getMockBuilder(RedirectChecker::class) + ->disableOriginalConstructor() + ->getMock(); + $checker->expects($this->any()) + ->method('canRedirect') + ->will($this->returnValue(TRUE)); + + // Set up the configuration for the requested domain. + $config_factory = $this->getConfigFactoryStub($data); + + // Create a mock path matcher. + $route_match = $this->getMock(RouteMatchInterface::class); + $path_matcher = new PathMatcher($config_factory, $route_match); + + $subscriber = new DomainRedirectRequestSubscriber( + $config_factory, + $checker, + $path_matcher + ); + + // Make a request to the urls from the data provider and get the response. + $event = $this->getGetResponseEventStub($request_url, http_build_query([])); + + // Run the main redirect method. + $subscriber->onKernelRequestCheckDomainRedirect($event); + + // Assert the expected response from the data provider. + if ($response_url) { + $this->assertTrue($event->getResponse() instanceof RedirectResponse); + $response = $event->getResponse(); + // Make sure that the response is properly redirected. + $this->assertEquals($response_url, $response->getTargetUrl()); + $this->assertEquals(302, $response->getStatusCode()); + } + else { + $this->assertNull($event->getResponse()); + } + } + + /** + * Gets response event object. + * + * @param $path_info + * The path info. + * @param $query_string + * The query string in the url. + * + * @return GetResponseEvent + * The response for the request. + */ + protected function getGetResponseEventStub($path_info, $query_string) { + $request = Request::create($path_info . '?' . $query_string, 'GET', [], [], [], ['SCRIPT_NAME' => 'index.php']); + + $http_kernel = $this->getMockBuilder(HttpKernelInterface::class) + ->getMock(); + return new GetResponseEvent($http_kernel, $request, 'test'); + } + + /** + * Data provider for the domain redirects. + * + * @return array + * An array of requests and expected responses for the redirect domains. + */ + public function providerDomains() { + $datasets = []; + $datasets[] = ['http://foo.com/example', 'http://bar.com/example']; + $datasets[] = ['http://example.com/foo/test/bar', 'http://example.com/bar/foo']; + $datasets[] = ['http://simpleexample.com/redirect', 'http://redirected.com/redirect']; + $datasets[] = ['http://nonexisting.com', NULL]; + $datasets[] = ['http://simpleexample.com/wrongpath', NULL]; + $datasets[] = ['http://foo.com/fixedredirect', 'http://bar.com/fixedredirect']; + return $datasets; + } +}