Index: includes/ooyala.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/includes/ooyala.pages.inc,v
retrieving revision 1.12
diff -u -r1.12 ooyala.pages.inc
--- includes/ooyala.pages.inc	11 Aug 2010 00:22:48 -0000	1.12
+++ includes/ooyala.pages.inc	29 Sep 2010 17:40:17 -0000
@@ -9,7 +9,7 @@
 /**
  * Menu callback; Display global configuration options for Ooyala fields.
  */
-function ooyala_settings_form() {
+function ooyala_settings_form(&$form_state) {
   $form['ooyala_global_pcode'] = array(
     '#type' => 'textfield',
     '#default_value' => variable_get('ooyala_global_pcode', ''),
@@ -84,16 +84,6 @@
     
   }
 
-  $form['ooyala_reporting_level'] = array(
-    '#type' => 'radios',
-    '#title' => t('Reporting level'),
-    '#options' => array(
-      '1' => t('Verbose - display errors and status messages'),
-      '0' => t('Quiet - log errors to watchdog (recommended for production sites)'),
-    ),
-    '#default_value' => variable_get('ooyala_reporting_level', 1),
-  );
-
   $autopublish_help = '';
   $autopublish_help .= t('Auto-publishing is a tool that you can use to publish content only after it has completed processing by the Ooyala servers. To set this up you need to do the following:');
   $autopublish_help .= theme('item_list', array(
@@ -115,7 +105,109 @@
     '#default_value' => variable_get('ooyala_autopublish', FALSE)
   );
 
-  return system_settings_form($form);
+  $form['ooyala_taxonomy'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Taxonomy vocabulary syncing'),
+    '#description' => t('These options allow you to syncronize <a href="!taxonomy_url">Taxonomy vocabularies</a> with <a href="!ooyala_url">Ooyala labels</a>. This can be useful to maintain useful information within both Ooyala Backlot and Drupal.', array('!taxonomy_url' => url('admin/content/taxonomy'), '!ooyala_url' => 'http://www.ooyala.com/www3/support_content_management#labels')),
+    '#access' => module_exists('taxonomy'),
+    '#tree' => TRUE,
+  );
+
+  $vocabularies = (array) module_invoke('taxonomy', 'get_vocabularies');
+  $api_active = ooyala_api_available();
+  $problems = array();
+
+  if (empty($vocabularies)) {
+    $problems[] = t('There are currently no Taxonomy vocabularies available to synchronize.');
+  }
+  if (!$api_active) {
+    $problems[] = t('The Ooyala API is currently not available. Check that your server can make HTTP requests.');
+  }
+  else {
+    $labels = ooyala_api_label_list('<root>');
+    if (count($labels) == 0) {
+      $problems[] = t('Currently there are no labels defined within <a href="!ooyala_backlot">Ooyala Backlot</a>. At least one label needs to be created in order to synchronize it with a Taxonomy vocabulary.', array('!ooyala_backlot' => 'https://backlot.ooyala.com/backlot/web'));
+    }
+  }
+
+  if (empty($problems)) {
+    $options = array();
+    foreach ($labels as $olid => $label) {
+      $label = ltrim($label, '/');
+      $options[$olid] = $label;
+    }
+
+    $default_values = variable_get('ooyala_taxonomy', array());
+    foreach ($vocabularies as $vocabulary) {
+      $form['ooyala_taxonomy'][$vocabulary->vid] = array(
+        '#title' => $vocabulary->name,
+        '#type' => 'select',
+        '#options' => array_merge(array('' => t('-- No syncing -- ')), $options),
+        '#default_value' => isset($default_values[$vocabulary->vid]) ? $default_values[$vocabulary->vid] : '',
+      );
+    }
+    $form['ooyala_taxonomy_current'] = array(
+      '#type' => 'value',
+      '#value' => $default_values,
+    );
+  }
+  else {
+    $form['ooyala_taxonomy']['#description'] .= ' <strong>' . t('Taxonomy syncing cannot be enabled for the following reasons:') . '</strong>' . theme('item_list', $problems);
+  }
+
+  $form['ooyala_reporting_level'] = array(
+    '#type' => 'radios',
+    '#title' => t('Reporting level'),
+    '#options' => array(
+      '1' => t('Verbose - display errors and status messages'),
+      '0' => t('Quiet - log errors to watchdog (recommended for production sites)'),
+    ),
+    '#default_value' => variable_get('ooyala_reporting_level', 1),
+    '#weight' => 10,
+  );
+
+  $form = system_settings_form($form);
+  $form['#submit'][] = 'ooyala_settings_form_submit';
+  return $form;
+}
+
+/**
+ * Submit handler for ooyala_settings_form().
+ *
+ * Taxonomies need special form handling. All other settings are saved by the
+ * standard system_settings_form_submit() function.
+ */
+function ooyala_settings_form_submit($form, &$form_state) {
+  $existing_vocabularies = $form_state['values']['ooyala_taxonomy_current'];
+  $pending_vocabularies = array();
+  $new_vocabularies = array();
+  foreach ($form_state['values']['ooyala_taxonomy'] as $vid => $olid) {
+    if ($olid) {
+      // If syncing is already enabled, keep it enabled.
+      if (!empty($existing_vocabularies[$vid])) {
+        $new_vocabularies[$vid] = $olid;
+      }
+      // Syncing is being enabled for the first time.
+      else {
+        // If this vocabulary has terms, add it to the list for batch operation.
+        if (count(taxonomy_get_tree($vid, 0, -1, 1))) {
+          $pending_vocabularies[$vid] = $olid;
+        }
+        // If the vocabulary is empty, just enable syncing immediately.
+        else {
+          $new_vocabularies[$vid] = $olid;
+        }
+      }
+    }
+  }
+
+  // Set the taxonomy variable immediately for existing or empty vocabularies.
+  variable_set('ooyala_taxonomy', $new_vocabularies);
+
+  // Set the redirect to enable vocabularies that have existing terms.
+  if (!empty($pending_vocabularies)) {
+    $form_state['redirect'] = array('admin/settings/ooyala/sync', array('vids' => array_keys($pending_vocabularies)));
+  }
 }
 
 /**
@@ -143,6 +235,74 @@
 }
 
 /**
+ * Menu callback; Batch process new vocabulary syncing.
+ */
+function ooyala_taxonomy_sync_confirm($form_state, $vids = array()) {
+  $form = array();
+
+  if (empty($vids) && isset($_GET['vids']) && is_array($_GET['vids'])) {
+    $vids = $_GET['vids'];
+  }
+
+  $form['vocabularies'] = array(
+    '#type' => 'value',
+    '#value' => $vids,
+  );
+
+  $vocabularies = taxonomy_get_vocabularies();
+  $vocabulary_list = array();
+  foreach ($vocabularies as $vocabulary) {
+    if (in_array($vocabulary->vid, $vids)) {
+      $vocabulary_list[] = check_plain($vocabulary->name);
+    }
+  }
+
+  $form['vocabularies'] = array(
+    '#type' => 'value',
+    '#value' => $vids,
+  );
+
+  $question = t('Sync existing terms with Ooyala?');
+  $description = '<p>' . t("The vocabularies you've chosen to sync need to be populated into Ooyala Backlot. The Drupal \"term\" data will be stored as Ooyala \"labels\". If you cancel this action, syncing will not be enabled. The following vocabularies need to be synced:") . '</p>';
+  $description .= theme('item_list', $vocabulary_list);
+  $path = 'admin/settings/ooyala';
+  return confirm_form($form, $question, $path, $description);
+}
+
+function ooyala_taxonomy_sync_confirm_submit($form, &$form_state) {
+  $batch = array(
+    'operations' => array(),
+    'finished' => 'ooyala_taxonomy_sync_finished',
+    'title' => t('Processing'),
+    'error_message' => t('The sync has encountered an error.'),
+    'file' => './'. drupal_get_path('module', 'ooyala') .'/includes/ooyala.taxonomy.inc',
+  );
+
+  foreach ($form_state['values']['vocabularies'] as $vid) {
+    $batch['operations'][] = array('ooyala_taxonomy_term_sync_push', array($vid));
+  }
+
+  batch_set($batch);
+  $form_state['redirect'] = 'admin/settings/ooyala';
+}
+
+/**
+ * Finished callback used after the initial Taxonomy sync is complete.
+ */
+function ooyala_taxonomy_sync_finished($success, $results, $operations) {
+  $message = format_plural(count($results), '1 video successfully processed.', '@count videos successfully processed.');
+
+  if ($success) {
+    $message = t('All content has been succesfully synced with Ooyala.') . ' ' . $message;
+    drupal_set_message($message);
+  }
+  else {
+    $message = t('An error occurred and synchronization did not complete.') . ' ' . $message;
+    drupal_set_message($message, 'error');
+  }
+}
+
+/**
  * Menu callback; Return AJAX data that will be sent to Ooyala on upload.
  */
 function ooyala_upload_js($thisfield) {
@@ -254,7 +414,7 @@
       }
       else {
         $data['content'] = theme('ooyala_upload_preview');
-        $data['message'] = t('A thumbnail was not able to be retrieved. Check that the video code correct and that the video has finished processing.');
+        $data['message'] = t('A thumbnail was not able to be retrieved. Check that the video code is correct and that the video has finished processing.');
         $data['error'] = 0;
       }
     }
Index: includes/ooyala.taxonomy.inc
===================================================================
RCS file: includes/ooyala.taxonomy.inc
diff -N includes/ooyala.taxonomy.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/ooyala.taxonomy.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,351 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Code responsible for checking and synchronizing labels with Ooyala.
+ */
+
+/**
+ * Retrieve a list of all labels from Ooyala and check them for changes.
+ *
+ * Note that the Ooyala module immediately notifies Ooyala of changes on the
+ * Drupal side through hook_taxonomy() and hook_nodeapi(). Therfor we only need
+ * to pull from Ooyala on cron jobs, bulk sending of information is only
+ * necessary when initially setting up a vocabulary.
+ *
+ * @see ooyala_taxonomy_term_sync_push()
+ */
+function ooyala_taxonomy_term_sync_pull() {
+  $vocabularies = taxonomy_get_vocabularies();
+  $ooyala_sync_labels = variable_get('ooyala_taxonomy', array());
+  $last_sync = variable_get('ooyala_last_sync', 0);
+
+  // Get a list of videos that have changed since last sync.
+  $videos = ooyala_api_video_query(array('updatedAfter' => $last_sync, 'orderBy' => 'updatedAt,asc', 'fields' => 'labels'));
+
+  // Update label information.
+  foreach ($ooyala_sync_labels as $vid => $ooyala_sync_label) {
+    if (empty($ooyala_sync_label)) {
+      continue;
+    }
+
+    // Get the list of labels, terms, and the term to label ID map.
+    $labels = ooyala_api_label_list($ooyala_sync_label);
+    $terms = ooyala_taxonomy_term_list($vid);
+    $map = ooyala_taxonomy_term_map($vid);
+
+    // Syncronize information from Ooyala into Drupal.
+    foreach ($labels as $olid => $label_path) {
+      $tid = array_search($olid, $map);
+      $term = $tid ? $terms[$tid] : NULL;
+      $parent_names = explode('/', substr($label_path, 1));
+      $label_name = array_pop($parent_names);
+      $parent_path = $parent_names ? '/' . implode('/', $parent_names) : '/';
+      $parent_olid = $parent_path ? array_search($parent_path, $labels) : '';
+      $parent_tid = $parent_olid ? array_search($parent_olid, $map) : 0;
+      $parent_term = $parent_tid ? $terms[$tid] : NULL;
+
+      // If the label does not yet have a taxonomy term, create one.
+      if ($tid == FALSE) {
+        $term = array(
+          'vid' => $vid,
+          'name' => $label_name,
+          'parent' => $parent_tid,
+          'olid' => $olid,
+        );
+        $term = (object) taxonomy_save_term($term);
+        $tid = $term->tid;
+        $terms[$tid] = $term;
+        $map[$tid] = $olid;
+      }
+      // The label already has a taxonomy term, check for changes.
+      else {
+        $original_term = drupal_clone($term);
+
+        // If the label has been renamed, update it on the Drupal side.
+        if ($label_name != $term->name) {
+          $term->name = $label_name;
+        }
+
+        // If the label has been moved, update the parent on the Drupal side.
+        if ($parent_tid != $term->pid) {
+          $term->pid = $parent_tid;
+        }
+
+        // Save any changes.
+        if ($original_term != $term) {
+          $term_array = (array) $term;
+          taxonomy_save_term($term_array);
+          $terms[$term->tid] = $term;
+        }
+      }
+    }
+
+    // Check for deleted labels and delete corresponding terms.
+    foreach ($map as $tid => $olid) {
+      if (is_null($olid)) {
+        taxonomy_del_term($tid);
+      }
+    }
+
+    // Update video usage of labels within this vocabulary.
+    foreach ($videos as $embedcode => $video) {
+      $nodes = ooyala_load_nodes($embedcode);
+      foreach ($nodes as $node) {
+        if ($node->nid == 1012) {
+          $node_labels = ooyala_node_labels($node, $vid);
+          $ooyala_labels = array();
+          foreach ($video['labels'] as $label_name) {
+            if ($olid = array_search($label_name, $labels)) {
+              $ooyala_labels[] = $olid;
+            }
+          }
+//          dsm($vid);
+//          dsm($node_labels);
+//          dsm($ooyala_labels);
+        }
+      }
+    }
+  }
+
+}
+
+/**
+ * Initial sync of Drupal taxonomy terms to Ooyala.
+ *
+ * This function is called when taxonomy syncing is first enabled. The normal
+ * sync routine assumes that Ooyala is always the most up-to-date source of
+ * labels (since Drupal immediately informs Ooyala of changes). However when
+ * first enabling a vocabulary for syncing, Drupal needs to populate Ooyala
+ * with labels.
+ *
+ * @param $vid
+ *   The Drupal vocabulary to send to Ooyala as labels.
+ * @param $context
+ *   The Batch API variable containing information about how many terms and
+ *   nodes remain to be processed.
+ *
+ * @see ooyala_taxonomy_term_sync_pull()
+ */
+function ooyala_taxonomy_term_sync_push($vid, &$context) {
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['vocabulary'] = taxonomy_vocabulary_load($vid);
+    $context['sandbox']['terms'] = taxonomy_get_tree($vid);
+    $context['sandbox']['max'] = count($context['sandbox']['terms']);
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['term_map'] = array();
+    foreach ($context['sandbox']['terms'] as $index => $term) {
+      $context['sandbox']['term_map'][$term->tid] = $index;
+    }
+  }
+
+  // Process terms by groups of 10.
+  $count = 10;
+  $terms = $context['sandbox']['terms'];
+  $term_map = $context['sandbox']['term_map'];
+  $current = $context['sandbox']['progress'];
+  $vocabulary = $context['sandbox']['vocabulary'];
+  $end = min($current + $count, $context['sandbox']['max']);
+  for ($current; $current <= $end ; $current++) {
+    $term = $terms[$current];
+
+    // Generate a label path.
+    $label = ooyala_taxonomy_create_label($vocabulary, $term);
+
+    // Send the term to Ooyala and retrieve a label ID.
+    $term->olid = ooyala_api_label_add($label);
+    ooyala_taxonomy_term_save($term);
+
+    // Update our progress information.
+    $context['sandbox']['progress'] = $current;
+    $context['message'] = t('Processing %title', array('%title' => $node->title));
+  }
+
+  // Inform the batch engine that we are not finished,
+  // and provide an estimation of the completion level we reached.
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+}
+
+/**
+ * Save a taxonomy term's Ooyala information to the database.
+ */
+function ooyala_taxonomy_term_save($term) {
+  static $time;
+  $time = isset($time) ? $time : time();
+
+  $update = !isset($term->updated) ? NULL : array('tid');
+  $term->updated = $time;
+  $term->olid = isset($term->olid) ? $term->olid : NULL;
+
+  if ($term->tid && $term->olid) {
+    drupal_write_record('ooyala_term', $term, $update);
+  }
+}
+
+/**
+ * Delete a taxonomy term's Ooyala information from the database.
+ */
+function ooyala_taxonomy_term_delete($term) {
+  db_query("DELETE FROM {ooyala_term} WHERE tid = %d", $term->tid);
+}
+
+/**
+ * Load an individual Drupal taxonomy term, including Ooyala Label ID (olid).
+ */
+function ooyala_taxonomy_term_load($tid) {
+  $result = db_query("SELECT * FROM {term_data} t LEFT JOIN {ooyala_term} ot ON t.tid = ot.tid WHERE t.tid = %d", $tid);
+  return db_fetch_object($result);
+}
+
+/**
+ * Load a list of Drupal taxonomy terms within a vocabulary.
+ */
+function ooyala_taxonomy_term_list($vid, $reset = FALSE) {
+  static $terms;
+
+  if (!isset($terms) || $reset) {
+    $terms = array();
+  }
+
+  if ($vid && !isset($terms[$vid])) {
+    $result = db_query("SELECT t.*, ot.olid, ot.updated FROM {term_data} t LEFT JOIN {ooyala_term} ot ON t.tid = ot.tid WHERE t.vid = %d", $vid);
+    $terms[$vid] = array();
+    while ($term = db_fetch_object($result)) {
+      $terms[$vid][$term->tid] = $term;
+    }
+
+    // Add parents after building the full list.
+    foreach ($terms[$vid] as $tid => $term) {
+      if ($term->pid && isset($terms[$vid][$term->pid])) {
+        $parent = $terms[$vid][$term->pid];
+        while ($parent->pid) {
+          $terms[$vid][$tid]->parents[] = $parent;
+          if ($terms[$vid][$parent->pid]) {
+            $parent = $terms[$vid][$parent->pid];
+          }
+        }
+      }
+    }
+  }
+
+  return $vid ? $terms[$vid] : TRUE;
+}
+
+/**
+ * Provide a term ID to Ooyala ID map.
+ */
+function ooyala_taxonomy_term_map($vid) {
+  $terms = ooyala_taxonomy_term_list($vid);
+  $map = array();
+  foreach ($terms as $tid => $term) {
+    $map[$tid] = $term->olid;
+  }
+  return $map;
+}
+
+
+/**
+ * Implementation of hook_taxonomy_term_insert().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_term_insert($term) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if (!empty($ooyala_vocabularies[$term->vid])) {
+    // Check if needing to add this term to Ooyala. If $term->olid is already
+    // set this term originated from Ooyala and doesn't need to be sent.
+    // See ooyala_taxonomy_term_sync().
+    if (!isset($term->olid)) {
+      $label = ooyala_taxonomy_create_label(NULL, $term);
+      if ($olid = ooyala_api_label_add($label)) {
+        $term->olid = $olid;
+      }
+    }
+    // Always add the entry in the ooyala_term table.
+    if (isset($term->olid)) {
+      ooyala_taxonomy_term_save($term);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy_term_update().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_term_update($term) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if ($ooyala_vocabularies[$term->vid]) {
+    // Load the Ooyala label ID and check for renamed terms.
+    $term = ooyala_taxonomy_term_load($term->tid);
+    if (isset($term->olid)) {
+      $old_label = ooyala_api_label_name($term->olid);
+      $label = ooyala_taxonomy_create_label(NULL, $term);
+      if ($old_label != $label) {
+        ooyala_api_label_rename($old_label, $label);
+        ooyala_taxonomy_term_save($term);
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy_term_delete().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taoxnomy_term_delete($term) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if ($ooyala_vocabularies[$term->vid]) {
+    // Delete any matching Ooyala label information.
+    $term = ooyala_taxonomy_term_load($term->tid);
+    if (isset($term->olid)) {
+      ooyala_api_label_delete($term->olid);
+      ooyala_taxonomy_term_delete($term);
+    }
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy_vocabulary_delete().
+ *
+ * Called from hook_taxonomy() in ooyala.module.
+ */
+function ooyala_taxonomy_vocabulary_delete($vocabulary) {
+  $ooyala_vocabularies = variable_get('ooyala_taxonomy', array());
+  if ($ooyala_vocabularies[$vocabulary->vid]) {
+    unset($ooyala_vocabularies[$vocabulary->vid]);
+    variable_set('ooyala_taxonomy', $ooyala_vocabularies);
+    db_query('DELETE FROM {ooyala_term} WHERE vid = %d', $vocabulary->vid);
+    drupal_set_message(t('The vocabulary %name is no longer synced with Ooyala. Labels within Ooyala backlot have not been affected.', array('%name' => $vocabulary->name)));
+  }
+}
+
+/**
+ * Given a vocabulary and term, create an Ooyala label.
+ */
+function ooyala_taxonomy_create_label($vocabulary = NULL, $term = NULL) {
+  static $vocabularies;
+
+  if (!isset($vocabularies)) {
+    $vocabularies = taxonomy_get_vocabularies();
+  }
+
+  if (!isset($vocabulary)) {
+    $vocabulary = $vocabularies[$term->vid];
+  }
+
+  $parents = isset($term->parents) ? $term->parents : taxonomy_get_parents($term->tid);
+
+  $label = '/' . $vocabulary->name;
+  foreach ($parents as $parent_term) {
+    $label .= '/' . $parent_term->name;
+  }
+  if ($term) {
+    $label .= '/' . $term->name;
+  }
+  return $label;
+}
Index: ooyala.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/ooyala.install,v
retrieving revision 1.1
diff -u -r1.1 ooyala.install
--- ooyala.install	10 Nov 2009 21:17:44 -0000	1.1
+++ ooyala.install	29 Sep 2010 17:40:18 -0000
@@ -7,9 +7,55 @@
  */
 
 /**
+ * Implementation of hook_schema().
+ */
+function ooyala_schema() {
+  $schema = array();
+
+  $schema['ooyala_term'] = array(
+    'description' => 'Table for storing Ooyala label to Drupal term ID pairs',
+    'fields' => array(
+      'vid' => array(
+        'description' => 'Foreign key to the vid of the {vocabulary} table.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'tid' => array(
+        'description' => 'Foreign key to the tid of the {term_data} table.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'olid' => array(
+        'description' => 'The label ID provided by Ooyala',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'updated' => array(
+        'description' => 'The last time that the matching Drupal term was updated.',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('vid', 'tid', 'olid'),
+    'indexes' => array(
+      'updated' => array('updated'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
  * Implementation of hook_install().
  */
 function ooyala_install() {
+  drupal_install_schema('ooyala');
   drupal_load('module', 'content');
   content_notify('install', 'ooyala');
 }
@@ -18,6 +64,7 @@
  * Implementation of hook_uninstall().
  */
 function ooyala_uninstall() {
+  drupal_uninstall_schema('ooyala');
   drupal_load('module', 'content');
   content_notify('uninstall', 'ooyala');
 }
@@ -37,3 +84,51 @@
   drupal_load('module', 'content');
   content_notify('disable', 'ooyala');
 }
+
+/**
+ * Install the initial Ooyala schema.
+ */
+function ooyala_update_1() {
+  $ret = array();
+  $schema = array();
+
+  $schema['ooyala_term'] = array(
+    'description' => 'Table for storing Ooyala label to Drupal term ID pairs',
+    'fields' => array(
+      'vid' => array(
+        'description' => 'Foreign key to the vid of the {vocabulary} table.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'tid' => array(
+        'description' => 'Foreign key to the tid of the {term_data} table',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'olid' => array(
+        'description' => 'The label ID provided by Ooyala',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'updated' => array(
+        'description' => 'The last time that the matching Drupal term was updated.',
+        'type' => 'int',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('vid', 'tid', 'olid'),
+    'indexes' => array(
+      'updated' => array('updated'),
+    ),
+  );
+
+  db_create_table($ret, 'ooyala_term', $schema['ooyala_term']);
+
+  return $ret;
+}
Index: ooyala.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ooyala/ooyala.module,v
retrieving revision 1.23
diff -u -r1.23 ooyala.module
--- ooyala.module	11 Aug 2010 21:09:32 -0000	1.23
+++ ooyala.module	29 Sep 2010 17:40:19 -0000
@@ -22,6 +22,15 @@
     'file' => 'includes/ooyala.pages.inc',
     'type' => MENU_NORMAL_ITEM,
   );
+  $items['admin/settings/ooyala/sync'] = array(
+    'title' => 'Sync confirmation',
+    'description' => 'Confirmation page before beginning taxonomy syncing.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ooyala_taxonomy_sync_confirm'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'includes/ooyala.pages.inc',
+    'type' => MENU_CALLBACK,
+  );
   $items['ooyala/js'] = array(
     'page callback' => 'ooyala_upload_js',
     'access arguments' => array('upload ooyala videos'),
@@ -169,7 +178,11 @@
  * Implementation of hook_cron().
  */
 function ooyala_cron() {
-  // first we will check for the list of pending thumbnails
+  if (!ooyala_api_available()) {
+    return;
+  }
+
+  // First we will check for the list of pending thumbnails.
   $thumbnails_to_get = variable_get('ooyala_pending_thumbnails', array());
 
   foreach ($thumbnails_to_get as $key => $embedcode) {
@@ -182,6 +195,12 @@
   }
 
   variable_set('ooyala_pending_thumbnails', $thumbnails_to_get);
+
+  // Sync Taxonomy terms.
+  if (module_exists('taxonomy') && variable_get('ooyala_taxonomy', array())) {
+    module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+    ooyala_taxonomy_term_sync_pull();
+  }
 }
 
 /**
@@ -200,14 +219,7 @@
       // If the auto-publish feature is enabled, display a message on
       // unpublished nodes on view.
       if ($page && $node->status == 0 && isset($node->ooyala_field_names) && variable_get('ooyala_autopublish', 0)) {
-        $embedcodes = array();
-        foreach ($node->ooyala_field_names as $field_name) {
-          foreach ($node->{$field_name} as $delta => $item) {
-            if (!empty($item['value'])) {
-              $embedcodes[] = $item['value'];
-            }
-          }
-        }
+        $embedcodes = ooyala_node_embedcodes($node);
         if ($embedcodes) {
           $videos = ooyala_api_video_load_multiple($embedcodes);
           $videos_processing = FALSE;
@@ -222,6 +234,32 @@
         }
       }
       break;
+    case 'presave':
+      $labels = ooyala_node_labels($node);
+      $embedcodes = ooyala_node_embedcodes($node);
+      if ($labels) {
+        // Remove all existing labels for synced vocabularies.
+        ooyala_api_video_label_remove($embedcodes, array_keys($labels));
+        $all_labels = array();
+        foreach ($labels as $group) {
+          $all_labels = array_merge($all_labels, $group);
+        }
+        ooyala_api_video_label_add($embedcodes, $all_labels);
+      }
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_taxonomy().
+ */
+function ooyala_taxonomy($op, $type, $array = NULL) {
+  // Taxonomy hooks are broken out separately in ooyala.taxonomy.inc.
+  module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+  $function = 'ooyala_taxonomy_' . $type . '_' . $op;
+  if (function_exists($function)) {
+    $object = (object) $array;
+    $function($object);
   }
 }
 
@@ -229,7 +267,7 @@
  * Implementation of hook_form_alter().
  */
 function ooyala_form_alter(&$form, &$form_state, $form_id) {
-  // Disable multiple values.
+  // Disable multiple values in the field configuration.
   if ($form_id == 'content_field_edit_form' && isset($form['#field']) && $form['#field']['widget']['type'] == 'ooyala_upload') {
     $form['field']['multiple']['#type'] = 'value';
   }
@@ -354,37 +392,6 @@
 }
 
 /**
- * Implementation of hook_widget_settings().
- */
-function ooyala_widget_settings($op, $widget) {
-  switch ($op) {
-    case 'form':
-      $form = array();
-      if (module_exists('taxonomy')){
-        $vocs = taxonomy_get_vocabularies();
-        foreach($vocs as $voc) {
-          $vocabularies[$voc->vid]=$voc->name;
-        }
-        $form['ooyala_tags'] = array(
-          '#type' => 'select',
-          '#multiple' => true,
-          '#title' => 'Vocabulary tags to send',
-          '#description' => t('Tags from these vocabularies will be sent to ooyala to help classify the videos in the backlot'),
-          '#options' => $vocabularies,
-          '#default_value' => $widget['ooyala_tags'],
-        );
-      }
-      $return = $form;
-      break;
-    case 'save':
-      $return = array('ooyala_tags');
-      break;
-  }
-
-  return $return;
-}
-
-/**
  * Implementation of hook_widget().
  */
 function ooyala_widget(&$form, &$form_state, $field, $items, $delta = NULL) {
@@ -483,6 +490,101 @@
 }
 
 /**
+ * Fetch a list of all embed codes within a node.
+ */
+function ooyala_node_embedcodes($node) {
+  $embedcodes = array();
+  $ooyala_field_names = ooyala_field_names($node->type);
+  foreach ($ooyala_field_names as $field_name) {
+    foreach ($node->{$field_name} as $delta => $item) {
+      if (!empty($item['value'])) {
+        $embedcodes[] = $item['value'];
+      }
+    }
+  }
+  return $embedcodes;
+}
+
+/**
+ * Fetch a list of all Ooyala labels on a node that should be synced.
+ *
+ * @param $node
+ *   A full node object.
+ * @param $mode
+ *   Either "id" or "name", the format in which labels are preferred.
+ */
+function ooyala_node_labels($node, $restrict_vid = NULL) {
+  $labels = array();
+
+  $ooyala_vocabularies = array_filter(variable_get('ooyala_taxonomy', array()));
+  if (!module_exists('taxonomy') || !isset($node->taxonomy) || empty($ooyala_vocabularies)) {
+    return $labels;
+  }
+
+  module_load_include('inc', 'ooyala', 'includes/ooyala.taxonomy');
+  $vocabularies = taxonomy_get_vocabularies();
+  if (isset($restrict_vid)) {
+    $ooyala_vocabularies = array_intersect_key($ooyala_vocabularies, array($restrict_vid => ''));
+  }
+
+  foreach ($ooyala_vocabularies as $vid => $olid) {
+    $ooyala_label = ooyala_api_label_name($olid);
+
+    $labels[$ooyala_label] = array();
+
+    // Tags are simple, since they don't need to match Drupal term IDs.
+    if (isset($node->taxonomy['tags'][$vid])) {
+      $terms = drupal_explode_tags($node->taxonomy['tags'][$vid]);
+      foreach ($terms as $term) {
+        $labels[$ooyala_label][] = $ooyala_label . '/' . $term;
+      }
+    }
+
+    // Normal controlled vocabularies, load Ooyala label IDs for each term.
+    $terms = array();
+
+    // Node form taxonomy format.
+    if (isset($node->taxonomy[$vid])) {
+      if (is_array($node->taxonomy[$vid])) {
+        foreach ($node->taxonomy[$vid] as $tid) {
+          if ($tid) {
+            $terms[$tid] = ooyala_taxonomy_term_load($tid);
+          }
+        }
+      }
+    }
+
+    // Node object format.
+    foreach ($node->taxonomy as $term) {
+      if (is_object($term) && $term->vid == $vid) {
+        $terms[] = ooyala_taxonomy_term_load($term->tid);
+      }
+    }
+
+    // Loop through all terms and build a list of labels.
+    foreach ($terms as $tid => $term) {
+      if (is_object($term)) {
+        // Use the label ID if available, as it tends to be more accurate.
+        if (isset($term->olid)) {
+          $labels[$ooyala_label][$term->tid] = $term->olid;
+        }
+        // Otherwise return a label as a path, assume it will be inserted.
+        else {
+          $parents = taxonomy_get_parents($tid);
+          $labels[$ooyala_label] = '';
+          foreach ($parents as $parent_term) {
+            $labels[$ooyala_label] .= '/' . $parent_term->name;
+          }
+          $labels[$ooyala_label] .= '/' . $term->name;
+        }
+      }
+    }
+  }
+
+  return (isset($restrict_vid) && isset($ooyala_label)) ? $labels[$ooyala_label] : $labels;
+}
+
+/**
  * Check if Ooyala APIs are available.
  */
 function ooyala_api_available() {
@@ -495,6 +597,209 @@
 }
 
 /**
+ * Retrieve a label ID from Ooyala based on label name.
+ */
+function ooyala_api_label_id($label_name) {
+  // TODO: Swap out for a more efficient API call when Ooyala adds ability to
+  // retrieve label name based on ID.
+  $label_name = rtrim($label_name, '/');
+  $parent_name = substr($label_name, strrpos('/', $label_name) + 1);
+  $labels = ooyala_api_label_list($parent_name);
+  return array_search($label_name, $labels);
+}
+
+/**
+ * Retrieve a label name from Ooyala based on label ID.
+ */
+function ooyala_api_label_name($label_id) {
+  $labels = ooyala_api_label_list();
+  return isset($labels[$label_id]) ? $labels[$label_id] : FALSE;
+}
+
+/**
+ * Retreive a list of labels from Ooyala.
+ *
+ * @param $parent
+ *   The parent label under which to retrieve all labels. Use "/" to
+ *   retrieve all labels. Use '<root>' to return only the root labels.
+ * @param $reset
+ *   Reset the internal cache of labels to retrieve.
+ */
+function ooyala_api_label_list($parent = '/', $reset = FALSE) {
+  static $labels;
+
+  if ($reset || !isset($labels)) {
+    $labels = array();
+  }
+
+  if (!isset($labels[$parent])) {
+    $labels[$parent] = array();
+
+    // Query for any new videos that need to be retrieved.
+    module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+    $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+    $params = array(
+      'mode' => 'listSubLabels',
+      'label' => $parent,
+    );
+    // Currently it is impossible to get just a list of top-level terms, so we
+    // have to get the full list.
+    if ($parent == '<root>') {
+      $params['label'] = '/';
+    }
+
+    $request = $ooyala_api->signed_params($params);
+    $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+    // Populate all the video entries with the results.
+    if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+      foreach ($xmldoc->children() as $result) {
+        $row = (array) $result;
+        $label = rtrim($row[0], '/');
+
+        // If building a list of root labels, skip all non-root labels.
+        $last_slash = strrpos($label, '/');
+        if ($parent == '<root>' && $last_slash !== 0) {
+          continue;
+        }
+        $labels[$parent][$row['@attributes']['id']] = $label;
+      }
+    }
+
+    // Sort all the labels so that all root items are first, then all second
+    // level, etc. This sets us up for width-first operations.
+    uasort($labels[$parent], '_ooyala_label_sort');
+  }
+
+  return $labels[$parent];
+}
+
+/**
+ * Add a label to the Ooyala label hierarchy.
+ *
+ * @param $label_name
+ *   The name of the label, including the label path.
+ *
+ * @return
+ *   The Ooyala Label ID of the new label.
+ */
+function ooyala_api_label_add($label_name) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'createLabels',
+    'labels' => is_string($label_name) ? $label_name : implode(',', $label_name),
+  ));
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  // Retrieve the label ID for the newly created label.
+  $success = FALSE;
+  $label_id = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+    if ($success) {
+      $label_id = ooyala_api_label_id($label_name);
+    }
+  }
+
+  return $label_id;
+}
+
+/**
+ * Rename a label in Ooyala.
+ *
+ * This function currently only accepts label paths, not label IDs.
+ */
+function ooyala_api_label_rename($old_label_name, $new_label_name) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'renameLabel',
+    'oldlabel' => $old_label_name,
+    'newlabel' => $new_label_name,
+  ));
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
+ * Delete a label in Ooyala.
+ */
+function ooyala_api_label_delete($label_name) {
+  // TODO: Ooyala APIs currently do not provide a way to delete labels.
+  return TRUE;
+
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(array(
+    'mode' => 'deleteLabels', // TODO: Correct when this becomes available.
+    'labels' => $label_name,
+  ));
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
+ * Retreive a non-cached list of videos from Ooyala.
+ *
+ * @param $params
+ *   A list of Ooyala parameters on which to base the search.
+ * @see http://www.ooyala.com/support/docs/backlot_api#query
+ */
+function ooyala_api_video_query($params = array()) {
+  $videos = array();
+
+  // Query for any new videos that need to be retrieved.
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params($params);
+  $response = drupal_http_request("http://api.ooyala.com/partner/query?$request");
+
+    // Populate all the video entries with the results.
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    foreach ($xmldoc->children() as $result) {
+      $video = (array) $result;
+
+      // Convert labels.
+      if (isset($video['labels'])) {
+        $labels = $video['labels']->children();
+        $video['labels'] = array();
+        foreach ($labels as $label) {
+          // TODO: It would be nice if labels had a label ID here to use as the
+          // key. API change required from Ooyala.
+          $video['labels'][] = $label[0];
+        }
+      }
+
+      // Not sure what the stat property is used for yet.
+      if (isset($video['stat'])) {
+        unset($video['stat']);
+      }
+      $videos[$video['embedCode']] = $video;
+    }
+  }
+
+  return $videos;
+}
+
+/**
  * Load an individual video from Ooyala.
  */
 function ooyala_api_video_load($embedcode) {
@@ -512,7 +817,7 @@
     $videos = array();
   }
 
-  // Build a list of videos to query that we do not yet have retreived.
+  // Build a list of videos to query that we do not yet have retrieved.
   $query_embedcodes = $embedcodes;
   foreach ($embedcodes as $key => $embedcode) {
     if (!isset($videos[$embedcode])) {
@@ -520,7 +825,7 @@
     }
   }
 
-  // Query for any new videos that need to be retreived.
+  // Query for any new videos that need to be retrieved.
   if (!empty($query_embedcodes)) {
     module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
 
@@ -528,12 +833,11 @@
     $request = $ooyala_api->signed_params(
       array(
         'embedCode' => implode(',', $query_embedcodes),
-        'expires' => time() + 30,
       )
     );
     $response = drupal_http_request("http://api.ooyala.com/partner/query?$request");
 
-    // Default each embedcode to FALSE in case it is not retreived.
+    // Default each embedcode to FALSE in case it is not retrieved.
     foreach ($query_embedcodes as $embedcode) {
       $videos[$embedcode] = FALSE;
     }
@@ -564,6 +868,60 @@
 }
 
 /**
+ * Remove labels from a video.
+ */
+function ooyala_api_video_label_remove($embedcode, $labels, $recursive = TRUE) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(
+    array(
+      'embedCodes' => is_string($embedcode) ? $embedcode : implode(',', $embedcode),
+      'labels' => is_string($labels) ? $labels : implode(',', $labels),
+      'mode' => 'unassignLabels',
+      'includeSublabels' => $recursive ? 'true' : 'false',
+    )
+  );
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
+ * Set labels on a video.
+ *
+ * @param $embedcode
+ *   An Ooyala embed code.
+ * @param $labels
+ *   An array of labels to set on a video.
+ */
+function ooyala_api_video_label_add($embedcode, $labels) {
+  module_load_include('inc', 'ooyala', 'includes/ooyala.partner_api');
+
+  $ooyala_api = new OoyalaPartnerAPI(variable_get('ooyala_global_secret', ''), variable_get('ooyala_global_pcode', ''));
+  $request = $ooyala_api->signed_params(
+    array(
+      'embedCodes' => is_string($embedcode) ? $embedcode : implode(',', $embedcode),
+      'labels' => is_string($labels) ? $labels : implode(',', $labels),
+      'mode' => 'assignLabels',
+    )
+  );
+  $response = drupal_http_request("http://api.ooyala.com/partner/labels?$request");
+
+  $success = FALSE;
+  if ($response->data && $xmldoc = @simplexml_load_string($response->data)) {
+    $success = ((string) $xmldoc[0]) == 'ok';
+  }
+
+  return $success;
+}
+
+/**
  * Fetch and save a video thumbnail from the Ooyala server.
  */
 function ooyala_api_fetch_image($embedcode) {
@@ -576,7 +934,6 @@
       'embedCode' => $embedcode,
       'range' => '0-1',
       'resolution' => '1024x768',
-      'expires' => time() + 30,
     )
   );
   $response = drupal_http_request("http://api.ooyala.com/partner/thumbnails?$request");
@@ -646,7 +1003,7 @@
     $width = variable_get('ooyala_video_width', 400);
   }
   if (empty($height)) {
-    $height = variable_Get('ooyala_video_height', 300);
+    $height = variable_get('ooyala_video_height', 300);
   }
 
   return sprintf('<script src="http://www.ooyala.com/player.js?autoplay=%d&width=%d&height=%d&embedCode=%s"></script>', $autoplay, $width, $height, $embedcode );
@@ -931,15 +1288,24 @@
 }
 
 /**
- * Utility function for eliminating the need to have the "text" module activated
- *
+ * Custom sort function to sort labels, putting root items first.
  */
+function _ooyala_label_sort($a, $b) {
+  $cmp = substr_count($a, '/') - substr_count($b, '/');
+  if ($cmp == 0) {
+    $cmp = strcasecmp($a, $b);
+  }
+  return $cmp;
+}
 
-if(!function_exists('_text_allowed_values')) {
+/**
+ * Utility function for eliminating the need to have the "text" module activated
+ */
+if (!function_exists('_text_allowed_values')) {
   function _text_allowed_values($element) {
     $field = content_fields($element['#field_name'], $element['#type_name']);
     if (($allowed_values = content_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) {
       return $allowed_values[$element['#item']['value']];
     }
   }
-}
\ No newline at end of file
+}
