diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php index 62ab2d6..ab34fc5 100644 --- a/core/lib/Drupal/Core/Access/AccessManager.php +++ b/core/lib/Drupal/Core/Access/AccessManager.php @@ -2,7 +2,7 @@ /** * @file - * Contains Drupal\Core\Access\AccessManager. + * Contains \Drupal\Core\Access\AccessManager. */ namespace Drupal\Core\Access; diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module index 66a80ec..8b24b6c 100644 --- a/core/modules/contact/contact.module +++ b/core/modules/contact/contact.module @@ -80,8 +80,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, ); @@ -95,18 +93,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()), \Drupal::currentUser()); -} - -/** * 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 061bd10..0000000 --- a/core/modules/contact/contact.pages.inc +++ /dev/null @@ -1,113 +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(), - )); - $form = \Drupal::entityManager()->getForm($message); - $form['#title'] = String::checkPlain($category->label()); - return $form; -} - -/** - * 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(); - } - - $message = entity_create('contact_message', array( - 'recipient' => $recipient, - 'category' => 'personal', - )); - $form = \Drupal::entityManager()->getForm($message); - $form['#title'] = t('Contact @username', array('@username' => $recipient->getUsername())); - return $form; -} - -/** - * 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 f12d67e..10ba2db 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' 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,7 +16,7 @@ contact.category_list: contact.category_add: path: '/admin/structure/contact/add' defaults: - _entity_form: contact_category.add + _entity_form: 'contact_category.add' _title: 'Add category' requirements: _permission: 'administer contact forms' @@ -24,9 +24,9 @@ contact.category_add: 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' @@ -43,7 +43,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.view' 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..59ee2c7 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,17 @@ class CategoryAccessController extends EntityAccessController { * {@inheritdoc} */ public function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $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'; + if ($operation == 'view') { + // Do not allow access personal category via site-wide route. + return $account->hasPermission('access site-wide contact form') && $entity->id() !== 'personal'; + } + elseif ($operation == 'delete' || $operation == 'update') { + // 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'; } 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 d1eb806..86d6f63 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,9 +7,11 @@ namespace Drupal\contact\Plugin\views\field; +use Drupal\Core\Access\AccessManager; use Drupal\Core\Entity\EntityInterface; use Drupal\user\Plugin\views\field\Link; use Drupal\views\ResultRow; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines a field that links to the user contact page, if access is permitted. @@ -21,6 +23,64 @@ class ContactLink extends Link { /** + * The access manager. + * + * @var \Drupal\Core\Access\AccessManager + */ + protected $accessManager; + + /** + * Current user object. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Gets the current active user. + * + * @todo: https://drupal.org/node/2105123 put this method in + * \Drupal\Core\Plugin\PluginBase instead. + * + * @return \Drupal\Core\Session\AccountInterface + */ + protected function currentUser() { + if (!$this->currentUser) { + $this->currentUser = \Drupal::currentUser(); + } + return $this->currentUser; + } + + /** + * Constructs a ContactLink 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\AccessManager $access_manager + * The access manager. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, AccessManager $access_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->accessManager = $access_manager; + } + + /** + * {@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_manager') + ); + } + + /** * {@inheritdoc} */ public function buildOptionsForm(&$form, &$form_state) { @@ -50,15 +110,12 @@ protected function renderLink(EntityInterface $entity, ResultRow $values) { // Check access when we pull up the user account so we know // if the user has made the contact page available. - $uid = $entity->id(); - - $path = "user/$uid/contact"; - if (!_contact_personal_tab_access($entity)) { + if (!$this->accessManager->checkNamedRoute('contact.personal_page', array('user' => $entity->id()), $this->currentUser())) { return; } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = $path; + $this->options['alter']['path'] = "user/{$entity->id()}/contact"; $title = t('Contact %user', array('%user' => $entity->name->value)); $this->options['alter']['attributes'] = array('title' => $title); diff --git a/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php b/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php index 1c69c2e..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.