? 828416-15-spaces-multiple.patch
? 828416-9-spaces-multiple.patch
? spaces_taxonomy/tests
Index: spaces.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/spaces/spaces.module,v
retrieving revision 1.25.4.33.2.32.2.47
diff -u -p -r1.25.4.33.2.32.2.47 spaces.module
--- spaces.module	20 Aug 2010 15:45:21 -0000	1.25.4.33.2.32.2.47
+++ spaces.module	16 Sep 2010 22:07:31 -0000
@@ -2,6 +2,21 @@
 // $Id: spaces.module,v 1.25.4.33.2.32.2.47 2010/08/20 15:45:21 yhahn Exp $
 
 /**
+ * When there are multiple potential spaces, select the canonical space.
+ */
+define('SPACES_MULTIPLE_HANDLING_CANONICAL', 1);
+
+/**
+ * When there are multiple potential spaces, randomly select one.
+ */
+define('SPACES_MULTIPLE_HANDLING_RANDOM', 2);
+
+/**
+ * Use an unmodified path as the canonical URL.
+ */
+define('SPACES_CANONICAL_UNMODIFIED', 'unmodified');
+
+/**
  * Core API ===========================================================
  */
 
Index: plugins/space.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/spaces/plugins/Attic/space.inc,v
retrieving revision 1.1.2.1
diff -u -p -r1.1.2.1 space.inc
--- plugins/space.inc	14 Dec 2009 23:19:16 -0000	1.1.2.1
+++ plugins/space.inc	16 Sep 2010 22:07:31 -0000
@@ -15,6 +15,7 @@ class space {
   var $type;
   var $id;
   var $active;
+  var $canonical;
 
   /**
    * Constructor.
Index: spaces_taxonomy/spaces_taxonomy.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/spaces/spaces_taxonomy/Attic/spaces_taxonomy.module,v
retrieving revision 1.1.2.4.2.4.2.6
diff -u -p -r1.1.2.4.2.4.2.6 spaces_taxonomy.module
--- spaces_taxonomy/spaces_taxonomy.module	6 Aug 2010 20:23:20 -0000	1.1.2.4.2.4.2.6
+++ spaces_taxonomy/spaces_taxonomy.module	16 Sep 2010 22:07:32 -0000
@@ -97,17 +97,43 @@ function spaces_taxonomy_menu_alter(&$it
  * Implementation of hook_form_alter().
  */
 function spaces_taxonomy_form_alter(&$form, $form_state, $form_id) {
-  if ($form['#id'] == 'node-form' && (arg(0) .'/'. arg(1) != 'admin/content')) {
+  if ($form['#id'] == 'node-form' && isset($form['taxonomy']) && (arg(0) .'/'. arg(1) != 'admin/content')) {
     $vid = variable_get('spaces_taxonomy_vid', 0);
-    $space = spaces_get_space();
-    if ($vid && !empty($form['taxonomy'][$vid]) && $space->type == 'taxonomy') {
-      $form['taxonomy'][$vid]['#disabled'] = TRUE;
-      $form['taxonomy'][$vid]['#default_value'] = $space->id;
+    if ($vid && !empty($form['taxonomy'][$vid])) {
+      // #input must be TRUE to trigger #process callbacks.
+      $form['taxonomy']['#input'] = TRUE;
+      $form['taxonomy']['#process'][] = '_spaces_taxonomy_form_process';
     }
   }
 }
 
 /**
+ * Forms API process callback for node forms.
+ *
+ * Selectively set the disabled state and default value of the spaces taxonomy term selects.
+ */
+function _spaces_taxonomy_form_process($element) {
+  $keys = array(variable_get('spaces_taxonomy_vid', 0));
+  // For consistency, support a key set by primary_term module.
+  if (isset($element['primaryterm'])) {
+    $keys[] = 'primaryterm';
+  }
+  $space = spaces_get_space();
+  if ($space->type == 'taxonomy') {
+    foreach ($keys as $key) {
+      if ($key != 'primaryterm' && !$element[$key]['#multiple']) {
+        $element[$key]['#disabled'] = TRUE;
+      }
+      $default_value = array_filter($element[$key]['#default_value']);
+      if (empty($default_value)) {
+        $element[$key]['#default_value'] = $space->id;
+      }
+    }
+  }
+  return $element;
+}
+
+/**
  * Implementation of hook_form_alter() for taxonomy_form_term.
  */
 function spaces_taxonomy_form_taxonomy_form_term_alter(&$form, $form_state) {
@@ -179,22 +205,32 @@ function spaces_taxonomy_taxonomy($op, $
  * Spaces Taxonomy settings form.
  */
 function spaces_taxonomy_settings(&$form_state) {
-  $form = array();
   // Collect an array of valid vocab options
-  $vocabs = array(0 => '---');
-  foreach (taxonomy_get_vocabularies() as $vocab) {
-    // Vocab may not be multiple
-    if (!$vocab->multiple && !$vocab->tags) {
-      $vocabs[$vocab->vid] = $vocab->name;
+  $options = array(0 => '---');
+  $vocabs = taxonomy_get_vocabularies();
+  foreach ($vocabs as $vocab) {
+    if (!$vocab->tags) {
+      $options[$vocab->vid] = $vocab->name;
     }
   }
+  $vid = variable_get('spaces_taxonomy_vid', 0);
   $form['spaces_taxonomy_vid'] = array(
     '#type' => 'select',
     '#title' => t('Spaces vocabulary'),
     '#description' => t('Choose one of the following vocabularies to enable for use with Spaces.'),
-    '#options' => $vocabs,
-    '#default_value' => variable_get('spaces_taxonomy_vid', 0),
+    '#options' => $options,
+    '#default_value' => $vid,
   );
+  if ($vid && $vocabs[$vid]->multiple) {
+    $options = array(SPACES_MULTIPLE_HANDLING_CANONICAL => t('primary (canonical)'), SPACES_MULTIPLE_HANDLING_RANDOM => t('random'));
+    $form['spaces_taxonomy_multiple_handling'] = array(
+      '#type' => 'radios',
+      '#title' => t('Method to select between multiple term spaces'),
+      '#description' => t('If multiple terms from the spaces vocabulary are selected for a piece of content and users are coming from outside any of those spaces, which term should be selected? Choose "primary (canonical)" to have users forwarded every time to a particular term\'s space. Chose "random" to have users forwarded to a potentially different term\'s space each time. If you choose "primary (canonical)", a specific primary space can be set for each node using the <a href="http://drupal.org/project/primary_term">Primary term</a> module. To enable this functionality, install Primary term and configure selected node types to use the Spaces vocabulary for primary terms. Otherwise, term weights on the Spaces vocabulary can be used to set priority; lighter terms will take priority as the primary or canonical space.'),
+      '#options' => $options,
+      '#default_value' => variable_get('spaces_taxonomy_multiple_handling', SPACES_MULTIPLE_HANDLING_CANONICAL),
+    );
+  }
   $form = system_settings_form($form);
   return $form;
 }
Index: spaces_taxonomy/test/spaces_taxonomy.test
===================================================================
RCS file: spaces_taxonomy/test/spaces_taxonomy.test
diff -N spaces_taxonomy/test/spaces_taxonomy.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ spaces_taxonomy/test/spaces_taxonomy.test	16 Sep 2010 22:07:32 -0000
@@ -0,0 +1,332 @@
+<?php
+// $Id: $
+
+/**
+ * @file
+ * Simpletests for the Spaces taxonomy module.
+ *
+ * Tests basic configuration and options for multiple space handling.
+ */
+
+
+/**
+ * Tests for spaces taxonomy functionality.
+ */
+class SpacesTaxonomyTestCase extends DrupalWebTestCase {
+
+  /**
+   * User with rights to post SimpleTest Example content.
+   */
+  protected $admin_user;
+
+  /**
+   * Spaces vocabulary.
+   */
+  protected $vocabulary;
+
+  /**
+   * First taxonomy term in Spaces vocabulary.
+   */
+  protected $term_1;
+
+  /**
+   * Second taxonomy term in Spaces vocabulary
+   */
+  protected $term_2;
+
+  /**
+   * Node that is assigned to a single spaces vocabulary term.
+   */
+  protected $node_1;
+
+  /**
+   * Node that is initially assigned to a single spaces vocabulary term but later
+   * given a second term.
+   */
+  protected $node_2;
+
+  /**
+   * Set up test.
+   */
+  public function setUp() {
+
+    parent::setUp(
+      'taxonomy',
+      'ctools',
+      'features',
+      'primary_term',
+      'purl',
+      'spaces',
+      'spaces_ui',
+      'spaces_taxonomy'
+    );
+
+    // Log in admin user.
+    $this->loginAdminUser();
+
+    // Enable path Purl type and assign it to spaces taxonomy.
+    variable_set('purl_types', array('path' => 'path'));
+
+    // Submit the Purl taxonomy settings form, setting the spaces vocabulary.
+    $this->drupalPost(
+      'admin/settings/purl/settings',
+      array(
+        'purl_method_spaces_taxonomy' => 'path',
+      ),
+      t('Save configuration')
+    );
+    $this->assertText(t('The configuration options have been saved.'), t('Spaces taxonomy settings form submitted successfully.'));
+    $this->assertEqual(variable_get('purl_method_spaces_taxonomy', NULL), 'path', t('Purl taxonomy setting saved successfully.'));
+
+    // Create needed objects.
+    $this->createNodesAndTaxonomy();
+
+  }
+
+  /**
+   * Unset user, nodes, and taxonomy.
+   */
+  public function tearDown() {
+    unset($this->admin_user);
+    unset($this->vocabulary);
+    unset($this->term_1);
+    unset($this->term_2);
+    unset($this->node_1);
+    unset($this->node_2);
+    parent::tearDown();
+  }
+
+  /**
+   * Login a user with site building and spaces/features management permissions.
+   */
+  protected function loginAdminUser() {
+    if (empty($this->admin_user)) {
+      $this->admin_user = $this->drupalCreateUser(
+        array(
+          'access content',
+          'administer nodes',
+          'administer content types',
+          'administer site configuration',
+          'access administration pages',
+          'administer spaces',
+        )
+      );
+    }
+    $this->drupalLogin($this->admin_user);
+  }
+
+  /**
+   * Create node and taxonomy objects needed by tests.
+   */
+  protected function createNodesAndTaxonomy() {
+    if (empty($this->vocabulary)) {
+      $settings = array(
+        'type' => 'spaces_taxonomy_example',
+      );
+      $this->drupalCreateContentType($settings);
+
+      $this->vocabulary = array(
+        'name' => $this->randomName(10),
+        'tags' => 0,
+        'multiple' => 0,
+        'required' => 0,
+        'hierarchy' => 0,
+        'relations' => 0,
+        'module' => 'taxonomy',
+        'weight' => 0,
+        'nodes' => array('spaces_taxonomy_example'),
+        'help' => '',
+      );
+      taxonomy_save_vocabulary($this->vocabulary);
+    }
+
+    // Submit the Spaces taxonomy settings form, setting the spaces vocabulary.
+    $this->drupalPost(
+      'admin/build/spaces/taxonomy',
+      array(
+        'spaces_taxonomy_vid' => $this->vocabulary['vid'],
+      ),
+      t('Save configuration')
+    );
+    $this->assertText(t('The configuration options have been saved.'), t('Spaces taxonomy settings form submitted successfully.'));
+    $this->assertEqual(variable_get('spaces_taxonomy_vid', NULL), $this->vocabulary['vid'], t('Spaces vocabulary setting saved successfully.'));
+
+    if (empty($this->term_1) && empty($this->term_2)) {
+
+      $this->term_1 = $this->term_2 = array(
+        'description' => '',
+        'parent' => array(),
+        'relations' => array(),
+        'weight' => 0,
+        'vid' => $this->vocabulary['vid'],
+      );
+      foreach (array(1 => 'aaaaa', 2 => 'bbbbb') as $index => $name) {
+        $term = 'term_' . $index;
+        $this->{$term}['name'] = $name;
+        $this->{$term}['purl'] = array('value' => $name);
+        taxonomy_save_term($this->{$term});
+
+        /*
+        // Submit the Spaces taxonomy settings form, setting the spaces vocabulary.
+        $this->drupalPost(
+          'admin/content/taxonomy/edit/term/' . $this->{$term}['tid'],
+          array(
+            'purl[value]' => $this->{$term}['name'],
+          ),
+          t('Save configuration')
+        );
+        $this->assertText(t('The configuration options have been saved.'), t('Purl path set for term !name.', array('!name' => $this->{$term}['name'])));
+        */
+
+        $this->assertEqual($this->{$term}['name'], $this->{$term}['purl']['value'], t('Purl path set for term !name.', array('!name' => $this->{$term}['name'])));
+      }
+
+    }
+
+    if (empty($this->node_1) && empty($this->node_2)) {
+      $this->node_1 = $this->node_2 = array(
+        'type' => 'spaces_taxonomy_example',
+        'taxonomy' => array($this->term_1['tid']),
+      );
+      $this->node_1 = $this->drupalCreateNode($this->node_1);
+
+      $this->node_2 = $this->drupalCreateNode($this->node_2);
+
+    }
+  }
+}
+
+class SpacesTaxonomyTestBasic extends SpacesTaxonomyTestCase {
+
+  /**
+   * Test info.
+   */
+  public function getInfo() {
+    return array(
+      'name' => t('Spaces Taxonomy: basic'),
+      'description' => t('Tests spaces taxonomy basic functionality. <strong>Requires Purl.</strong>.') ,
+      'group' => t('Spaces'),
+    );
+  }
+
+  /**
+   * Test basic functionality of Spaces taxonomy.
+   */
+  public function testSpacesTaxonomyBasic() {
+
+
+
+  }
+
+}
+
+class SpacesTaxonomyTestMultipleHandling extends SpacesTaxonomyTestCase {
+
+  /**
+   * Test info.
+   */
+  public function getInfo() {
+    return array(
+      'name' => t('Spaces Taxonomy: multiple handling'),
+      'description' => t('Tests multiple handling in spaces taxonomy. <strong>Requires Purl.</strong>.') ,
+      'group' => t('Spaces'),
+    );
+  }
+
+  /**
+   * Test multiple handling in Spaces taxonomy.
+   */
+  public function testSpacesTaxonomyMultipleHandling() {
+
+    // Ensure that the multiple handling setting is not available.
+    $this->drupalGet('admin/build/spaces/taxonomy');
+    $this->assertNoText(t('Multiple handling'), t('When the spaces vocabulary doesn\'t support multiple terms, no setting is available for multiple handling.'));
+
+    // Setup: convert spaces vocabulary to multiple.
+    $this->vocabulary['multiple'] = 1;
+    taxonomy_save_vocabulary($this->vocabulary);
+    // Add a second term to node_2.
+    $this->node_2->taxonomy[$this->term_2['tid']] = taxonomy_get_term($this->term_2['tid']);
+    node_save($this->node_2);
+    // Set multiple handling to canonical.
+    $this->drupalPost(
+      'admin/build/spaces/taxonomy',
+      array(
+        'spaces_taxonomy_vid' => $this->vocabulary['vid'],
+        'spaces_taxonomy_multiple_handling' => SPACES_MULTIPLE_HANDLING_CANONICAL,
+      ),
+      t('Save configuration')
+    );
+    $this->assertText(t('The configuration options have been saved.'), t('Spaces taxonomy settings form submits successfully.'));
+    $this->assertEqual(variable_get('spaces_taxonomy_multiple_handling', SPACES_MULTIPLE_HANDLING_CANONICAL), SPACES_MULTIPLE_HANDLING_CANONICAL, t('Spaces taxonomy settings saved successfully.'));
+
+    // Ensure that the multiple handling setting is available.
+    $this->drupalGet('admin/build/spaces/taxonomy');
+    $this->assertText(t('Method to select between multiple term spaces'), t('When the spaces vocabulary supports multiple terms, a setting is available for multiple handling.'));
+
+    // Test the first node, which has a single term and so should have no caononical link.
+    $this->drupalGet('node/' . $this->node_1->nid);
+    $this->assertNoRaw('<link rel="canonical"', t('When multiple handling is set to canonical, a node with a single term has no canonical link.'));
+
+    // Test the second node, which has two terms, in the canonical space.
+    $this->drupalGet('node/' . $this->node_2->nid);
+    $this->assertNoRaw('<link rel="canonical"', t('When multiple handling is set to canonical, a node with two terms has no canonical link when in the canonical term\'s space.'));
+
+    // Test the second node in a non-canonical space.
+    $this->drupalGet($this->term_2['purl']['value'] . '/node/' . $this->node_2->nid);
+    $options = array(
+      'purl' => array(
+        'id' => $this->term_1['tid'],
+        'provider' => 'spaces_taxonomy'
+      )
+    );
+    $url = url('node/' . $this->node_2->nid, $options);
+    $this->assertRaw('<link rel="canonical" href="' . $url . '"', t("When multiple handling is set to canonical, a node with two terms has a canonical link to the canonical term's space when in the non-canonical term's space."));
+
+    // Adjust term weight to switch the canonical term space.
+    $this->term_2['weight'] = -1;
+    taxonomy_save_term($this->term_2);
+
+    // Test what should now not be the canonical space.
+    $this->drupalGet($this->term_1['purl']['value'] . '/node/' . $this->node_2->nid);
+    $options = array(
+      'purl' => array(
+        'id' => $this->term_2['tid'],
+        'provider' => 'spaces_taxonomy'
+      )
+    );
+    $url = url('node/' . $this->node_2->nid, $options);
+    $this->assertRaw('<link rel="canonical" href="' . $url . '"', t("When multiple handling is set to canonical, term weight determines ordering such that a node with two terms has a canonical link to the lower weighted term's space when in a higher weighted term's space."));
+
+    // Set primary_term and ensure it affects canonical term selection.
+    $this->node_2->primaryterm = $this->term_1['tid'];
+    node_save($this->node_2);
+    $this->drupalGet($this->term_1['purl']['value'] . '/node/' . $this->node_2->nid);
+    $this->assertNoRaw('<link rel="canonical"', t('The primary term space is canonical.'));
+
+    // Switch multiple handling to random.
+    $this->drupalPost(
+      'admin/build/spaces/taxonomy',
+      array(
+        'spaces_taxonomy_vid' => $this->vocabulary['vid'],
+        'spaces_taxonomy_multiple_handling' => SPACES_MULTIPLE_HANDLING_RANDOM,
+      ),
+      t('Save configuration')
+    );
+    $this->assertText(t('The configuration options have been saved.'), t('Spaces taxonomy settings form submits successfully.'));
+    $this->assertEqual(variable_get('spaces_taxonomy_multiple_handling', SPACES_MULTIPLE_HANDLING_CANONICAL), SPACES_MULTIPLE_HANDLING_RANDOM, t('Spaces taxonomy settings saved to random.'));
+
+    // Test for an unmodified canonical url.
+    $this->drupalGet($this->term_1['purl']['value'] . '/node/' . $this->node_2->nid);
+    $options = array(
+      'purl' => array(
+        'disabled' => TRUE
+      )
+    );
+    $url = url('node/' . $this->node_2->nid, $options);
+    $this->assertRaw('<link rel="canonical" href="' . $url . '"', t("When multiple handling is set to random, all nodes with multiple spaces vocabulary terms link to an unmodified node path."));
+
+   
+  }
+}
+
Index: spaces_taxonomy/plugins/space_taxonomy.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/spaces/spaces_taxonomy/plugins/Attic/space_taxonomy.inc,v
retrieving revision 1.1.2.3
diff -u -p -r1.1.2.3 space_taxonomy.inc
--- spaces_taxonomy/plugins/space_taxonomy.inc	26 Apr 2010 19:33:30 -0000	1.1.2.3
+++ spaces_taxonomy/plugins/space_taxonomy.inc	16 Sep 2010 22:07:32 -0000
@@ -41,6 +41,7 @@ class space_taxonomy extends space_type_
             foreach ($terms['tids'] as $tid) {
               if ($term = taxonomy_get_term($tid)) {
                 $this->router_term($term);
+                break;
               }
             }
           }
@@ -49,9 +50,53 @@ class space_taxonomy extends space_type_
       case 'node':
         $node = $object;
         if (!empty($node->taxonomy)) {
-          foreach ($node->taxonomy as $term) {
-            if ($term->vid == variable_get('spaces_taxonomy_vid', 0)) {
-              $this->router_term($term);
+          $spaces_vid = variable_get('spaces_taxonomy_vid', 0);
+          foreach ($node->taxonomy as $tid => $term) {
+            if ($term->vid != $spaces_vid) {
+              unset($node->taxonomy[$tid]);
+            }
+          }
+          $terms = $node->taxonomy;
+          // Multiple handling is needed only if the node has more than one term in
+          // the Spaces vocabulary.
+          if (count($node->taxonomy) > 1) {
+            $multiple_handling = variable_get('spaces_taxonomy_multiple_handling', SPACES_MULTIPLE_HANDLING_CANONICAL);
+
+            switch ($multiple_handling) {
+              case SPACES_MULTIPLE_HANDLING_CANONICAL:
+                // If there is a primary_term designated, priorize it. If a site has the
+                // primary_term module installed and configured to use the spaces_taxonomy
+                // vocabulary for primary terms for a given content type, node authors
+                // will be able to select per-node canonical spaces.
+                if (!empty($node->primary_term) && isset($node->taxonomy[$node->primary_term->tid]) && $node->primary_term->vid == variable_get('spaces_taxonomy_vid', 0)) {
+                  $this->canonical = $node->primary_term->tid;
+                }
+                // Otherwise, take the first term. Doing so allows site admins to set priority based on term
+                // weight.
+                else {
+                  $this->canonical = key($node->taxonomy);
+                }
+                break;
+              case SPACES_MULTIPLE_HANDLING_RANDOM:
+                $this->canonical = SPACES_CANONICAL_UNMODIFIED;
+                // Randomize the term selected. Doing so resets the keys.
+                shuffle($terms);
+                break;
+            }
+          }
+
+          // If we have a current taxonomy space, stay in it if this node has its term.
+          if (!empty($this->id) && isset($node->taxonomy[$this->id])) {
+            $this->router_term($node->taxonomy[$this->id]);
+          }
+          else {
+            // If we have a canonical term set, use it.
+            if ($this->canonical && isset($node->taxonomy[$this->canonical])) {
+              $this->router_term($node->taxonomy[$this->canonical]);
+            }
+            // Otherwise, take the first term. Use $terms to respect randomization.
+            else {
+              $this->router_term(reset($terms));
             }
           }
         }
@@ -70,6 +115,29 @@ class space_taxonomy extends space_type_
       if ($space && (!$this->active || ($this->id != $space->id))) {
         $space->activate();
       }
+      // If we're already in the active taxonomy space, determine if there is a
+      // canonical space.
+      elseif ($space && $this->id && !empty($this->canonical) && ($this->canonical == SPACES_CANONICAL_UNMODIFIED || $this->canonical != $this->id)) {
+        // Don't bother if PURL is using separate domains or subdomains.
+        $purl_method = variable_get('purl_method_spaces_taxonomy', 0);
+        if (!empty($purl_method) && !in_array($purl_method, array('domain', 'subdomain'))) {
+          // Only act if we are on a node's primary page.
+          // Can't use menu_get_object() because it triggers a loop through spaces_menu_access().
+          if (arg(0) == 'node' && is_numeric($nid = arg(1))) {
+            $options = array('purl' => array());
+            // Set a space option if a space is designated as canonical.
+            if ($this->canonical == SPACES_CANONICAL_UNMODIFIED) {
+              $options['purl']['disabled'] = TRUE;
+            }
+            // Otherwise, the canonical URL is unmodified.
+            else {
+              $options['purl']['id'] = $this->canonical;
+              $options['purl']['provider'] = 'spaces_taxonomy';
+            }
+            purl_canonical(array('path' => 'node/' . $nid, 'options' => $options));
+          }
+        }
+      }
     }
   }
 
