diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module index 4cdea75..6a61e05 100644 --- a/core/modules/contact/contact.module +++ b/core/modules/contact/contact.module @@ -86,8 +86,6 @@ function contact_menu() { ); $items['contact/%contact_category'] = array( 'title' => 'Contact category form', - 'title callback' => 'entity_page_label', - 'title arguments' => array(1), 'route_name' => 'contact.site_page_category', 'type' => MENU_VISIBLE_IN_BREADCRUMB, ); @@ -101,18 +99,6 @@ function contact_menu() { } /** - * Access callback: Checks access for a user's personal contact form. - * - * @param $account - * The user object of the user whose contact form is being requested. - * - * @see contact_menu() - */ -function _contact_personal_tab_access(UserInterface $account) { - return \Drupal::service('access_manager')->checkNamedRoute('contact.personal_page', array('user' => $account->id())); -} - -/** * Implements hook_entity_bundle_info(). */ function contact_entity_bundle_info() { diff --git a/core/modules/contact/contact.pages.inc b/core/modules/contact/contact.pages.inc deleted file mode 100644 index 491bb30..0000000 --- a/core/modules/contact/contact.pages.inc +++ /dev/null @@ -1,110 +0,0 @@ -get('default_category'); - if (isset($categories[$default_category])) { - $category = $categories[$default_category]; - } - // If there are no categories, do not display the form. - else { - if (user_access('administer contact forms')) { - drupal_set_message(t('The contact form has not been configured. Add one or more categories to the form.', array('@add' => url('admin/structure/contact/add'))), 'error'); - return array(); - } - else { - throw new NotFoundHttpException(); - } - } - } - else if ($category->id() == 'personal') { - throw new NotFoundHttpException(); - } - $message = entity_create('contact_message', array( - 'category' => $category->id(), - )); - return \Drupal::entityManager()->getForm($message); -} - -/** - * Page callback: Form constructor for the personal contact form. - * - * @param $recipient - * The account for which a personal contact form should be generated. - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * - * @see contact_menu() - * @see contact_personal_form_submit() - * - * @ingroup forms - * - * @deprecated Use \Drupal\contact\Controller\ContactController::contactPersonalPage() - */ -function contact_personal_page($recipient) { - global $user; - - // Check if flood control has been activated for sending e-mails. - if (!user_access('administer contact forms') && !user_access('administer users')) { - contact_flood_control(); - } - - drupal_set_title(t('Contact @username', array('@username' => user_format_name($recipient))), PASS_THROUGH); - - $message = entity_create('contact_message', array( - 'recipient' => $recipient, - 'category' => 'personal', - )); - return \Drupal::entityManager()->getForm($message); -} - -/** - * Throws an exception if the current user is not allowed to submit a contact form. - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * - * @see contact_site_page() - * @see contact_personal_page() - */ -function contact_flood_control() { - $config = \Drupal::config('contact.settings'); - $limit = $config->get('flood.limit'); - $interval = $config->get('flood.interval'); - if (!\Drupal::service('flood')->isAllowed('contact', $limit, $interval)) { - drupal_set_message(t("You cannot send more than %limit messages in @interval. Try again later.", array( - '%limit' => $limit, - '@interval' => format_interval($interval), - )), 'error'); - throw new AccessDeniedHttpException(); - } -} diff --git a/core/modules/contact/contact.routing.yml b/core/modules/contact/contact.routing.yml index c616c25..fea384a 100644 --- a/core/modules/contact/contact.routing.yml +++ b/core/modules/contact/contact.routing.yml @@ -1,10 +1,10 @@ contact.category_delete: - path: 'admin/structure/contact/manage/{contact_category}/delete' + path: '/admin/structure/contact/manage/{contact_category}/delete' defaults: - _entity_form: contact_category.delete + _entity_form: 'contact_category.delete' _title: 'Delete' requirements: - _entity_access: contact_category.delete + _entity_access: 'contact_category.delete' contact.category_list: path: '/admin/structure/contact' @@ -16,16 +16,16 @@ contact.category_list: contact.category_add: path: '/admin/structure/contact/add' defaults: - _entity_form: contact_category.add + _entity_form: 'contact_category.add' requirements: _permission: 'administer contact forms' contact.category_edit: path: '/admin/structure/contact/manage/{contact_category}' defaults: - _entity_form: contact_category.edit + _entity_form: 'contact_category.edit' requirements: - _entity_access: contact_category.update + _entity_access: 'contact_category.update' contact.site_page: path: '/contact' @@ -42,7 +42,7 @@ contact.site_page_category: _title: 'Contact category form' _content: '\Drupal\contact\Controller\ContactController::contactSitePage' requirements: - _permission: 'access site-wide contact form' + _entity_access: 'contact_category.page' contact.personal_page: path: '/user/{user}/contact' diff --git a/core/modules/contact/lib/Drupal/contact/CategoryAccessController.php b/core/modules/contact/lib/Drupal/contact/CategoryAccessController.php index ba0bab7..45b9b94 100644 --- a/core/modules/contact/lib/Drupal/contact/CategoryAccessController.php +++ b/core/modules/contact/lib/Drupal/contact/CategoryAccessController.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityAccessController; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; + /** * Defines an access controller for the contact category entity. * @@ -21,12 +22,18 @@ class CategoryAccessController extends EntityAccessController { * {@inheritdoc} */ public function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { + $this->prepareUser($account); if ($operation == 'delete' || $operation == 'update') { - // Do not allow delete 'personal' category used for personal contact form. - return user_access('administer contact forms', $account) && $entity->id() !== 'personal'; + // Do not allow the 'personal' category to be deleted, as it's used for + // the personal contact form. + return $account->hasPermission('administer contact forms') && $entity->id() !== 'personal'; + } + elseif ($operation == 'page') { + // Do not allow access personal category via site-wide route. + return $account->hasPermission('access site-wide contact form') && $entity->id() !== 'personal'; } else { - return user_access('administer contact forms', $account); + return $account->hasPermission('administer contact forms'); } } diff --git a/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php b/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php index cb6c62d..ff5f71d 100644 --- a/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php +++ b/core/modules/contact/lib/Drupal/contact/Controller/ContactController.php @@ -7,28 +7,135 @@ namespace Drupal\contact\Controller; +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Flood\FloodInterface; use Drupal\contact\CategoryInterface; use Drupal\user\UserInterface; +use Drupal\Component\Utility\String; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Controller routines for contact routes. */ -class ContactController { +class ContactController extends ControllerBase implements ContainerInjectionInterface { /** - * @todo Remove contact_site_page(). + * The flood service. + * + * @var \Drupal\Core\Flood\FloodInterface + */ + protected $flood; + + /** + * Constructs a ContactController object. + * + * @param \Drupal\Core\Flood\FloodInterface $flood + * The flood service. + */ + public function __construct(FloodInterface $flood) { + $this->flood = $flood; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('flood') + ); + } + + /** + * Presents the site-wide contact form. + * + * @param \Drupal\contact\CategoryInterface $contact_category + * The contact category to use. + * + * @return array + * The form as render array as expected by drupal_render(). + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * Exception is thrown when user tries to access non existing default + * contact category form. */ public function contactSitePage(CategoryInterface $contact_category = NULL) { - module_load_include('pages.inc', 'contact'); - return contact_site_page($contact_category); + // Check if flood control has been activated for sending e-mails. + if (!$this->currentUser()->hasPermission('administer contact forms')) { + $this->contactFloodControl(); + } + + // Use the default category if no category has been passed. + if (empty($contact_category)) { + $contact_category = $this->entityManager() + ->getStorageController('contact_category') + ->load($this->config('contact.settings')->get('default_category')); + // If there are no categories, do not display the form. + if (empty($contact_category)) { + if ($this->currentUser()->hasPermission('administer contact forms')) { + drupal_set_message($this->t('The contact form has not been configured. Add one or more categories to the form.', array( + '@add' => $this->urlGenerator()->generateFromRoute('contact.category_add'))), 'error'); + return array(); + } + else { + throw new NotFoundHttpException(); + } + } + } + + $message = $this->entityManager() + ->getStorageController('contact_message') + ->create(array( + 'category' => $contact_category->id(), + )); + + $form = $this->entityManager()->getForm($message); + $form['#title'] = String::checkPlain($contact_category->label()); + return $form; } /** - * @todo Remove contact_personal_page(). + * Form constructor for the personal contact form. + * + * @param \Drupal\user\UserInterface $user + * The account for which a personal contact form should be generated. + * + * @return array + * The personal contact form as render array as expected by drupal_render(). */ public function contactPersonalPage(UserInterface $user) { - module_load_include('pages.inc', 'contact'); - return contact_personal_page($user); + // Check if flood control has been activated for sending e-mails. + if (!$this->currentUser()->hasPermission('administer contact forms') && !$this->currentUser()->hasPermission('administer users')) { + $this->contactFloodControl(); + } + + $message = $this->entityManager()->getStorageController('contact_message')->create(array( + 'category' => 'personal', + 'recipient' => $user->id(), + )); + + $form = $this->entityManager()->getForm($message); + $form['#title'] = $this->t('Contact @username', array('@username' => $user->getUsername())); + return $form; + } + + /** + * Throws an exception if the current user triggers flood control. + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + protected function contactFloodControl() { + $limit = $this->config('contact.settings')->get('flood.limit'); + $interval = $this->config('contact.settings')->get('flood.interval'); + if (!$this->flood->isAllowed('contact', $limit, $interval)) { + drupal_set_message($this->t('You cannot send more than %limit messages in @interval. Try again later.', array( + '%limit' => $limit, + '@interval' => format_interval($interval), + )), 'error'); + throw new AccessDeniedHttpException(); + } } } diff --git a/core/modules/contact/lib/Drupal/contact/Plugin/views/field/ContactLink.php b/core/modules/contact/lib/Drupal/contact/Plugin/views/field/ContactLink.php index e1943d5..97f4dc1 100644 --- a/core/modules/contact/lib/Drupal/contact/Plugin/views/field/ContactLink.php +++ b/core/modules/contact/lib/Drupal/contact/Plugin/views/field/ContactLink.php @@ -7,10 +7,14 @@ namespace Drupal\contact\Plugin\views\field; -use Drupal\Core\Entity\EntityInterface; use Drupal\Component\Annotation\PluginID; +use Drupal\Core\Access\StaticAccessCheckInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Routing\RouteProviderInterface; use Drupal\user\Plugin\views\field\Link; use Drupal\views\ResultRow; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; /** * Defines a field that links to the user contact page, if access is permitted. @@ -22,6 +26,53 @@ class ContactLink extends Link { /** + * The access manager for contact.personal_page route. + * + * @var \Drupal\Core\Access\StaticAccessCheckInterface + */ + protected $accessChecker; + + /** + * The router provider interface. + * + * @var \Drupal\Core\Routing\RouteProviderInterface + */ + protected $routeProvider; + + /** + * Constructs a Drupal\Component\Plugin\PluginBase object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Access\StaticAccessCheckInterface $access_checker + * The access manager for contact.personal_page route. + * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider + * The router provider interface. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, StaticAccessCheckInterface $access_checker, RouteProviderInterface $route_provider) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->accessChecker = $access_checker; + $this->routeProvider = $route_provider; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('access_check.contact_personal'), + $container->get('router.route_provider') + ); + } + + /** * {@inheritdoc} */ public function buildOptionsForm(&$form, &$form_state) { @@ -54,7 +105,10 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) { $uid = $entity->id(); $path = "user/$uid/contact"; - if (!_contact_personal_tab_access($entity)) { + $request = Request::create($path); + $request->attributes->set('user', $entity); + $route = $this->routeProvider->getRouteByName('contact.personal_page'); + if ($this->accessChecker->access($route, $request) !== StaticAccessCheckInterface::ALLOW) { return; } diff --git a/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php b/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php index e974ec6..2c95d3b 100644 --- a/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php +++ b/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php @@ -94,6 +94,8 @@ function testPersonalContactAccess() { $this->drupalLogin($this->web_user); $this->drupalGet('user/' . $this->admin_user->id() . '/contact'); $this->assertResponse(200); + // Check the page title is properly displayed. + $this->assertText(t('Contact @username', array('@username' => $this->admin_user->getUsername()))); // Test denied access to admin user's own contact form. $this->drupalLogout(); diff --git a/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php b/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php index 8a9e210..b8ae287 100644 --- a/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php +++ b/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php @@ -90,6 +90,9 @@ function testSiteWideContact() { $this->drupalGet('contact'); $this->assertResponse(200); $this->assertText(t('The contact form has not been configured.')); + // Test access personal category via site-wide contact page. + $this->drupalGet('contact/personal'); + $this->assertResponse(403); // Add categories. // Test invalid recipients. @@ -126,6 +129,9 @@ function testSiteWideContact() { $this->assertEqual($config['reply'], $reply); $this->assertNotEqual($id, \Drupal::config('contact.settings')->get('default_category')); $this->assertRaw(t('Category %label has been updated.', array('%label' => $label))); + // Ensure the label is displayed on the contact page for this category. + $this->drupalGet('contact/' . $id); + $this->assertText($label); // Reset the category back to be the default category. \Drupal::config('contact.settings')->set('default_category', $id)->save();