You are here

social_content.module in Social Content 7

Same filename and directory in other branches
  1. 7.2 social_content.module

Social Content module.

File

social_content.module
View source
<?php

/**
 * @file
 * Social Content module.
 */

/**
 * Implements hook_menu().
 */
function social_content_menu() {
  $info = array();
  $info['admin/config/services/social-content'] = array(
    'title' => 'Social content',
    'page callback' => 'social_content_overview_types',
    'access arguments' => array(
      'administer social content',
    ),
    'file' => 'social_content.admin.inc',
  );
  $info['admin/config/services/social-content/%social_content_type/edit'] = array(
    'title' => 'Edit Social content type',
    'title callback' => 'social_content_page_title',
    'title arguments' => array(
      4,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'social_content_form',
      4,
    ),
    'access arguments' => array(
      'administer social content',
    ),
    'file' => 'social_content.admin.inc',
  );
  $info['admin/config/services/social-content/%social_content_type/run'] = array(
    'title' => 'Run Social content import',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'social_content_import_run_form',
      4,
    ),
    'access arguments' => array(
      'administer social content',
    ),
    'file' => 'social_content.admin.inc',
  );
  return $info;
}

/**
 * Menu argument loader: loads a social_content_type type by string.
 *
 * @param string $name
 *   The machine-readable name of a social_content_type to load.
 *
 * @return object|FALSE
 *   A node type object or FALSE if $name does not exist.
 */
function social_content_type_load($name) {
  $types = social_content_get_types();
  if (isset($types[$name])) {
    return $types[$name];
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_permission().
 */
function social_content_permission() {
  return array(
    'administer social content' => array(
      'title' => t('Administer social content'),
      'description' => t('Administer social content.'),
    ),
  );
}

/**
 * Title callback: Returns the full title for a social content type.
 *
 * @param object $social_content_type
 *   The social content type object.
 *
 * @return string
 *   An unsanitized string that from the title of the social content type.
 *
 * @see social_content_menu()
 */
function social_content_page_title($social_content_type) {
  return $social_content_type['title'];
}

/**
 * Gets all social content types.
 *
 * Invokes hook_social_content_info and hook_social_content_info_alter.
 */
function social_content_get_types() {
  $social_content_types =& drupal_static(__FUNCTION__);
  if (!isset($social_content_types)) {
    $social_content_types = module_invoke_all('social_content_info');
    foreach ($social_content_types as $name => $type) {
      $social_content_types[$name]['name'] = $name;
    }
    drupal_alter('social_content_info', $social_content_types);
  }
  return $social_content_types;
}

/**
 * Get the settings for a social_content_type.
 *
 * @param object $social_content_type
 *   The social content type object.
 *
 * @return array
 *   An array of settings.
 */
function social_content_get_settings($social_content_type) {
  $settings =& drupal_static(__FUNCTION__);
  if (!$settings || !isset($settings[$social_content_type['name']])) {
    $defaults = array(
      'limit' => 200,
      'auto_publish' => 1,
      'enabled' => 0,
    );
    if (isset($social_content_type['additional_settings']) && is_array($social_content_type['additional_settings'])) {
      $defaults += $social_content_type['additional_settings'];
    }
    $social_content_settings = variable_get('social_content_' . $social_content_type['name'], array());
    $social_content_settings += $defaults;
    drupal_alter('social_content_settings', $social_content_settings, $social_content_type);
    $settings[$social_content_type['name']] = $social_content_settings;
  }
  return $settings[$social_content_type['name']];
}

/**
 * Implements hook_cron().
 *
 * Run through the social content types and import the posts.
 */
function social_content_cron() {
  $types = social_content_get_types();
  foreach ($types as $social_content_type) {
    $settings = social_content_get_settings($social_content_type);
    if ($settings['enabled']) {
      social_content_run_import($social_content_type, $settings);
    }
  }
}

/**
 * Implements hook_cronapi().
 *
 * Elysia cron hook.
 * Split all social content imports into their own jobs for elysia cron.
 * Stagger the time by five minutes for each run.
 */
function social_content_cronapi($op, $job = NULL) {
  $items = array();
  $mins = 0;
  foreach (social_content_get_types() as $social_content_type) {
    $settings = social_content_get_settings($social_content_type);
    if ($settings['enabled']) {
      $mins += 5;
      if ($mins > 60) {
        $mins -= 60;
      }
      $items['social_content_' . $social_content_type['name']] = array(
        'description' => t('Social content import for !title', array(
          '!title' => $social_content_type['title'],
        )),
        'rule' => $mins . ' */2 * * *',
        'callback' => 'social_content_run_import',
        'arguments' => array(
          $social_content_type,
          $settings,
        ),
      );
    }
  }
  return $items;
}

/**
 * Implements hook_cron_alter().
 *
 * If we are using elysia cron, then remove the default social_content_cron.
 * We don't want it running multiple times.
 */
function social_content_cron_alter(&$data) {
  if (isset($data['social_content_cron']) && module_exists('elysia_cron')) {
    unset($data['social_content_cron']);
  }
}

/**
 * Run an import for a particular social content type.
 *
 * Gathers data from the registered callback (defined in "data_callback").
 *
 * @param object $social_content_type
 *   The social content type object.
 *
 * @param array $settings
 *   The settings array @see social_content_get_settings()
 *
 * @return int|bool
 *   The number of posts imported, or FALSE if it failed.
 */
function social_content_run_import($social_content_type, $settings) {
  $data = FALSE;
  $langcode = social_content_get_content_type_langcode($social_content_type['content_type']);
  $last_id = social_content_get_last_id($social_content_type, $langcode);
  $function = $social_content_type['data_callback'];
  if (function_exists($function)) {
    $data = $function($settings, $last_id);
    if (!empty($data)) {
      $result = social_content_import_data($data, $social_content_type, $settings, $langcode);
      if ($result) {
        watchdog('social_content', 'Social content imported !count !type nodes', array(
          '!count' => $result,
          '!type' => $social_content_type['title'],
        ), WATCHDOG_INFO);
      }
      return $result;
    }
  }
  if ($data === FALSE) {
    watchdog('social_content', 'Unable to process !social_content_type, unable to fetch feed', array(
      '!social_content_type' => $social_content_type['title'],
    ));
    return FALSE;
  }
}

/**
 * Go through the posts and process each one.
 *
 * Checks whether the post already exists (if so it's skipped)
 * Checks whether we've hit the limit.
 *
 * @param array $data
 *   An array of posts, normally retrieved through the "data_callback".
 * @param object $social_content_type
 *   The social content type object.
 * @param array $settings
 *   The settings array @see social_content_get_settings()
 * @param settings $langcode
 *   The language code being used.
 *
 * @return int|bool
 *   The number of posts imported, or FALSE if it failed.
 */
function social_content_import_data($data, $social_content_type, $settings, $langcode = LANGUAGE_NONE) {
  $count = 0;
  foreach ($data as $post) {
    $external_id_remote_field = key($social_content_type['external_id_field_mapping']);
    if (!isset($post->{$external_id_remote_field})) {
      watchdog('social_content', 'Unable to read external ID for !social_content_type', array(
        '!social_content_type' => $social_content_type['title'],
      ), WATCHDOG_WARNING);
      return FALSE;
    }
    $external_id = $post->{$external_id_remote_field};
    if (social_content_post_exists($external_id, $social_content_type, $langcode)) {
      break;
    }
    if ($settings['limit'] > 0 && $count > $settings['limit']) {
      break;
    }
    if (social_content_save_post($post, $external_id, $langcode, $social_content_type, $settings)) {
      $count++;
    }
  }
  return $count;
}

/**
 * Get the langcode to use for the import.
 *
 * @param string $content_type
 *   The node content type we are using.
 *
 * @return string
 *   Language code
 */
function social_content_get_content_type_langcode($content_type) {

  // Check if content can be localised
  // TODO: We are assuming that if a content type is translatable
  // we should always use the current language.
  // Language should come from the default that
  // the node would have been created in.
  if (module_exists('locale') && locale_multilingual_node_type($content_type)) {
    global $language_content;
    return $language_content->language;
  }
  return LANGUAGE_NONE;
}

/**
 * Check for existing content.
 *
 * @return bool
 *   Existing content was found.
 */
function social_content_post_exists($external_id, $social_content_type, $langcode) {
  $field_name = current($social_content_type['external_id_field_mapping']);
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'node')
    ->propertyCondition('type', $social_content_type['content_type'])
    ->propertyCondition('language', $langcode)
    ->fieldCondition($field_name, 'value', $external_id)
    ->execute();
  if (!empty($result)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Get the last external id that was imported.
 *
 * @param object $social_content_type
 *   The social content type object.
 * @param string $langcode
 *   The language code string.
 *
 * @return string|bool
 *   The latest external id, or NULL if nothing was found.
 */
function social_content_get_last_id($social_content_type, $langcode) {
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'node')
    ->propertyCondition('type', $social_content_type['content_type'])
    ->propertyCondition('language', $langcode)
    ->propertyOrderBy('created', 'DESC')
    ->range(0, 1)
    ->execute();
  if (!empty($result) && isset($result['node'])) {
    $nodes = node_load_multiple(array_keys($result['node']));
    $node = array_shift($nodes);
    $field_name = current($social_content_type['external_id_field_mapping']);
    $field = field_get_items('node', $node, $field_name, $langcode);
    if ($field && isset($field[0]['value'])) {
      return $field[0]['value'];
    }
  }
  return NULL;
}

/**
 * Save the current post.
 *
 * Creates a entity_metadata_wrapper and sets the the title and external id.
 * The wrapper is then passed to the "post_callback" function (defined in
 * hook_social_content_info())
 *
 * @see social_content_import_data()
 * @see entity_metadata_wrapper()
 *
 * @param objet $post
 *   The post object
 * @param string $external_id
 *   The external ID string
 * @param string $langcode
 *   The language code to be used.
 * @param object $social_content_type
 *   The social content type object.
 * @param array $settings
 *   The settings array @see social_content_get_settings()
 *
 * @return int|bool
 *   The created entity id.
 */
function social_content_save_post($post, $external_id, $langcode, $social_content_type, $settings) {
  $path = $social_content_type['name'] . '/' . $external_id;
  $values = array(
    'type' => $social_content_type['content_type'],
    'uid' => 1,
    'status' => $settings['auto_publish'],
    'promote' => 0,
    'language' => $langcode,
    'created' => time(),
    'path' => array(
      'alias' => $path,
    ),
  );
  $entity = entity_create('node', $values);
  $wrapper = entity_metadata_wrapper('node', $entity);

  // Set Title.
  $wrapper->title
    ->set($social_content_type['title'] . ' : ' . $external_id);

  // Set External ID field.
  $extenal_id_field = current($social_content_type['external_id_field_mapping']);
  $wrapper->{$extenal_id_field}
    ->set($external_id);
  if (isset($social_content_type['post_callback'])) {
    $function = $social_content_type['post_callback'];
    if (function_exists($function)) {
      $result = $function($wrapper, $post, $external_id, $settings);
      if (!$result) {
        return FALSE;
      }
    }
  }

  // Save the node.
  $wrapper
    ->save();
  return $wrapper
    ->getIdentifier();
}

/**
 * Fetches an external image and saves it to the files_managed table.
 *
 * Helper function, used by modules implementing social content types.
 *
 * @param string $url
 *   The url for where to grab the image from.
 * @param string $field_name
 *   Machine name of the field this image will be attached to.
 *   Required to work out the uri_scheme.
 * @param string $min_resolution
 *   (Optional) The min_resolution to check for.
 *
 * @return object|bool
 *   Returns a file object ready to be attached to a field (or FALSE).
 */
function social_content_get_image_file($url, $field_name, $min_resolution = NULL) {
  if (!$url) {
    return FALSE;
  }
  $result = drupal_http_request($url);
  if ($result->code != 200) {
    return FALSE;
  }

  // Find the image extension
  // Facebook has generic /graph/picture urls that redirect.
  // So look for a redirect url first in the response.
  if (isset($result->redirect_url)) {
    $path_info = pathinfo($result->redirect_url);
  }
  else {
    $path_info = pathinfo($url);
  }

  // TODO: Create a nice filename from the content type.
  $filename = $path_info['basename'];
  $field_info = field_info_field($field_name);
  $field_uri_scheme = $field_info['settings']['uri_scheme'];
  $file = file_save_data($result->data, $field_uri_scheme . '://' . $filename, FILE_EXISTS_RENAME);
  if (!$file) {
    return FALSE;
  }
  if ($min_resolution) {
    $validators = array(
      'file_validate_is_image' => array(),
      'file_validate_image_resolution' => array(
        0,
        $min_resolution,
      ),
    );
    if (file_validate($file, $validators)) {
      return FALSE;
    }
  }

  // Add additional fields (required for adding to a file field).
  return array(
    'fid' => $file->fid,
    'filename' => $file->filename,
    'filemime' => $file->filemime,
    'uid' => 1,
    'uri' => $file->uri,
    'status' => 1,
    'display' => 1,
  );
}

/**
 * Strip non utf8 characters that can sometimes come through.
 *
 * Helper function, used by modules implementing social content types.
 *
 * @param string $text
 *   Filtered text.
 */
function social_content_validate_text($text) {

  // Solution found here: http://stackoverflow.com/a/4266468
  $text = preg_replace('/[^(\\x20-\\x7F)]*/', '', $text);
  return $text;
}

Functions

Namesort descending Description
social_content_cron Implements hook_cron().
social_content_cronapi Implements hook_cronapi().
social_content_cron_alter Implements hook_cron_alter().
social_content_get_content_type_langcode Get the langcode to use for the import.
social_content_get_image_file Fetches an external image and saves it to the files_managed table.
social_content_get_last_id Get the last external id that was imported.
social_content_get_settings Get the settings for a social_content_type.
social_content_get_types Gets all social content types.
social_content_import_data Go through the posts and process each one.
social_content_menu Implements hook_menu().
social_content_page_title Title callback: Returns the full title for a social content type.
social_content_permission Implements hook_permission().
social_content_post_exists Check for existing content.
social_content_run_import Run an import for a particular social content type.
social_content_save_post Save the current post.
social_content_type_load Menu argument loader: loads a social_content_type type by string.
social_content_validate_text Strip non utf8 characters that can sometimes come through.