You are here

ack_menu.test in Access Control Kit 7

Tests for the ACK menu module.

File

ack_menu/ack_menu.test
View source
<?php

/**
 * @file
 * Tests for the ACK menu module.
 */

/**
 * Tests the menu access functions.
 */
class AckMenuAccessTest extends DrupalWebTestCase {

  /**
   * A user with administrative control over all menus.
   *
   * @var object
   */
  protected $menuAdmin;

  /**
   * A user with administrative control over scheme menus.
   *
   * @var object
   */
  protected $ackAdmin;

  /**
   * An ACK-enabled role that allows managing menu items.
   *
   * @var object
   */
  protected $ackRole;

  /**
   * A user to whom access will be granted during the test.
   *
   * @var object
   */
  protected $ackUser;

  /**
   * A user with no menu-related access.
   *
   * @var object
   */
  protected $noAccessUser;

  /**
   * An array of menu items for testing user access.
   *
   * @var array
   */
  protected $items;

  /**
   * The machine name of the test access scheme.
   *
   * @var string
   */
  protected $schemeMachineName;

  /**
   * Implements getInfo(), required method for SimpleTest.
   */
  public static function getInfo() {
    return array(
      'name' => 'ACK menu access',
      'description' => 'Tests controlling access to menu items.',
      'group' => 'Access control kit',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  public function setUp() {
    parent::setUp(array(
      'ack_menu',
    ));

    // Create and log in our scheme menu admin.
    $this->ackAdmin = $this
      ->drupalCreateUser(array(
      'administer access schemes',
      'administer access grants',
      'administer ack_menu',
      'create article content',
      'edit own article content',
    ));
    $this
      ->drupalLogin($this->ackAdmin);

    // Create the test role.
    $rid = $this
      ->drupalCreateRole(array(
      'ack manage menu links',
      'create article content',
      'edit own article content',
    ));
    $this->ackRole = user_role_load($rid);

    // Create a user account for use in access grants.
    $this->ackUser = $this
      ->drupalCreateUser(array(
      'access content',
    ));

    // Add the user to the test role.
    db_insert('users_roles')
      ->fields(array(
      'uid' => $this->ackUser->uid,
      'rid' => $this->ackRole->rid,
    ))
      ->execute();
    $pass_raw = $this->ackUser->pass_raw;
    $this->ackUser = user_load($this->ackUser->uid, TRUE);
    $this->ackUser->pass_raw = $pass_raw;

    // Create a user with no menu access.
    $this->noAccessUser = $this
      ->drupalCreateUser(array(
      'access content',
      'create article content',
      'edit own article content',
    ));

    // Create a menu administrator.
    $this->menuAdmin = $this
      ->drupalCreateUser(array(
      'access content',
      'administer menu',
      'administer permissions',
      'create article content',
      'edit own article content',
    ));
  }

  /**
   * Utility function to set up the access scheme.
   */
  public function setUpScheme() {

    // Create a simple user-based scheme.
    $this->schemeMachineName = drupal_strtolower($this
      ->randomName());
    $name = $this
      ->randomName();
    $edit = array(
      'name' => $name,
      'machine_name' => $this->schemeMachineName,
      'roles[' . $this->ackRole->rid . ']' => TRUE,
    );
    $this
      ->drupalPost('admin/structure/access/add/user', $edit, 'Save access scheme and continue');

    // Attach the menu map handler.
    $edit = array(
      'handlers[menu_link][handler]' => 'AckMenuMap',
      'handlers[menu_link][AckMenuMap][menus][navigation]' => TRUE,
    );
    $this
      ->drupalPost(NULL, $edit, 'Save access scheme');
    $this
      ->assertText(t('Updated access scheme @name', array(
      '@name' => $name,
    )), 'Access scheme configured.');
  }

  /**
   * Utility function to set up a menu link access test.
   */
  public function setUpMenu() {
    $this
      ->setUpScheme();

    // Create the test menu items.
    $this->items = array();
    $keys = array(
      'not mapped',
      'mapped top link',
      'mapped child link',
      'not mapped child link',
      'no access child link',
      'no access grandchild link',
      'no access top link',
      'mapped child link with no access parent',
    );
    foreach ($keys as $key) {
      $item = array(
        'link_path' => 'node',
        'link_title' => $this
          ->randomName(),
        'menu_name' => 'navigation',
      );
      switch ($key) {
        case 'mapped child link':
        case 'not mapped child link':
        case 'no access child link':
          $item['plid'] = $this->items['mapped top link']['mlid'];
          break;
        case 'no access grandchild link':
          $item['plid'] = $this->items['no access child link']['mlid'];
          break;
        case 'mapped child link with no access parent':
          $item['plid'] = $this->items['no access top link']['mlid'];
          break;
        default:
          $item['plid'] = NULL;
      }
      $mlid = menu_link_save($item);
      $this->items[$key] = menu_link_load($mlid);
    }
    menu_cache_clear_all();

    // Map the menu items.
    $element = 'AckMenuMap[' . $this->schemeMachineName . ']';
    $edit = array(
      $element => $this->ackUser->uid,
    );
    $this
      ->drupalPost('admin/structure/menu/item/' . $this->items['mapped top link']['mlid'] . '/edit', $edit, 'Save');
    $edit = array(
      $element => $this->ackAdmin->uid,
    );
    $this
      ->drupalPost('admin/structure/menu/item/' . $this->items['mapped child link']['mlid'] . '/edit', $edit, 'Save');
    $this
      ->drupalPost('admin/structure/menu/item/' . $this->items['mapped child link with no access parent']['mlid'] . '/edit', $edit, 'Save');
    $edit = array(
      $element => $this->noAccessUser->uid,
    );
    $this
      ->drupalPost('admin/structure/menu/item/' . $this->items['no access top link']['mlid'] . '/edit', $edit, 'Save');
    $this
      ->drupalPost('admin/structure/menu/item/' . $this->items['no access child link']['mlid'] . '/edit', $edit, 'Save');
  }

  /**
   * Test the menu management UI.
   */
  public function testAckMenuUI() {

    // Check that administrators are notified when no schemes are available.
    $this
      ->clickLink(t('Manage menu links'));
    $this
      ->assertText(t('No access schemes have been configured to manage menu links.'));
    $this
      ->clickLink(t('access scheme administration page'));
    $this
      ->assertLink(t('Add access scheme'));

    // Finish building the test environment.
    $this
      ->setUpMenu();

    // Verify that non-integer schemes cannot use the menu link handler.
    $edit = array(
      'name' => $this
        ->randomName(),
      'machine_name' => drupal_strtolower($this
        ->randomName()),
      'roles[' . $this->ackRole->rid . ']' => TRUE,
    );
    $this
      ->drupalPost('admin/structure/access/add/boolean', $edit, 'Save access scheme and continue');
    $this
      ->assertNoFieldByName('handlers[menu_link][handler]');
    $this
      ->assertText(t('No object access handlers are available to manage Menu link objects in a Boolean scheme.'));
    $this
      ->drupalGet('admin/structure/menu/manage/navigation/add');
    $this
      ->assertFieldByName('AckMenuMap[' . $this->schemeMachineName . ']', '', 'Link can be mapped to an integer scheme.');
    $this
      ->assertNoFieldByName('AckMenuMap[' . $edit['machine_name'] . ']', '', 'Link cannot be mapped to a boolean scheme.');
    $ack_menu_url = 'ack_menu/manage/' . $this->schemeMachineName . '/';
    $ack_link_url = 'admin/structure/menu/item/';

    // Test as the scheme menu admin.
    $this
      ->clickLink(t('Manage menu links'));

    // A scheme menu admin should see all realms.
    $this
      ->assertLinkByHref($ack_menu_url . '1', 0, 'The user 1 realm is listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->ackAdmin->uid, 0, 'The admin user realm is listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->ackUser->uid, 0, 'The ACK user realm is listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->noAccessUser->uid, 0, 'The no access user realm is listed.');

    // Test a realm with no mappings.
    $this
      ->drupalGet($ack_menu_url . '1');
    $this
      ->assertTitle(t('Manage @realm menu links', array(
      '@realm' => 'placeholder-for-uid-1',
    )) . ' | Drupal');
    $this
      ->assertText(t('There are no menu links yet for @realm.', array(
      '@realm' => 'placeholder-for-uid-1',
    )));
    $this
      ->assertLink(t('Add a link'));
    $this
      ->assertNoLink(t('Edit the parent link'));
    $edit = array();
    for ($i = 0; $i < 2; $i++) {
      $this
        ->clickLink(t('Add @realm menu link', array(
        '@realm' => 'placeholder-for-uid-1',
      )));
      $selector = $this
        ->xpath('//select[@name=:name and @disabled="disabled"]', array(
        ':name' => 'AckMenuMap[' . $this->schemeMachineName . ']',
      ));
      $this
        ->assertTrue($selector, 'The mapping element is disabled.');
      $this
        ->assertOptionSelected('edit-ackmenumap-' . $this->schemeMachineName, 1, 'The realm mapping is selected.');
      $this
        ->assertOptionSelected('edit-parent', 'navigation:0', 'The parent menu is selected.');
      $this
        ->assertFieldByXPath('//option[@value="navigation:' . $this->items['mapped top link']['mlid'] . '"]', TRUE, 'Links in managed menus from other realms are not filtered.');
      $this
        ->assertNoFieldByXPath('//option[@value="main-menu:0"]', TRUE, 'Unmanaged menus are not available as parent options.');
      $edit[$i] = array(
        'link_title' => $this
          ->randomName(),
        'link_path' => 'node',
      );
      $this
        ->drupalPost(NULL, $edit[$i], 'Save');
      $this
        ->assertLink(t('Edit the parent link'), $i);
      if (!$i) {

        // With only one link, the parent link header should not be shown.
        $this
          ->assertNoRaw('<legend><span class="fieldset-legend"><a href="' . url('node') . '" title="">' . $edit[$i]['link_title'] . '</a></span></legend>', 'Parent link header is not displayed when only one parent is present.');
      }
      else {

        // With more than one, we need a header for each.
        $this
          ->assertRaw('<legend><span class="fieldset-legend"><a href="' . url('node') . '" title="">' . $edit[$i]['link_title'] . '</a></span></legend>', 'Parent link headers are displayed when there are two parents available.');
      }
    }

    // Make sure both headers are present.
    $this
      ->assertRaw('<legend><span class="fieldset-legend"><a href="' . url('node') . '" title="">' . $edit[0]['link_title'] . '</a></span></legend>');

    // Check access to menu items.
    $this
      ->clickLink(t('Edit the parent link'));
    $this
      ->assertText(t('Edit menu link'), 'The link can be edited.');
    $selector = $this
      ->xpath('//select[@name=:name and @disabled="disabled"]', array(
      ':name' => 'AckMenuMap[' . $this->schemeMachineName . ']',
    ));
    $this
      ->assertFalse($selector, 'The mapping can be changed.');
    $this
      ->assertOptionSelected('edit-ackmenumap-' . $this->schemeMachineName, 1, 'The realm mapping is selected.');
    $this
      ->drupalPost(NULL, array(), 'Delete');
    $this
      ->assertText(t('Are you sure you want to delete the custom menu link'), 'The link can be deleted.');

    // Check the access callbacks.
    $this
      ->drupalGet($ack_menu_url . 'a');
    $this
      ->assertResponse(403, 'Access is denied to an invalid realm.');
    $this
      ->drupalGet('admin/structure/menu/settings');
    $this
      ->assertResponse(403, 'Access is denied to the menu settings page.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu');
    $this
      ->assertResponse(403, 'Access is denied to an unmanaged menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to an unmanaged menu.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation');
    $this
      ->assertResponse(200, 'Access is granted to a managed menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation/add');
    $this
      ->assertResponse(200, 'Access is granted to add a link to a managed menu.');
    $this
      ->drupalGet('admin/structure/menu');
    $this
      ->assertResponse(200, 'Access is granted to the menu admin page.');

    // Check that the menu overview page is properly filtered.
    $this
      ->assertNoLinkByHref('admin/structure/menu/manage/main-menu');
    $this
      ->assertNoLinkByHref('admin/structure/menu/manage/management');
    $this
      ->assertLinkByHref('admin/structure/menu/manage/navigation');
    $this
      ->assertNoLinkByHref('admin/structure/menu/manage/user-menu');
    $this
      ->assertNoLinkByHref('admin/structure/menu/settings');

    // Test as the test user, without an access grant.
    $this
      ->drupalLogin($this->ackUser);
    $this
      ->clickLink(t('Manage menu links'));

    // Without a grant, the menu manager should be empty.
    $this
      ->assertNoLinkByHref($ack_menu_url, 'No realms are listed.');
    $this
      ->assertText(t('You have not been granted access to any menu trees.'));

    // Check the access callbacks.
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid);
    $this
      ->assertResponse(403, 'Access is denied to a menu tree.');
    $this
      ->drupalGet($ack_menu_url . 'a');
    $this
      ->assertResponse(403, 'Access is denied to an invalid realm.');
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid . '/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link.');
    $this
      ->drupalGet($ack_link_url . $this->items['mapped top link']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit a mapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['mapped top link']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete a mapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit an unmapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete an unmapped link.');
    $this
      ->drupalGet('admin/structure/menu');
    $this
      ->assertResponse(403, 'Access is denied to the menu admin page.');
    $this
      ->drupalGet('admin/structure/menu/settings');
    $this
      ->assertResponse(403, 'Access is denied to the menu settings page.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu');
    $this
      ->assertResponse(403, 'Access is denied to an unmanaged menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to an unmanaged menu.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation');
    $this
      ->assertResponse(403, 'Access is denied to a managed menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to a managed menu.');

    // Grant access to the test user.
    $this
      ->drupalLogin($this->ackAdmin);
    $field = 'ack_' . $this->schemeMachineName . '[und]';
    $edit = array(
      'user' => $this->ackUser->name,
      'role' => $this->ackRole->rid,
      $field . '[' . $this->ackUser->uid . ']' => TRUE,
      $field . '[' . $this->ackAdmin->uid . ']' => TRUE,
      $field . '[' . $this->menuAdmin->uid . ']' => TRUE,
    );
    $this
      ->drupalPost('admin/access/add/' . $this->schemeMachineName, $edit, 'Save');

    // Retest as the test user.
    $this
      ->drupalLogin($this->ackUser);
    $this
      ->clickLink(t('Manage menu links'));

    // With a grant, the menu manager should only list the assigned realms.
    $this
      ->assertNoLinkByHref($ack_menu_url . '1', 'The user 1 realm is not listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->ackAdmin->uid, 0, 'The admin user realm is listed: ' . $this->ackAdmin->name);
    $this
      ->assertNoLinkByHref($ack_menu_url . $this->menuAdmin->uid, 'The global admin user realm is not listed: ' . $this->menuAdmin->name);
    $this
      ->assertLinkByHref($ack_menu_url . $this->ackUser->uid, 0, 'The ACK user realm is listed: ' . $this->ackUser->name);
    $this
      ->assertNoLinkByHref($ack_menu_url . $this->noAccessUser->uid, 'The no access user realm is not listed: ' . $this->noAccessUser->name);

    // Check the access callbacks.
    $this
      ->drupalGet($ack_menu_url . $this->menuAdmin->uid);
    $this
      ->assertResponse(403, 'Access is denied to a realm menu tree with no mapped links.');
    $this
      ->drupalGet($ack_menu_url . $this->noAccessUser->uid);
    $this
      ->assertResponse(403, 'Access is denied to the no access menu tree.');
    $this
      ->drupalGet($ack_menu_url . 'a');
    $this
      ->assertResponse(403, 'Access is denied to an invalid realm.');
    $this
      ->drupalGet($ack_menu_url . $this->noAccessUser->uid . '/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to the no access realm.');
    $this
      ->drupalGet($ack_link_url . $this->items['no access top link']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit the top link in the no access realm.');
    $this
      ->drupalGet($ack_link_url . $this->items['no access child link']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit a link in the no access realm that is a child of an accessible link.');
    $this
      ->drupalGet($ack_link_url . $this->items['no access grandchild link']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit a link in the no access realm that is a grandchild of an accessible link.');
    $this
      ->drupalGet($ack_link_url . $this->items['no access top link']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete the top link in the no access realm.');
    $this
      ->drupalGet($ack_link_url . $this->items['no access child link']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete a link in the no access realm that is a child of an accessible link.');
    $this
      ->drupalGet($ack_link_url . $this->items['no access grandchild link']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete a link in the no access realm that is a grandchild of an accessible link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit an unmapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete an unmapped link.');
    $this
      ->drupalGet('admin/structure/menu');
    $this
      ->assertResponse(403, 'Access is denied to the menu admin page.');
    $this
      ->drupalGet('admin/structure/menu/settings');
    $this
      ->assertResponse(403, 'Access is denied to the menu settings page.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu');
    $this
      ->assertResponse(403, 'Access is denied to an unmanaged menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to an unmanaged menu.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation');
    $this
      ->assertResponse(403, 'Access is denied to a managed menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to a managed menu.');

    // Put the links we'll be testing into a more easily referenced format.
    // The parent link for the user's realm.
    $parent = new stdClass();
    $parent->mlid = $this->items['mapped top link']['mlid'];
    $parent->plid = $this->items['mapped top link']['plid'];
    $parent->title = $this->items['mapped top link']['title'];
    $parent->field_name = $parent->plid . '[subtree][mlid:' . $parent->mlid . ']';
    $parent->edit = $ack_link_url . $parent->mlid . '/edit';
    $parent->delete = $ack_link_url . $parent->mlid . '/delete';

    // A normal child link of $parent.
    $child = new stdClass();
    $child->mlid = $this->items['not mapped child link']['mlid'];
    $child->title = $this->items['not mapped child link']['title'];
    $child->field_name = $parent->mlid . '[subtree][mlid:' . $child->mlid . ']';
    $child->edit = $ack_link_url . $child->mlid . '/edit';
    $child->delete = $ack_link_url . $child->mlid . '/delete';

    // A child link of $parent explicitly mapped to an accessible foreign realm.
    $foreign_child = new stdClass();
    $foreign_child->mlid = $this->items['mapped child link']['mlid'];
    $foreign_child->title = $this->items['mapped child link']['title'];
    $foreign_child->field_name = $parent->mlid . '[subtree][mlid:' . $foreign_child->mlid . ']';
    $foreign_child->field_id = 'edit-' . $parent->mlid . '-subtree-mlid' . $foreign_child->mlid;
    $foreign_child->edit = $ack_link_url . $foreign_child->mlid . '/edit';
    $foreign_child->delete = $ack_link_url . $foreign_child->mlid . '/delete';

    // A child link of $parent explicitly mapped to an inaccessible realm.
    $no_access_child = new stdClass();
    $no_access_child->mlid = $this->items['no access child link']['mlid'];
    $no_access_child->title = $this->items['no access child link']['title'];
    $no_access_child->field_name = $parent->mlid . '[subtree][mlid:' . $no_access_child->mlid . ']';
    $no_access_child->edit = $ack_link_url . $no_access_child->mlid . '/edit';
    $no_access_child->delete = $ack_link_url . $no_access_child->mlid . '/delete';

    // A child link of $no_access_child.
    $no_access_grandchild = new stdClass();
    $no_access_grandchild->mlid = $this->items['no access grandchild link']['mlid'];
    $no_access_grandchild->title = $this->items['no access grandchild link']['title'];
    $no_access_grandchild->field_name = $no_access_child->mlid . '[subtree][mlid:' . $no_access_grandchild->mlid . ']';

    // Test the subtree for a realm with a top-level mapping.
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid);

    // The parent link.
    $this
      ->assertNoFieldByName($parent->field_name . '[plid]', $parent->plid, 'The parent link does not appear in the subtree: ' . $parent->title);
    $this
      ->assertLinkByHref($parent->edit, 0, 'The parent link shows the edit operation.');
    $this
      ->assertNoLinkByHref($parent->delete, 'The parent link does not show the delete operation.');

    // The normal child link.
    $this
      ->assertFieldByName($child->field_name . '[plid]', $parent->mlid, 'The child link appears in the subtree: ' . $child->title);
    $this
      ->assertLinkByHref($child->edit, 0, 'The child link shows the edit operation.');
    $this
      ->assertLinkByHref($child->delete, 0, 'The child link shows the delete operation.');

    // The accessible foreign child link.
    $this
      ->assertFieldByName($foreign_child->field_name . '[plid]', $parent->mlid, 'The foreign child link appears in the subtree: ' . $foreign_child->title);
    $this
      ->assertLinkByHref($foreign_child->edit, 0, 'The foreign child link shows the edit operation.');
    $this
      ->assertLinkByHref($foreign_child->delete, 0, 'The foreign child link shows the delete operation.');
    $this
      ->assertFieldChecked($foreign_child->field_id . '-hidden', 'The foreign child link is enabled.');

    // The inaccessible foreign child link.
    $this
      ->assertFieldByName($no_access_child->field_name . '[plid]', $parent->mlid, 'The inaccessible child link appears in the subtree: ' . $no_access_child->title);
    $this
      ->assertNoLinkByHref($no_access_child->edit, 'The inaccessible child link does not show the edit operation.');
    $this
      ->assertNoLinkByHref($no_access_child->delete, 'The inaccessible child link does not show the delete operation.');

    // The inaccessible grandchild link.
    $this
      ->assertNoFieldByName($no_access_grandchild->field_name . '[plid]', $parent->mlid, 'The inaccessible grandchild link does not appear in the subtree: ' . $no_access_grandchild->title);
    $this
      ->assertNoLink($no_access_grandchild->title);

    // Test editing a link.
    $edit = array(
      $foreign_child->field_name . '[hidden]' => FALSE,
    );
    $this
      ->drupalPost(NULL, $edit, 'Save configuration');
    $this
      ->assertNoFieldChecked($foreign_child->field_id . '-hidden', 'The foreign child link is disabled: ' . $foreign_child->title);

    // Add a link to the realm.
    $this
      ->clickLink(t('Add @realm menu link', array(
      '@realm' => $this->ackUser->name,
    )));
    $this
      ->assertOptionSelected('edit-parent', 'navigation:' . $parent->mlid, 'The parent link is selected: ' . $parent->title);
    $this
      ->assertFieldByXPath('//option[@value="navigation:' . $child->mlid . '"]', TRUE, 'The child link is an option: ' . $child->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:' . $foreign_child->mlid . '"]', TRUE, 'The foreign child link is not an option: ' . $foreign_child->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:' . $no_access_child->mlid . '"]', TRUE, 'The inaccessible child link is not an option: ' . $no_access_child->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:' . $no_access_grandchild->mlid . '"]', TRUE, 'The inaccessible grandchild link is not an option: ' . $no_access_grandchild->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:0"]', TRUE, 'The parent menu options were filtered.');
    $this
      ->assertNoFieldByXPath('//option[@value="main-menu:0"]', TRUE, 'Unmanaged menus are not available as parent options.');
    $edit = array(
      'link_title' => $this
        ->randomName(),
      'link_path' => 'node',
      'parent' => 'navigation:' . $child->mlid,
    );
    $this
      ->drupalPost(NULL, $edit, 'Save');
    $this
      ->assertText(t('Your configuration has been saved.'));
    $this
      ->assertLink($edit['link_title'], 0, 'The ACK user can create a link.');

    // A few more links for the next set of tests.
    // The parent link of the inaccessible realm.
    $no_access = new stdClass();
    $no_access->mlid = $this->items['no access top link']['mlid'];
    $no_access->title = $this->items['no access top link']['title'];
    $no_access->edit = $ack_link_url . $no_access->mlid . '/edit';
    $no_access->delete = $ack_link_url . $no_access->mlid . '/delete';

    // A link mapped to this realm, but a child of an inaccessible link.
    $child_of_no_access = new stdClass();
    $child_of_no_access->mlid = $this->items['mapped child link with no access parent']['mlid'];
    $child_of_no_access->title = $this->items['mapped child link with no access parent']['title'];
    $child_of_no_access->field_name = $no_access->mlid . '[subtree][mlid:' . $child_of_no_access->mlid . ']';
    $child_of_no_access->edit = $ack_link_url . $child_of_no_access->mlid . '/edit';
    $child_of_no_access->delete = $ack_link_url . $child_of_no_access->mlid . '/delete';

    // Edit the accessible foreign child link.
    $options = array(
      'query' => array(
        'destination' => $ack_menu_url . $this->ackUser->uid,
      ),
    );
    $this
      ->drupalGet($foreign_child->edit, $options);
    $this
      ->assertOptionSelected('edit-parent', 'navigation:' . $parent->mlid, 'The parent link is selected: ' . $parent->title);
    $this
      ->assertNoOptionSelected('edit-parent', 'navigation:' . $child_of_no_access->mlid, 'The other potential parent option is available, but not selected: ' . $child_of_no_access->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:' . $no_access->mlid . '"]', TRUE, 'The "no access" realm parent link is not available as a parent option: ' . $no_access->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:0"]', TRUE, 'The parent menu options were filtered.');
    $this
      ->assertNoFieldByXPath('//option[@value="main-menu:0"]', TRUE, 'Unmanaged menus are not available as parent options.');
    $selector = $this
      ->xpath('//select[@name=:name and @disabled="disabled"]', array(
      ':name' => 'weight',
    ));
    $this
      ->assertFalse($selector, 'The weight element is not disabled.');
    $this
      ->drupalPost(NULL, array(
      'enabled' => TRUE,
    ), 'Save');
    $this
      ->assertText(t('Your configuration has been saved.'));
    $this
      ->assertFieldChecked($foreign_child->field_id . '-hidden', 'The ACK user can edit a link.');

    // Test the subtrees for a realm with child-level mappings.
    $this
      ->drupalGet($ack_menu_url . $this->ackAdmin->uid);
    $this
      ->assertNoLinkByHref($no_access->edit, 'The "no access" realm parent link is not listed.');
    $this
      ->assertNoFieldByName($foreign_child->field_name . '[plid]', $parent->mlid, 'The link with the accessible parent does not appear in the subtree: ' . $foreign_child->title);
    $this
      ->assertLink($foreign_child->title);
    $this
      ->assertLinkByHref($foreign_child->edit, 0, 'The link with the accessible parent shows the edit operation.');
    $this
      ->assertNoLinkByHref($foreign_child->delete, 'The link with the accessible parent does not show the delete operation.');
    $this
      ->assertNoFieldByName($child_of_no_access->field_name . '[plid]', $no_access->mlid, 'The link with the inaccessible parent does not appear in a subtree for its parent: ' . $child_of_no_access->title);
    $this
      ->assertNoFieldByName($foreign_child->mlid . '[subtree][mlid:' . $child_of_no_access->mlid . ']' . '[plid]', $no_access->mlid, 'The link with the inaccessible parent does not appear in a subtree for the link with the accessible parent: ' . $child_of_no_access->title);
    $this
      ->assertLink($child_of_no_access->title);
    $this
      ->assertLinkByHref($child_of_no_access->edit, 0, 'The link with the inaccessible parent shows the edit operation.');
    $this
      ->assertNoLinkByHref($child_of_no_access->delete, 'The link with the inaccessible parent does not show the delete operation.');

    // Delete the link with the accessible parent.
    $options = array(
      'query' => array(
        'destination' => $ack_menu_url . $this->ackAdmin->uid,
      ),
    );
    $this
      ->drupalPost($foreign_child->delete, array(), 'Confirm', $options);
    $this
      ->assertText(t('The menu link @title has been deleted.', array(
      '@title' => $foreign_child->title,
    )));
    $this
      ->assertNoLinkByHref($foreign_child->edit, 'The ACK user can delete a link.');
    $this
      ->assertLinkByHref($child_of_no_access->edit, 0, 'The link with the inaccessible parent is still available.');

    // Test editing an item with an inaccessible parent.
    $this
      ->clickLink(t('Edit the parent link'));
    $this
      ->assertFieldByName('link_title', $child_of_no_access->title);
    $this
      ->assertOptionSelected('edit-parent', 'navigation:' . $no_access->mlid, 'The parent is selected: ' . $no_access->title);
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:' . $parent->mlid . '"]', TRUE, 'The parent link options were filtered.');
    $selector = $this
      ->xpath('//select[@name=:name and @disabled="disabled"]', array(
      ':name' => 'weight',
    ));
    $this
      ->assertTrue($selector, 'The weight element is disabled.');

    // Test as a user with no menu-related access.
    $this
      ->drupalLogin($this->noAccessUser);
    $this
      ->assertNoLink(t('Manage menu links'));

    // Check the access callbacks.
    $this
      ->drupalGet('ack_menu');
    $this
      ->assertResponse(403, 'Access is denied to the menu manager.');
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid);
    $this
      ->assertResponse(403, 'Access is denied to a menu tree.');
    $this
      ->drupalGet($ack_menu_url . 'a');
    $this
      ->assertResponse(403, 'Access is denied to an invalid realm.');
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid . '/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link.');
    $this
      ->drupalGet($ack_link_url . $this->items['mapped top link']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit a mapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['mapped top link']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete a mapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/edit');
    $this
      ->assertResponse(403, 'Access is denied to edit an unmapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/delete');
    $this
      ->assertResponse(403, 'Access is denied to delete an unmapped link.');
    $this
      ->drupalGet('admin/structure/menu');
    $this
      ->assertResponse(403, 'Access is denied to the menu admin page.');
    $this
      ->drupalGet('admin/structure/menu/settings');
    $this
      ->assertResponse(403, 'Access is denied to the menu settings page.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu');
    $this
      ->assertResponse(403, 'Access is denied to an unmanaged menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to an unmanaged menu.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation');
    $this
      ->assertResponse(403, 'Access is denied to a managed menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation/add');
    $this
      ->assertResponse(403, 'Access is denied to add a link to a managed menu.');

    // Test as the menu administrator.
    $this
      ->drupalLogin($this->menuAdmin);
    $this
      ->clickLink(t('Manage menu links'));

    // A menu admin should see all realms.
    $this
      ->assertLinkByHref($ack_menu_url . '1', 0, 'The user 1 realm is listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->ackAdmin->uid, 0, 'The admin user realm is listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->ackUser->uid, 0, 'The ACK user realm is listed.');
    $this
      ->assertLinkByHref($ack_menu_url . $this->noAccessUser->uid, 0, 'The no access user realm is listed.');

    // Check the access callbacks.
    $this
      ->assertResponse(200, 'Access is granted to the menu manager.');
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid);
    $this
      ->assertResponse(200, 'Access is granted to a menu tree.');
    $this
      ->drupalGet($ack_menu_url . 'a');
    $this
      ->assertResponse(403, 'Access is denied to an invalid realm.');
    $this
      ->drupalGet($ack_menu_url . $this->ackUser->uid . '/add');
    $this
      ->assertResponse(200, 'Access is granted to add a link.');
    $this
      ->drupalGet($ack_link_url . $this->items['mapped top link']['mlid'] . '/edit');
    $this
      ->assertResponse(200, 'Access is granted to edit a mapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['mapped top link']['mlid'] . '/delete');
    $this
      ->assertResponse(200, 'Access is granted to delete a mapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/edit');
    $this
      ->assertResponse(200, 'Access is granted to edit an unmapped link.');
    $this
      ->drupalGet($ack_link_url . $this->items['not mapped']['mlid'] . '/delete');
    $this
      ->assertResponse(200, 'Access is granted to delete an unmapped link.');
    $this
      ->drupalGet('admin/structure/menu/settings');
    $this
      ->assertResponse(200, 'Access is granted to the menu settings page.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu');
    $this
      ->assertResponse(200, 'Access is granted to an unmanaged menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/main-menu/add');
    $this
      ->assertResponse(200, 'Access is granted to add a link to an unmanaged menu.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation');
    $this
      ->assertResponse(200, 'Access is granted to a managed menu list.');
    $this
      ->drupalGet('admin/structure/menu/manage/navigation/add');
    $this
      ->assertResponse(200, 'Access is granted to add a link to a managed menu.');
    $this
      ->drupalGet('admin/structure/menu');
    $this
      ->assertResponse(200, 'Access is granted to the menu admin page.');

    // Check that the menu overview page is not filtered.
    $this
      ->assertLinkByHref('admin/structure/menu/manage/main-menu');
    $this
      ->assertLinkByHref('admin/structure/menu/manage/management');
    $this
      ->assertLinkByHref('admin/structure/menu/manage/navigation');
    $this
      ->assertLinkByHref('admin/structure/menu/manage/user-menu');
    $this
      ->assertLinkByHref('admin/structure/menu/settings');

    // Check the permission descriptions.
    $this
      ->drupalGet('admin/people/permissions');
    $this
      ->assertText(t('Manage the links in the following access scheme-controlled menus: @menus.', array(
      '@menus' => 'Navigation',
    )), 'Permissions page lists manageable menus.');
    $scheme = access_scheme_machine_name_load($this->schemeMachineName);
    $this
      ->assertText(t('This permission only applies to the following access schemes: @schemes.', array(
      '@schemes' => $scheme->name,
    )), 'Permissions page lists applicable schemes.');
  }

  /**
   * Test controlling access to the node menu options.
   */
  public function testNodeMenuUI() {
    $this
      ->setUpMenu();

    // Test as the scheme menu admin, but with no usable menus on the node.
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertNoText(t('Provide a menu link'));
    variable_set('menu_options_article', array(
      'main-menu',
      'management',
    ));
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertNoText(t('Provide a menu link'));

    // Add the node's menu to the scheme and retest.
    $edit = array(
      'handlers[menu_link][AckMenuMap][menus][main-menu]' => TRUE,
    );
    $this
      ->drupalPost('admin/structure/access/' . $this->schemeMachineName, $edit, 'Save access scheme');
    $this
      ->assertText(t('Updated access scheme'), 'Access scheme configured.');
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertText(t('Provide a menu link'));
    $this
      ->assertNoFieldByXPath('//option[@value="management:0"]', TRUE, 'Removed parent option: management');
    $edit = array(
      'title' => $this
        ->randomName(),
      'menu[enabled]' => TRUE,
      'menu[link_title]' => $this
        ->randomName(),
      'menu[parent]' => 'main-menu:0',
    );
    $this
      ->drupalPost(NULL, $edit, 'Save');
    $this
      ->assertLink($edit['menu[link_title]'], 0, 'The scheme menu admin can create a link from the node form.');
    $this
      ->clickLink(t('Edit'));
    $this
      ->assertFieldChecked('edit-menu-enabled');
    $this
      ->assertOptionSelected('edit-menu-parent', 'main-menu:0');

    // Test as the test user, without an access grant.
    $this
      ->drupalLogin($this->ackUser);
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertNoText(t('Provide a menu link'));
    $this
      ->drupalPost(NULL, array(
      'title' => $this
        ->randomName(),
    ), 'Save');
    $this
      ->clickLink(t('Edit'));
    $this
      ->assertNoText(t('Provide a menu link'), 'The ACK user cannot create a link from the node form without a grant.');

    // Grant access to the test user.
    $this
      ->drupalLogin($this->ackAdmin);
    $field = 'ack_' . $this->schemeMachineName . '[und]';
    $edit = array(
      'user' => $this->ackUser->name,
      'role' => $this->ackRole->rid,
      $field . '[' . $this->ackUser->uid . ']' => TRUE,
      $field . '[' . $this->ackAdmin->uid . ']' => TRUE,
    );
    $this
      ->drupalPost('admin/access/add/' . $this->schemeMachineName, $edit, 'Save');

    // Retest as the test user, but with no usable menus on the node.
    $this
      ->drupalLogin($this->ackUser);
    variable_set('menu_options_article', array(
      'main-menu',
    ));
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertNoText(t('Provide a menu link'));
    $this
      ->drupalPost(NULL, array(
      'title' => $this
        ->randomName(),
    ), 'Save');
    $this
      ->clickLink(t('Edit'));
    $this
      ->assertNoText(t('Provide a menu link'), 'The ACK user cannot create a link from the node form without a usable menu.');

    // Retest with a usable menu.
    variable_set('menu_options_article', array(
      'main-menu',
      'navigation',
    ));
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertText(t('Provide a menu link'));

    // Check parent option filtering before save.
    $options = array(
      'mapped top link' => TRUE,
      'mapped child link' => TRUE,
      'no access child link' => FALSE,
      'no access grandchild link' => FALSE,
      'mapped child link with no access parent' => TRUE,
      'not mapped' => FALSE,
      'no access top link' => FALSE,
    );
    foreach ($options as $key => $allowed) {
      $xpath = '//option[@value="navigation:' . $this->items[$key]['mlid'] . '"]';
      if ($allowed) {
        $this
          ->assertFieldByXPath($xpath, TRUE, 'Allowed parent option: ' . $key);
      }
      else {
        $this
          ->assertNoFieldByXPath($xpath, TRUE, 'Removed parent option: ' . $key);
      }
    }
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:0"]', TRUE, 'Removed parent option: navigation');
    $this
      ->assertNoFieldByXPath('//option[@value="main-menu:0"]', TRUE, 'Removed parent option: main menu');

    // Check parent option filtering after save.
    $parent = 'navigation:' . $this->items['mapped top link']['mlid'];
    $edit = array(
      'title' => $this
        ->randomName(),
      'menu[enabled]' => TRUE,
      'menu[link_title]' => $this
        ->randomName(),
      'menu[parent]' => $parent,
    );
    $this
      ->drupalPost(NULL, $edit, 'Save');
    $this
      ->assertLink($edit['menu[link_title]'], 0, 'The ACK user can create a link from the node form.');
    $this
      ->clickLink(t('Edit'));
    $this
      ->assertFieldChecked('edit-menu-enabled');
    $this
      ->assertOptionSelected('edit-menu-parent', $parent);

    // Links from realms other than the one that currently owns this link should
    // be excluded from the parent options.
    $options['mapped child link'] = FALSE;
    $options['mapped child link with no access parent'] = FALSE;
    foreach ($options as $key => $allowed) {
      $xpath = '//option[@value="navigation:' . $this->items[$key]['mlid'] . '"]';
      if ($allowed) {
        $this
          ->assertFieldByXPath($xpath, TRUE, 'Allowed parent option: ' . $key);
      }
      else {
        $this
          ->assertNoFieldByXPath($xpath, TRUE, 'Removed parent option: ' . $key);
      }
    }
    $this
      ->assertNoFieldByXPath('//option[@value="navigation:0"]', TRUE, 'Removed parent option: navigation');
    $this
      ->assertNoFieldByXPath('//option[@value="main-menu:0"]', TRUE, 'Removed parent option: main menu');

    // Test a node with an inaccessible link.
    $this
      ->drupalLogin($this->ackAdmin);
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'article',
      'uid' => $this->ackUser->uid,
    ));
    $edit = array(
      'link_title' => $this
        ->randomName(),
      'link_path' => 'node/' . $node->nid,
    );
    $this
      ->drupalPost('ack_menu/manage/' . $this->schemeMachineName . '/' . $this->noAccessUser->uid . '/add', $edit, 'Save');
    $this
      ->drupalLogin($this->ackUser);
    $this
      ->drupalGet('node/' . $node->nid . '/edit');
    $this
      ->assertText(t('Edit Article @title', array(
      '@title' => $node->title,
    )));
    $this
      ->assertNoText(t('Provide a menu link'), 'The ACK user cannot edit a link owned by a foreign realm.');

    // Test as a user with no menu-related access.
    $this
      ->drupalLogin($this->noAccessUser);
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertNoText(t('Provide a menu link'));
    $this
      ->drupalPost(NULL, array(
      'title' => $this
        ->randomName(),
    ), 'Save');
    $this
      ->clickLink(t('Edit'));
    $this
      ->assertNoText(t('Provide a menu link'), 'A user with no menu-related access cannot create a link from the node form.');

    // Test as the menu administrator.
    $this
      ->drupalLogin($this->menuAdmin);
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertText(t('Provide a menu link'));
    $this
      ->assertFieldByXPath('//option[@value="navigation:0"]', TRUE, 'Allowed parent option: navigation');
    $this
      ->assertFieldByXPath('//option[@value="main-menu:0"]', TRUE, 'Allowed parent option: main menu');
    $edit = array(
      'title' => $this
        ->randomName(),
      'menu[enabled]' => TRUE,
      'menu[link_title]' => $this
        ->randomName(),
      'menu[parent]' => 'main-menu:0',
    );
    $this
      ->drupalPost(NULL, $edit, 'Save');
    $this
      ->assertLink($edit['menu[link_title]'], 0, 'The menu admin can create a link from the node form.');
    $this
      ->clickLink(t('Edit'));
    $this
      ->assertFieldChecked('edit-menu-enabled');
    $this
      ->assertOptionSelected('edit-menu-parent', 'main-menu:0');
  }

  /**
   * Test that the module responds appropriately to events that affect the map.
   */
  public function testEventHooks() {
    $this
      ->setUpScheme();
    $scheme = access_scheme_machine_name_load($this->schemeMachineName);

    // Create two mapped links.
    for ($i = 0; $i < 2; $i++) {
      $edit = array(
        'link_title' => $this
          ->randomName(),
        'link_path' => 'node',
      );
      $this
        ->drupalPost('ack_menu/manage/' . $scheme->machine_name . '/' . $this->ackUser->uid . '/add', $edit, 'Save');
    }

    // Confirm that mappings are deleted when links are deleted.
    $links = ack_menu_realm_links($scheme, $this->ackUser->uid, NULL, TRUE);
    $this
      ->assertEqual(count($links), 2, 'Two mappings found.');
    $link = reset($links);
    menu_link_delete($link['mlid']);
    $this
      ->assertFalse(menu_link_load($link['mlid']), 'One link deleted.');
    $links = ack_menu_realm_links($scheme, $this->ackUser->uid, NULL, TRUE);
    $this
      ->assertEqual(count($links), 1, 'One mapping remains.');

    // Create a second scheme with a mapped link.
    $this
      ->setUpScheme();
    $new_scheme = access_scheme_machine_name_load($this->schemeMachineName);
    $edit = array(
      'link_title' => $this
        ->randomName(),
      'link_path' => 'node',
    );
    $this
      ->drupalPost('ack_menu/manage/' . $new_scheme->machine_name . '/' . $this->ackUser->uid . '/add', $edit, 'Save');

    // Confirm that mappings are deleted when schemes are deleted.
    $new_links = ack_menu_realm_links($new_scheme, $this->ackUser->uid, NULL, TRUE);
    $this
      ->assertEqual(count($links) + count($new_links), 2, 'Two mappings found.');
    access_scheme_delete($new_scheme->sid);
    $this
      ->assertFalse(access_scheme_load($new_scheme->sid), 'One scheme deleted.');
    $new_links = ack_menu_realm_links($new_scheme, $this->ackUser->uid, NULL, TRUE);
    $this
      ->assertEqual(count($links) + count($new_links), 1, 'One mapping remains.');
  }

}

Classes

Namesort descending Description
AckMenuAccessTest Tests the menu access functions.