You are here

pmpapi.module in Public Media Platform API Integration 7

Creates basic calls to the PMP API.

File

pmpapi.module
View source
<?php

/**
 * @file
 * Creates basic calls to the PMP API.
 */

/**
 * Implements hook_permisssion().
 */
function pmpapi_permission() {
  return array(
    'administer PMP API' => array(
      'title' => t('Administer the PMP API'),
      'description' => t('Perform administration tasks for the PMP API.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function pmpapi_menu() {
  $items['admin/config/services/pmp'] = array(
    'title' => 'Public Media Platform (PMP) API',
    'description' => 'Control your PMP API settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'pmpapi_config_form',
    ),
    'access arguments' => array(
      'administer PMP API',
    ),
    'file' => 'pmpapi.admin.inc',
  );
  $items['admin/config/services/pmp/api_config'] = array(
    'title' => 'API Settings',
    'weight' => -20,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/services/pmp/api_test'] = array(
    'title' => 'Test PMP API',
    'page callback' => 'pmpapi_test_api',
    'access arguments' => array(
      'administer PMP API',
    ),
    'file' => 'pmpapi.pages.inc',
  );
  return $items;
}

/**
 * Fetches a single doc from the PMP.
 *
 * @param $guid
 *   A guid.
 *
 * @return object
 *   An PMPAPIDrupal object, or NULL if errors.
 */
function pmpapi_fetch($guid, $cache = TRUE) {
  $pmp = new PMPAPIDrupal($cache);
  if (empty($pmp->errors)) {
    $pmp
      ->pull(array(
      'guid' => $guid,
    ));
  }
  return $pmp;
}

/**
 * Fetches multiple docs from the PMP, narrowed by query-type parameters.
 *
 * @param $params
 *   PMP query parameters.
 *
 * @return object
 *   An PMPAPIDrupal object, or NULL if errors.
 */
function pmpapi_query($params, $cache = TRUE) {
  $pmp = new PMPAPIDrupal($cache);
  if (empty($pmp->errors)) {
    $pmp
      ->pull($params);
  }
  return $pmp;
}

/**
 * Sends a doc to the PMP.
 *
 * @param $values
 *   Key/value pairs of data to be sent to the PMP.
 *
 * @return object
 *   An PMPAPIDrupal object.
 */
function pmpapi_send($values) {
  $pmp = new PMPAPIDrupal();
  $doc = $pmp
    ->createDoc($values);
  return $pmp
    ->push($doc);
}

/**
 * Generates a GUID (UUID v4).
 *
 * @return string
 *   A GUID (UUID v4).
 */
function pmpapi_guid() {
  $pmp = new PMPAPIDrupal();
  $guid = $pmp
    ->guid();
  return $guid;
}

/**
 * A very, very simple health check.
 *
 * @return boolean
 *   TRUE if there are no PMP errors, FALSE otherwise.
 */
function pmpapi_ping() {
  $pmp = new PMPAPIDrupal(FALSE);
  if (empty($pmp->errors)) {
    $pmp
      ->pull(array(
      'profile' => 'story',
      'limit' => 1,
    ));
    if (empty($pmp->errors) && count($pmp->query->results->docs)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Removes a doc from the PMP
 *
 * @param string $guid
 *   The GUID of the doc to be removed.
 */
function pmpapi_remove($guid) {
  $pmp = new PMPAPIDrupal();
  try {
    $pmp
      ->delete($guid);
    pmp_delete_guid_from_queue($guid);
    return $pmp;
  } catch (Exception $e) {
    drupal_set_message(t('Error deleting doc from PMP. Deletion will be re-tried at next cron run.'), 'warning');
    pmpapi_increment_delete_tries($guid);
    return FALSE;
  }
}

/**
 * Removes a GUID from the PMPAPI_DELETE_QUEUE DrupalQueue
 *
 * @param string $guid
 *   A GUID.
 */
function pmp_delete_guid_from_queue($guid) {
  $queue = DrupalQueue::get('PMPAPI_DELETE_QUEUE');
  $unused_items = array();
  $deleted_item = NULL;

  // walk through the queue, if the GUID is already in the queue, remove it.
  while ($item = $queue
    ->claimItem()) {
    if (!empty($item->data['guid']) && $item->data['guid'] == $guid) {
      $deleted_item = $item;
      $queue
        ->deleteItem($item);
      break;
    }
    else {
      $unused_items[] = $item;
    }
  }
  foreach ($unused_items as $item) {
    $queue
      ->releaseItem($item);
  }
  return $deleted_item;
}

/**
 * Increments the number of delete tries for a given GUID
 *
 * @param string $guid
 *   A GUID.
 */
function pmpapi_increment_delete_tries($guid) {
  $queue = DrupalQueue::get('PMPAPI_DELETE_QUEUE');
  $deleted_item = pmp_delete_guid_from_queue($guid);
  $tries = !empty($deleted_item->data['tries']) ? $deleted_item->data['tries'] + 1 : 1;

  // Queue the GUID so we can delete it later
  if ($tries < 3) {
    $fresh_item = array(
      'guid' => $guid,
      'tries' => $tries,
    );
    $queue
      ->createItem($fresh_item);
  }
  else {
    watchdog('pmpapi_increment_delete_tries', 'Failed to be able to delete doc from the PMP [GUID=' . $guid . ' ] after three tries. Removing GUID from queue.');
  }
}

/**
 * Implements hook_cron().
 */
function pmpapi_cron() {
  pmpapi_flush_delete_queue();
}

/**
 * Flushes the PMPAPI_DELETE_QUEUE DrupalQueue
 */
function pmpapi_flush_delete_queue() {
  $queue = DrupalQueue::get('PMPAPI_DELETE_QUEUE');
  while ($item = $queue
    ->claimItem()) {
    if (!empty($item->data['guid'])) {
      $queue
        ->releaseItem($item);
      pmpapi_remove($item->data['guid']);
    }
  }
}

/**
 * Fetches a single PMP group from the API.
 *
 * @param string $guid
 *  A PMP GUID
 *
 * @return object
 *   A PMP doc.
 */
function pmpapi_fetch_group($guid) {
  $pmp = new PMPAPIDrupal();
  $pmp
    ->pull(array(
    'guid' => $guid,
    'limit' => 1000,
  ));
  if (empty($pmp->errors) && !empty($pmp->query->results->docs[0])) {
    return $pmp->query->results->docs[0];
  }
  return array();
}

/**
 * Fetches multiple PMP groups from the API.
 *
 * @return array
 *   A list of PMP group docs.
 */
function pmpapi_fetch_groups() {
  $pmp = new PMPAPIDrupal();
  $params = array(
    'limit' => 1000,
    'profile' => 'group',
    'writeable' => 'true',
  );
  $pmp
    ->pull($params);
  if (empty($pmp->errors) && !empty($pmp->query->results->docs)) {
    return $pmp->query->results->docs;
  }
  return array();
}

/**
 * Clears locally cached PMP key/value pairs.
 *
 * @param string $key
 *   A key of a cache PMP value.
 */
function pmpapi_clear_pmp_cache($key = '') {
  $pmp = new PMPAPIDrupal();
  if (empty($pmp->errors)) {
    if ($key) {
      $pmp
        ->cacheClear($key);
    }
    else {
      $pmp
        ->cacheClearAll();
    }
  }
}

/**
 * Creates a list of all profiles.
 *
 * @return array
 *   A list of all profiles; both key and value are profile name.
 */
function pmpapi_get_profile_list() {
  $info = module_invoke_all('pmpapi_profile_info');
  drupal_alter('pmpapi_profile_info', $info);
  $keys = array_keys($info);
  return array_combine($keys, $keys);
}

/**
 * Fetches profile info for a given profile name.
 *
 * @param $profile
 *   The name of a PMP profile.
 *
 * @return array
 *   The profile info, or an empty array if profile does not exist.
 */
function pmpapi_get_profile_info($profile = 'story') {
  $info = module_invoke_all('pmpapi_profile_info');
  drupal_alter('pmpapi_profile_info', $info);
  if (!empty($info[$profile])) {
    return $info[$profile];
  }
  return array();
}

/**
 * Implements hook_pmpapi_profile_info().
 */
function pmpapi_pmpapi_profile_info() {
  $pmp['story'] = array(
    'title' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('The title of the doc.'),
      'required' => TRUE,
    ),
    'byline' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('Rendered byline as suggested by document distributor.'),
    ),
    'published' => array(
      'type' => 'datetime',
      'accepted_types' => array(
        'date',
        'datetime',
        'datestamp',
      ),
      'description' => t('Display date of the document ("published" date), in ISO 8601.'),
    ),
    'contentencoded' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
        'text_with_summary',
      ),
      'description' => t('A representation of the content which can be used literally as HTML-encoded content.'),
    ),
    'contenttemplated' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
        'text_with_summary',
      ),
      'description' => t('A representation of the content which has placeholders for rich-media assets.'),
    ),
    'description' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
      ),
      'description' => t('A representation of the content of this document without HTML.'),
    ),
    'teaser' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
      ),
      'description' => t('A short description, appropriate for showing in small spaces.'),
    ),
    'tags' => array(
      'type' => 'array',
      'accepted_types' => array(
        'text',
        'taxonomy_term_reference',
      ),
      'description' => t('Tags.'),
      'multiple' => TRUE,
    ),
    'item' => array(
      'type' => 'item',
      'accepted_types' => array(
        'image',
        'file',
        'entityreference',
      ),
      'multiple' => TRUE,
    ),
  );
  $pmp['image'] = array(
    'title' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('The title of the doc.'),
      'required' => TRUE,
    ),
    'byline' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('Rendered byline as suggested by document distributor.'),
    ),
    'description' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('A representation of the content of this document without HTML.'),
    ),
  );
  $pmp['video'] = array(
    'title' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('The title of the doc.'),
      'required' => TRUE,
    ),
    'description' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
      ),
      'description' => t('A representation of the content of this document without HTML.'),
    ),
    'teaser' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
      ),
      'description' => t('A short description, appropriate for showing in small spaces.'),
    ),
    'tags' => array(
      'type' => 'array',
      'accepted_types' => array(
        'text',
        'taxonomy_term_reference',
      ),
      'description' => t('Tags.'),
      'multiple' => TRUE,
    ),
    'published' => array(
      'type' => 'datetime',
      'accepted_types' => array(
        'date',
        'datetime',
        'datestamp',
      ),
      'description' => t('Display date of the document ("published" date), in ISO 8601.'),
    ),
    'contentencoded' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
        'text_long',
        'text_with_summary',
      ),
      'description' => t('A representation of the content which can be used literally as HTML-encoded content.'),
    ),
  );
  $pmp['audio'] = array(
    'title' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('The title of the doc.'),
      'required' => TRUE,
    ),
    'description' => array(
      'type' => 'text',
      'accepted_types' => array(
        'text',
      ),
      'description' => t('A representation of the content of this document without HTML.'),
    ),
  );
  return $pmp;
}

/**
 * Implements hook_entity_load().
 */
function pmpapi_entity_load($entities, $type) {
  foreach ($entities as $entity) {
    $ids = entity_extract_ids($type, $entity);
    $entity_id = $ids[0];
    $pmp_info = db_query("SELECT guid, permissions FROM {pmpapi_local_docs} WHERE entity_type=:entity_type AND entity_id=:id", array(
      ':entity_type' => $type,
      ':id' => $entity_id,
    ))
      ->fetchAssoc();
    if (!empty($pmp_info['guid'])) {
      $entity->pmpapi_guid = $pmp_info['guid'];
      $entity->pmpapi_permissions = unserialize($pmp_info['permissions']);
      $entity->pmpapi_permissions_added = TRUE;
    }
  }
}

/**
 * Implements hook_entity_insert().
 */
function pmpapi_entity_insert($entity, $type) {
  if (!empty($entity->pmpapi_guid)) {
    list($entity_id, $vid, $bundle) = entity_extract_ids($type, $entity);
    $permissions = !empty($entity->pmpapi_permissions) ? serialize($entity->pmpapi_permissions) : NULL;
    db_insert('pmpapi_local_docs')
      ->fields(array(
      'entity_type' => $type,
      'entity_id' => $entity_id,
      'guid' => $entity->pmpapi_guid,
      'permissions' => $permissions,
    ))
      ->execute();
  }
}

/**
 * Implements hook_entity_update().
 */
function pmpapi_entity_update($entity, $type) {
  if (!empty($entity->pmpapi_guid)) {
    list($entity_id, $vid, $bundle) = entity_extract_ids($type, $entity);
    $permissions = !empty($entity->pmpapi_permissions) ? serialize($entity->pmpapi_permissions) : NULL;
    db_merge('pmpapi_local_docs')
      ->key(array(
      'entity_type' => $type,
    ))
      ->key(array(
      'entity_id' => $entity_id,
    ))
      ->fields(array(
      'guid' => $entity->pmpapi_guid,
      'permissions' => $permissions,
    ))
      ->execute();
  }
}

/**
 * Implements hook_entity_delete().
 */
function pmpapi_entity_delete($entity, $type) {
  list($entity_id, $vid, $bundle) = entity_extract_ids($type, $entity);
  db_delete('pmpapi_local_docs')
    ->condition('entity_id', $entity_id)
    ->condition('entity_type', $type)
    ->execute();
}

/**
 * Finds a matching entity id for a given GUID.
 *
 * @param string $guid
 *   A PMP GUID
 *
 * @return integer/FALSE
 *   The entity id of the entity, or FALSE, if no entity exists.
 */
function pmpapi_get_eid_from_guid($guid) {
  return db_query('SELECT entity_id FROM {pmpapi_local_docs} WHERE guid=:guid', array(
    ':guid' => $guid,
  ))
    ->fetchField();
}

/**
 * Finds a matching entity type for a given GUID.
 *
 * @param string $guid
 *   A PMP GUID
 *
 * @return string/FALSE
 *   The entity type of the entity, or FALSE, if no entity exists.
 */
function pmpapi_get_entity_type_from_guid($guid) {
  return db_query('SELECT entity_type FROM {pmpapi_local_docs} WHERE guid=:guid', array(
    ':guid' => $guid,
  ))
    ->fetchField();
}

/**
 * Finds a matching entity for a given GUID.
 *
 * @param string $guid
 *   A PMP GUID
 *
 * @return object/FALSE
 *   The entity, or NULL, if no entity exists.
 */
function pmpapi_get_entity_from_guid($guid) {
  $entity = NULL;
  $doc = db_query('SELECT entity_id, entity_type FROM {pmpapi_local_docs} WHERE guid=:guid', array(
    ':guid' => $guid,
  ))
    ->fetchAssoc();
  if (!empty($doc['entity_id']) && !empty($doc['entity_type'])) {
    $entities = entity_load($doc['entity_type'], array(
      $doc['entity_id'],
    ));
    if (!empty($entities)) {
      $entity = array_shift($entities);
    }
  }
  return $entity;
}

/**
 * Generate a list of fields (including label as a fake field) for a given bundle.
 *
 * @param string $entity_type
 *   Name of an entity type
 *
 * @param string $bundle_name
 *   Name of an entity bundle
 *
 * @return array
 *   A list of field info (including lable as fake field, if applicable)
 */
function pmpapi_get_augmented_fields($entity_type, $bundle_name) {
  $instances = field_info_instances($entity_type, $bundle_name);
  $fields = array();
  foreach ($instances as $instance) {
    $name = $instance['field_name'];
    $fields[$name] = field_info_field($name);
    $fields[$name]['instance'] = $instance;
  }
  $entity_info = entity_get_info($entity_type);
  if (!empty($entity_info['entity keys']['label'])) {
    $label = $entity_info['entity keys']['label'];
    $fake_field = array(
      'label' => $label,
      'field_name' => $label,
      'type' => 'text',
      'instance' => array(
        'required' => TRUE,
      ),
    );
    $fields = array_merge(array(
      $label => $fake_field,
    ), $fields);
  }
  return $fields;
}

/**
 * Generate a list of instances (including label as a fake instance) for a given
 * bundle.
 *
 * @param string $entity_type
 *   Name of an entity type
 *
 * @param string $bundle_name
 *   Name of an entity bundle
 *
 * @return array
 *   A list of instance info (including label as fake instance, if applicable)
 */
function pmpapi_get_augmented_instances($entity_type, $bundle_name) {
  $instances = field_info_instances($entity_type, $bundle_name);
  $entity_info = entity_get_info($entity_type);
  if (!empty($entity_info['entity keys']['label'])) {
    $label = $entity_info['entity keys']['label'];
    $fake_instance = array(
      'label' => $label,
      'field_name' => $label,
      'widget' => array(
        'type' => 'text_textfield',
      ),
    );
    $instances = array_merge(array(
      $label => $fake_instance,
    ), $instances);
  }
  return $instances;
}

/**
 * Determines if a given PMP doc is valid (i.e., not embargoed nor expired)
 *
 * @param $doc object
 *   A PMP doc.
 *
 * @return boolean
 *   TRUE if doc is valid, FALSE if not.
 */
function pmpapi_doc_is_valid($doc) {
  $now = pmpapi_convert_timestamp();
  $from_timestamp = strtotime($doc->attributes->valid->from);
  $to_timestamp = strtotime($doc->attributes->valid->to);
  $from = pmpapi_convert_timestamp($from_timestamp);
  $to = pmpapi_convert_timestamp($to_timestamp);
  return $now >= $from && $now <= $to;
}

/**
 * Converts a given timestamp to a localized PMP-friendly date.
 *
 * @param $timestamp int
 *   A timestamp.
 *
 * @return string
 *   A localized PMP-friendly date.
 */
function pmpapi_convert_timestamp($timestamp = NULL) {
  if ($timestamp === NULL) {
    $timestamp = REQUEST_TIME;
  }
  return format_date($timestamp, 'custom', DateTime::ISO8601);
}

/**
 * Determines if a URL seems like an MP3
 *
 * @param $url
 *   A url pointing to the file
 *
 * @return bool
 *   TRUE if path seems to point to an M3U, FALSE otherwise
 */
function pmpapi_url_is_mp3($url) {
  $pieces = drupal_parse_url($url);
  return substr($pieces['path'], -4) === '.mp3';
}

/**
 * Determines if a URL seems like an M3U
 *
 * @param $url
 *   A url pointing to the file
 *
 * @return bool
 *   TRUE if path seems to point to an M3U, FALSE otherwise
 */
function pmpapi_url_is_m3u($url) {
  $pieces = drupal_parse_url($url);
  return substr($pieces['path'], -4) === '.m3u';
}

/**
 * Attempts to crack an M3U and find first link to an MP3
 *
 * @param $url
 *   A url pointing to an M3U
 *
 * @return
 *   The first MP3 URL to be found in the M3U, FALSE if no MP3 is found,
 *   NULL if the file cannot be opened.
 */
function pmpapi_get_mp3_from_m3u($url) {
  $lines = file($url);
  if ($lines) {
    foreach ($lines as $line) {
      if (pmpapi_url_is_mp3($line)) {
        return $line;
      }
    }
  }
  else {
    return NULL;
  }
}

/**
 * Return all PMP creators (who have created 1+ docs)
 *
 * @return array
 *   An array of creator name (key) and creator GUID (value)
 */
function pmpapi_get_creators() {
  $creators = array(
    'APM' => '98bf597a-2a6f-446c-9b7e-d8ae60122f0d',
    'NPR' => '6140faf0-fb45-4a95-859a-070037fafa01',
    'NPRDS' => '39b744ba-e132-4ef3-9099-885aef0ff2f1',
    'PBS' => 'fc53c568-e939-4d9c-86ea-c2a2c70f1a99',
    'PRI' => '7a865268-c9de-4b27-a3c1-983adad90921',
    'PRX' => '609a539c-9177-4aa7-acde-c10b77a6a525',
  );
  return $creators;
}

/**
 * Create a list of all PMP properties (essentially 'programs')
 *
 * @return array
 *   An array of property GUID (key) and property name (value)
 */
function pmpapi_get_properties() {
  $properties = array();
  $pmp = pmpapi_query(array(
    'profile' => 'property',
    'limit' => 10000,
  ));
  if (!empty($pmp->query->results->docs)) {
    foreach ($pmp->query->results->docs as $doc) {
      $properties[$doc->attributes->guid] = $doc->attributes->title;
    }
  }
  asort($properties);
  return $properties;
}

Functions

Namesort descending Description
pmpapi_clear_pmp_cache Clears locally cached PMP key/value pairs.
pmpapi_convert_timestamp Converts a given timestamp to a localized PMP-friendly date.
pmpapi_cron Implements hook_cron().
pmpapi_doc_is_valid Determines if a given PMP doc is valid (i.e., not embargoed nor expired)
pmpapi_entity_delete Implements hook_entity_delete().
pmpapi_entity_insert Implements hook_entity_insert().
pmpapi_entity_load Implements hook_entity_load().
pmpapi_entity_update Implements hook_entity_update().
pmpapi_fetch Fetches a single doc from the PMP.
pmpapi_fetch_group Fetches a single PMP group from the API.
pmpapi_fetch_groups Fetches multiple PMP groups from the API.
pmpapi_flush_delete_queue Flushes the PMPAPI_DELETE_QUEUE DrupalQueue
pmpapi_get_augmented_fields Generate a list of fields (including label as a fake field) for a given bundle.
pmpapi_get_augmented_instances Generate a list of instances (including label as a fake instance) for a given bundle.
pmpapi_get_creators Return all PMP creators (who have created 1+ docs)
pmpapi_get_eid_from_guid Finds a matching entity id for a given GUID.
pmpapi_get_entity_from_guid Finds a matching entity for a given GUID.
pmpapi_get_entity_type_from_guid Finds a matching entity type for a given GUID.
pmpapi_get_mp3_from_m3u Attempts to crack an M3U and find first link to an MP3
pmpapi_get_profile_info Fetches profile info for a given profile name.
pmpapi_get_profile_list Creates a list of all profiles.
pmpapi_get_properties Create a list of all PMP properties (essentially 'programs')
pmpapi_guid Generates a GUID (UUID v4).
pmpapi_increment_delete_tries Increments the number of delete tries for a given GUID
pmpapi_menu Implements hook_menu().
pmpapi_permission Implements hook_permisssion().
pmpapi_ping A very, very simple health check.
pmpapi_pmpapi_profile_info Implements hook_pmpapi_profile_info().
pmpapi_query Fetches multiple docs from the PMP, narrowed by query-type parameters.
pmpapi_remove Removes a doc from the PMP
pmpapi_send Sends a doc to the PMP.
pmpapi_url_is_m3u Determines if a URL seems like an M3U
pmpapi_url_is_mp3 Determines if a URL seems like an MP3
pmp_delete_guid_from_queue Removes a GUID from the PMPAPI_DELETE_QUEUE DrupalQueue