summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content_distribution.info13
-rw-r--r--content_distribution.install59
-rw-r--r--content_distribution.module433
-rw-r--r--includes/content_distribution.views.inc301
-rw-r--r--modules/content_retriever/content_retriever.admin.inc445
-rw-r--r--modules/content_retriever/content_retriever.info7
-rw-r--r--modules/content_retriever/content_retriever.install65
-rw-r--r--modules/content_retriever/content_retriever.module585
-rw-r--r--modules/distributor_service/distributor_service.inc101
-rw-r--r--modules/distributor_service/distributor_service.info5
-rw-r--r--modules/distributor_service/distributor_service.module80
11 files changed, 2094 insertions, 0 deletions
diff --git a/content_distribution.info b/content_distribution.info
new file mode 100644
index 0000000..9d9bea5
--- /dev/null
+++ b/content_distribution.info
@@ -0,0 +1,13 @@
+name = Content Distributor
+description = Allows nodes to be distributed via the Services module.
+package = Content Distribution
+core = 6.x
+dependencies[] = node
+dependencies[] = date_api
+dependencies[] = distributor_service
+dependencies[] = system_service
+dependencies[] = user_service
+dependencies[] = nodequeue_service
+dependencies[] = views_service
+dependencies[] = views
+dependencies[] = nodequeue
diff --git a/content_distribution.install b/content_distribution.install
new file mode 100644
index 0000000..a53d7c3
--- /dev/null
+++ b/content_distribution.install
@@ -0,0 +1,59 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+
+function content_distribution_install() {
+ // Create tables.
+ drupal_install_schema('content_distribution');
+
+ //weight of module needs to be higher than that of the nodequeue module.
+ db_query("UPDATE {system} SET weight = 100 WHERE name = 'content_distribution'");
+
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function content_distribution_uninstall() {
+ // Remove tables.
+ drupal_uninstall_schema('content_distribution');
+ variable_del('distributed_sites');
+
+ $types = node_get_types();
+ //cycle through each contenty type and delete var if exists.
+ foreach($types as $type){
+ variable_del('distribute_content_type_'.$type->type);
+ }
+
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function content_distribution_schema() {
+ $schema['content_distribution'] = array(
+ 'description' => t('Stores node information on nodes set up for content distribution'),
+ 'fields' => array(
+ 'nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => t("The distributed node's {node}.nid."),
+ ),
+ 'published_date' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => t("The Unix timestamp the distributed node was published."),
+ ),
+ ),
+ 'primary key' => array('nid'),
+ );
+
+ return $schema;
+}
+
+
diff --git a/content_distribution.module b/content_distribution.module
new file mode 100644
index 0000000..3bbfc0e
--- /dev/null
+++ b/content_distribution.module
@@ -0,0 +1,433 @@
+<?php
+/*
+ * Module originally written by Greg Harvey
+ * http://www.drupaler.co.uk
+ */
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function content_distribution_form_alter(&$form, $form_state, $form_id) {
+ global $user;
+
+ // node add form - add published date field.
+ if($form['#id'] === 'node-form'){
+ //print_r($form);
+ // check if content type is set up for distribution...
+ $is_distributable = variable_get('distribute_content_type_'.$form['type']['#value'], 0);
+ if($is_distributable !== 0){
+ // get local date and timezone settings for form values.
+ $format = 'Y-m-d H:i';
+ $local_timezone = date_default_timezone_get();
+ $date = date_make_date(time(),$local_timezone, DATE_UNIX);
+ $date = date_format_date($date, 'custom', $format);
+
+ $form['date'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Published Date'),
+ '#collapsible' => 1,
+ '#collapsed' => 0,
+ );
+
+ $form['date']['published_date'] = array(
+ '#type' => 'date_select',
+ '#default_value' => $form['#node']->published_date,
+ '#date_format' => $format,
+ '#date_timezone' => $local_timezone,
+ '#date_year_range' => '0:+2',
+ '#date_increment' => 5,
+ );
+
+ $form['#submit'][] = 'content_distribution_node_add_form_submit';
+ }
+ }
+
+ // content type add form - add checkbox to enable / disable distribution of nodes of this type
+ if (($form_id == 'node_type_form')) {
+ $form['content_distribution'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Content Distribution Settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['content_distribution']['distribute'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Distribute content of this type'),
+ '#default_value' => variable_get('distribute_content_type_'.$form['#node_type']->type, 0),
+ '#description' => t('Allows nodes of this content type to be distributed via the services module. '),
+ );
+ $form['#submit'][] = 'content_distribution_node_type_form_submit';
+ }
+
+ //hide workflow options that have no effect on CMS
+ if ($form['#id'] == 'node-form' && $user->uid != 1) {
+ unset($form['options']['sticky']);
+ unset($form['options']['promote']);
+ }
+}
+
+/**
+ * Additional submit handler for the node type form.
+ */
+function content_distribution_node_type_form_submit($form, &$form_state) {
+ // set variable to distinguish if content type is distributable or not
+
+ if($form['content_distribution']['distribute']['#default_value'] !== $form_state['values']['distribute']){
+ if (($form_state['values']['distribute']) === 1) {
+ variable_set('distribute_content_type_'.$form['#node_type']->type, 1);
+
+ //add node ids to distribution db table.
+ $result = db_query("SELECT nid from {node} WHERE type = '%s'", $form['#node_type']->type);
+ while($nid = db_fetch_object($result)){
+ //write record in content_distribution table
+ drupal_write_record('content_distribution', $nid);
+ }
+ }else{
+ variable_set('distribute_content_type_'.$form['#node_type']->type, 0);
+
+ //delete node ids from distribution db table
+ $result = db_query("SELECT nid from {node} WHERE type = '%s'", $form['#node_type']->type);
+ while($nid = db_fetch_object($result)){
+ db_query('DELETE FROM {content_distribution} WHERE nid = %d', $nid->nid);
+ }
+ }
+ }
+}
+
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * We add the vocab_name to the taxonomy object so we can use it later
+ * on the destination server to look up a matching vocabulary - vid is not
+ * necessarily the same on both servers, hence using the name instead
+ */
+function content_distribution_nodeapi(&$node, $op, $teaser, $page) {
+ $is_distributable = variable_get('distribute_content_type_'.$node->type, 0);
+
+ switch ($op) {
+ case 'load':
+ // check if nodes of this content type are distributable
+ if($is_distributable !== 0){
+ // get the published date info from the {content_distribution} table
+ $result = db_query("SELECT published_date FROM {content_distribution} WHERE nid = %d", $node->nid);
+ $pub_date = db_result($result);
+ //format the date field
+ $format = 'Y-m-d H:i';
+ $local_timezone = date_default_timezone_get();
+ $pub_date = date_make_date($pub_date,$local_timezone, DATE_UNIX);
+ $pub_date = date_format_date($pub_date, 'custom', $format);
+
+ //taxonomy objects are not available during the load op, so we need to load them ourselves
+ $taxonomy = taxonomy_node_get_terms($node);
+ if ($taxonomy) {
+ $vocab_names = array();
+ foreach ($taxonomy as $tid => $term) {
+ $vocab_names[$tid] = taxonomy_vocabulary_load($term->vid)->name;
+ }
+ }
+ //load op must return a structured array to be appended to the $node object
+ return array(
+ 'vocab_names' => $vocab_names,
+ 'published_date' => $pub_date,
+ );
+ }
+ break;
+ case 'insert':
+ if($is_distributable !== 0){
+ // convert date to timestamp before insert
+ $published_date = $node->published_date;
+ $published_date = date_convert($published_date, DATE_DATETIME, DATE_UNIX);
+
+ db_query("INSERT INTO {content_distribution} (nid, published_date) VALUES (%d, %d)", $node->nid,$published_date);
+ }
+ break;
+ case 'update':
+ if($is_distributable !== 0){
+ // see if node is already in DB
+ $result = db_query("SELECT nid FROM {content_distribution} WHERE nid = %d", $node->nid);
+
+ // convert date to timestamp before insert
+ $published_date = $node->published_date;
+ $published_date = date_convert($published_date, DATE_DATETIME, DATE_UNIX);
+
+ // if result fetched we already have a row so just update the table with new published date
+ if (($nid = db_result($result)) != FALSE) {
+ db_query("UPDATE {content_distribution} SET published_date = %d WHERE nid = %d", $published_date, $nid);
+ }
+ }
+ break;
+ case 'delete':
+ // remove row from node delete
+ if($is_distributable !== 0){
+ db_query('DELETE FROM {content_distribution} WHERE nid = %d', $node->nid);
+
+ $sites = variable_get('distributed_sites', '');
+ if($sites){
+ //foreach site go and check if the node is there
+ //and delete it off that site.
+ foreach($sites as $k => $site){
+ $endpoint = $site['sitetext'] . '/services/xmlrpc';
+ $api_key = '';
+
+ //grab a session id from the system.connect method
+ $connect = xmlrpc($endpoint, 'system.connect');
+ $session_id = $connect['sessid'];
+
+ //call the service to delete the node on the remote site
+ $result = xmlrpc($endpoint, 'node.deleteDistributedNode', $session_id, (int)$node->nid);
+
+ if($result){
+ drupal_set_message(t('Deleted node on ' . $site['sitetext']. ' with node id ' . $result));
+ }else{
+ drupal_set_message(t('No node to delete on ' . $site['sitetext']));
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+/**
+ * Implementation of hook_views_api().
+ */
+function content_distribution_views_api() {
+ return array(
+ 'api' => 2,
+ 'path' => drupal_get_path('module', 'content_distribution') . '/includes',
+ );
+}
+
+
+/**
+ * Implementation of hook_menu().
+ */
+function content_distribution_menu(){
+ $items = array();
+
+ $items['admin/settings/distribution'] = array(
+ 'title' => t('Content Distribution Settings'),
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('content_distribution_admin_form'),
+ 'access arguments' => array('administer content distribution'),
+ 'description' => t('Settings for content distribution.'),
+ 'weight' => -10,
+ 'type' => MENU_NORMAL_ITEM
+ );
+
+ $items ['distribution/js'] = array(
+ 'title' => 'Javascript Site Form',
+ 'page callback' => 'distribution_site_js',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * Implementation of hook_perm().
+ * Here we add the permissions for access to our admin page.
+ */
+function content_distribution_perm() {
+ return array('administer content distribution');
+}
+
+/**
+ * build the admin form.
+ */
+function content_distribution_admin_form(&$form_state){
+
+ $form['site_count'] = array(
+ '#type' => 'value',
+ '#default_value' => 1,
+ '#value' => $form_state['values']['site_count'],
+ );
+ // Add a wrapper for the choices and more button.
+ $form['site_wrapper'] = array(
+ '#tree' => FALSE,
+ '#weight' => -4,
+ '#prefix' => '<div class="clear-block" id="site-choice-wrapper">' . t('Please enter the URL(s) of the remote sites you will be distributing content to in the text fields below.'),
+ '#suffix' => '</div>',
+ );
+
+ // Container for just the poll choices.
+ $form['site_wrapper']['site'] = array(
+ '#tree' => TRUE,
+ '#prefix' => '<div id="site-choices">',
+ '#suffix' => '</div>',
+ '#theme' => 'site_choices',
+ );
+
+ $sites = variable_get('distributed_sites', '');
+
+ // set up choice count
+ if(isset($form_state['values']['site_count'])){
+ $choice_count = $form_state['values']['site_count'];
+ }else if($sites){
+ $choice_count = count($sites);
+ foreach($sites as $k => $site){
+ $form_state['values']['site'][$k]['sitetext']['#value'] = $site['sitetext'];
+ }
+ }else{
+ $choice_count = 1;
+ }
+
+ // Add the current choices to the form.
+ for ($delta = 0; $delta < $choice_count; $delta++) {
+ $text = isset($form_state['values']['site'][$delta]) ? $form_state['values']['site'][$delta]['sitetext']['#value'] : '';
+ $form['site_wrapper']['site'][$delta] = _site_choice_form($delta, $text);
+ }
+
+ // ahah element to handle adding more sites
+ $form['site_wrapper']['site_more'] = array(
+ '#type' => 'submit',
+ '#value' => t('Add More Sites'),
+ '#description' => t("If the amount of boxes above isn't enough, click here to add more sites."),
+ '#weight' => 1,
+ '#submit' => array('more_sites_submit'), // If no javascript action.
+ '#ahah' => array(
+ 'path' => 'distribution/js',
+ 'wrapper' => 'site-choices',
+ 'method' => 'replace',
+ 'effect' => 'fade',
+ ),
+ );
+
+ $form['site_wrapper']['submit'] = array(
+ '#type' => 'submit',
+ '#weight' => 2,
+ '#value' => t('Save Settings'),
+ );
+
+ return $form;
+}
+
+/**
+ * validate function for the distribution settings form.
+ */
+function content_distribution_admin_form_validate($form, &$form_state){
+ // validate that the user entered a valid url
+ // in the format http://www.site.com
+ if($form_state['values']['site']){
+ foreach($form_state['values']['site'] as $k => $site){
+ if(empty($site['sitetext'])){continue;}
+ else if(!(valid_url($site['sitetext'], true))){
+ form_set_error('sitetext', 'Site ' . ($k+1) . ' is not a valid URL, please re-enter.');
+ }
+ }
+ }
+}
+
+/**
+ * submit function for the distribution settings form.
+ */
+function content_distribution_admin_form_submit($form, &$form_state){
+ //check there is actually a value in each text field
+ //then save the array of fields into a variable
+ if($form_state['values']['site']){
+ foreach($form_state['values']['site'] as $k => $site){
+ if(empty($site['sitetext'])){
+ unset($form_state['values']['site'][$k]);
+ }
+ }
+ variable_set('distributed_sites', array_values($form_state['values']['site']));
+ drupal_set_message(t('Content Distribution Settings Saved.'));
+ }
+
+}
+
+function more_sites_submit($form, &$form_state){
+ // increment the site count variable
+ $form_state['values']['site_count']++;
+}
+
+function _site_choice_form($delta, $value = '') {
+
+ $form = array(
+ '#tree' => TRUE,
+ );
+
+ // We'll manually set the #parents property of these fields so that
+ // their values appear in the $form_state['values']['site'] array.
+ $form['sitetext'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Site @n', array('@n' => ($delta + 1))),
+ '#default_value' => $value,
+ '#description' => t('Enter URL in the format http://www.mysite.com'),
+ '#parents' => array('site', $delta, 'sitetext'),
+ );
+
+ return $form;
+}
+
+/**
+ * Menu callback for AHAH additions.
+ */
+function distribution_site_js() {
+ $delta = count($_POST['site']);
+
+ // Build our new form element.
+ $form_element = _site_choice_form($delta);
+
+ //drupal_alter('form', $form_element, array(), 'poll_choice_js');
+
+ // Build the new form.
+ $form_state = array('submitted' => FALSE);
+ $form_build_id = $_POST['form_build_id'];
+ // Add the new element to the stored form. Without adding the element to the
+ // form, Drupal is not aware of this new elements existence and will not
+ // process it. We retreive the cached form, add the element, and resave.
+ if (!$form = form_get_cache($form_build_id, $form_state)) {
+ exit();
+ }
+ $form['site_wrapper']['site'][$delta] = $form_element;
+ form_set_cache($form_build_id, $form, $form_state);
+ $form += array(
+ '#post' => $_POST,
+ '#programmed' => FALSE,
+ );
+
+ // Rebuild the form.
+ $form = form_builder($_POST['form_id'], $form, $form_state);
+
+ // Render the new output.
+ $choice_form = $form['site_wrapper']['site'];
+ unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers.
+
+ $output = theme('status_messages') . drupal_render($choice_form);
+
+ drupal_json(array('status' => TRUE, 'data' => $output));
+}
+
+
+/**
+ * Theme the admin site form for choices.
+ *
+ * @ingroup themeable
+ */
+function theme_site_choices($form) {
+ // Change the button title to reflect the behavior when using JavaScript.
+ //drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-poll-more").val("'. t('Add another choice') .'"); }); }', 'inline');
+
+ foreach (element_children($form) as $key) {
+ // No need to print the field title every time.
+ //unset($form[$key]['sitetext']['#title']);
+
+ drupal_render($form[$key]['sitetext']);
+
+ }
+ $output .= drupal_render($form);
+ return $output;
+}
+
+/**
+ * Implementation of hook_theme()
+ */
+function content_distribution_theme() {
+ return array(
+ 'site_choices' => array(
+ 'arguments' => array('form' => NULL),
+ ),
+ );
+} \ No newline at end of file
diff --git a/includes/content_distribution.views.inc b/includes/content_distribution.views.inc
new file mode 100644
index 0000000..e66be42
--- /dev/null
+++ b/includes/content_distribution.views.inc
@@ -0,0 +1,301 @@
+<?php
+
+/**
+ * @file content_distribution.views.inc
+ * Provides support for the Views module.
+ *
+ */
+
+function content_distribution_views_default_views() {
+ // get list of all available queue ids to populate 'relationship'
+ // field of the view
+ $qids = nodequeue_get_all_qids(0,0);
+
+ $views = array();
+
+ $view = new view;
+$view->name = 'content_distribution_queue';
+$view->description = 'Display a list of all nodes in queue \'Content Distribution Queue\'';
+$view->tag = 'nodequeue';
+$view->view_php = '';
+$view->base_table = 'node';
+$view->is_cacheable = FALSE;
+$view->api_version = 2;
+$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+$handler = $view->new_display('default', 'Defaults', 'default');
+$handler->override_option('relationships', array(
+ 'nodequeue_rel' => array(
+ 'label' => 'queue',
+ 'required' => 1,
+ 'limit' => 1,
+ 'qids' => $qids,
+ 'id' => 'nodequeue_rel',
+ 'table' => 'node',
+ 'field' => 'nodequeue_rel',
+ 'relationship' => 'none',
+ ),
+));
+$handler->override_option('fields', array(
+ 'title' => array(
+ 'id' => 'title',
+ 'table' => 'node',
+ 'field' => 'title',
+ 'label' => '',
+ 'relationship' => 'none',
+ 'link_to_node' => 1,
+ ),
+));
+$handler->override_option('sorts', array(
+ 'position' => array(
+ 'id' => 'position',
+ 'table' => 'nodequeue_nodes',
+ 'field' => 'position',
+ 'order' => 'ASC',
+ 'relationship' => 'nodequeue_rel',
+ ),
+));
+$handler->override_option('arguments', array(
+ 'qid' => array(
+ 'default_action' => 'not found',
+ 'style_plugin' => 'default_summary',
+ 'style_options' => array(),
+ 'wildcard' => 'all',
+ 'wildcard_substitution' => 'All',
+ 'title' => '',
+ 'default_argument_type' => 'fixed',
+ 'default_argument' => '',
+ 'validate_type' => 'none',
+ 'validate_fail' => 'not found',
+ 'break_phrase' => 0,
+ 'not' => 0,
+ 'id' => 'qid',
+ 'table' => 'nodequeue_nodes',
+ 'field' => 'qid',
+ 'relationship' => 'nodequeue_rel',
+ 'default_options_div_prefix' => '',
+ 'default_argument_user' => 0,
+ 'default_argument_fixed' => '',
+ 'default_argument_php' => '',
+ 'validate_argument_node_type' => array(),
+ 'validate_argument_node_access' => 0,
+ 'validate_argument_nid_type' => 'nid',
+ 'validate_argument_vocabulary' => array(),
+ 'validate_argument_type' => 'tid',
+ 'validate_argument_php' => '',
+ ),
+ 'type' => array(
+ 'default_action' => 'not found',
+ 'style_plugin' => 'default_summary',
+ 'style_options' => array(),
+ 'wildcard' => 'all',
+ 'wildcard_substitution' => 'All',
+ 'title' => '',
+ 'default_argument_type' => 'fixed',
+ 'default_argument' => '',
+ 'validate_type' => 'none',
+ 'validate_fail' => 'not found',
+ 'id' => 'type',
+ 'table' => 'node',
+ 'field' => 'type',
+ 'relationship' => 'none',
+ 'default_options_div_prefix' => '',
+ 'default_argument_user' => 0,
+ 'default_argument_fixed' => '',
+ 'default_argument_php' => '',
+ 'validate_argument_node_type' => array(),
+ 'validate_argument_node_access' => 0,
+ 'validate_argument_nid_type' => 'nid',
+ 'validate_argument_vocabulary' => array(),
+ 'validate_argument_type' => 'tid',
+ 'validate_argument_php' => '',
+ ),
+ 'changed' => array(
+ 'id' => 'changed',
+ 'table' => 'node',
+ 'field' => 'changed',
+ 'validate_type' => 'none',
+ 'validate_fail' => 'not found',
+ 'default_argument_type' => 'php',
+ 'relationship' => 'none',
+ 'title' => '',
+ 'default_action' => 'not found',
+ 'wildcard' => '',
+ 'wildcard_substitution' => '',
+ 'default_options_div_prefix' => '',
+ 'default_argument_user' => 0,
+ 'default_argument_fixed' => '',
+ 'default_argument_php' => '',
+ 'validate_argument_node_type' => array(),
+ 'validate_argument_node_access' => 0,
+ 'validate_argument_nid_type' => 'nid',
+ 'validate_argument_vocabulary' => array(),
+ 'validate_argument_type' => 'tid',
+ 'validate_argument_php' => '',
+ ),
+));
+$handler->override_option('filters', array(
+ 'published_date' => array(
+ 'operator' => '<=',
+ 'value' => array(
+ 'type' => 'offset',
+ 'value' => 'now',
+ 'min' => '',
+ 'max' => '',
+ ),
+ 'group' => '0',
+ 'exposed' => FALSE,
+ 'expose' => array(
+ 'operator' => 'published_date_op',
+ 'label' => 'Content Distribution: Published Date',
+ 'use_operator' => 0,
+ 'identifier' => 'published_date',
+ 'optional' => 1,
+ 'remember' => 0,
+ ),
+ 'id' => 'published_date',
+ 'table' => 'content_distribution',
+ 'field' => 'published_date',
+ 'relationship' => 'none',
+ ),
+));
+$handler->override_option('access', array(
+ 'type' => 'none',
+ 'role' => array(),
+ 'perm' => '',
+));
+$handler->override_option('title', 'Queue \'Content Distribution Queue\'');
+$handler->override_option('use_pager', '1');
+$handler->override_option('use_more', 1);
+$handler->override_option('row_plugin', 'node');
+$handler->override_option('row_options', array(
+ 'teaser' => 1,
+ 'links' => 1,
+));
+$handler = $view->new_display('page', 'Page', 'page');
+$handler->override_option('path', 'nodequeue/distribution');
+$handler->override_option('menu', array(
+ 'type' => 'none',
+ 'title' => '',
+ 'weight' => 0,
+ 'name' => 'navigation',
+));
+$handler->override_option('tab_options', array(
+ 'type' => 'none',
+ 'title' => '',
+ 'weight' => 0,
+));
+$handler = $view->new_display('block', 'Block', 'block');
+$handler->override_option('items_per_page', 5);
+$handler->override_option('style_plugin', 'list');
+$handler->override_option('style_options', array(
+ 'type' => 'ul',
+));
+$handler->override_option('row_options', array(
+ 'inline' => array(),
+ 'separator' => '',
+));
+$handler->override_option('block_description', 'Queue \'Content Distribution Queue\'');
+$handler->override_option('block_caching', -1);
+
+
+
+
+ $views[$view->name] = $view;
+ return $views;
+}
+
+/**
+ * Implementation of hook_views_data_alter()
+ *
+ * Replaces the node.changed data for the views module and adds an
+ * 'Updated date' argument.
+ */
+function content_distribution_views_data_alter(&$data) {
+ // changed field
+ $data['node']['changed'] = array(
+ 'title' => t('Updated date'), // The item it appears as on the UI,
+ 'help' => t('The date the node was last updated.'), // The help that appears on the UI,
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ 'argument' => array(
+ 'field' => 'changed',
+ 'handler' => 'views_handler_argument_node_created_fulldate',
+ ),
+ );
+}
+
+/**
+ * Define extra filters for the views module.
+ */
+function content_distribution_views_data(){
+ $data = array();
+
+ // Define the base group of this table. Fields that don't
+ // have a group defined will go into this field by default.
+ $data['content_distribution']['table']['group'] = t('Content Distribution');
+
+ $data['content_distribution']['table']['base'] = array(
+ 'field' => 'nid',
+ 'title' => t('Content Distribution'),
+ 'help' => t("Content distribution relates to nodes which are set up to be distributed remotely."),
+ );
+
+ $data['content_distribution']['table']['join'] = array(
+ 'node' => array(
+ 'left_field' => 'nid',
+ 'field' => 'nid',
+ ),
+ );
+ // ----------------------------------------------------------------
+ // Fields
+
+ //published date
+ $data['content_distribution']['published_date'] = array(
+ 'title' => t('Published Date'),
+ 'help' => t('Date and time of when node is set to be published.'),
+ 'field' => array(
+ 'handler' => 'views_handler_field_date',
+ 'click sortable' => TRUE,
+ ),
+ 'sort' => array(
+ 'handler' => 'views_handler_sort_date',
+ ),
+ 'filter' => array(
+ 'handler' => 'views_handler_filter_date',
+ ),
+ );
+
+ return $data;
+}
+
+
+/**
+ * Implementation of hook_views_query_alter()
+ *
+ * Rewrites the where clause for the third argument (updated date)
+ * so it is 'greater than or equals' rather than the default 'equals'
+ * for the nodequeue view to function correctly
+ */
+function content_distribution_views_query_alter(&$view, &$query) {
+ if ($view->name == 'content_distribution_queue') {
+ $query->where[0]['clauses'][3] = "DATE_FORMAT((FROM_UNIXTIME(node.changed)), '%Y%m%%d%H%i') >= '%s'";
+ }
+}
+
+/**
+ * Implementation of hook_views_api().
+ */
+function content_distribution_views_views_api() {
+ return array(
+ 'api' => 2,
+ 'path' => drupal_get_path('module', 'content_distribution') . '/includes',
+ );
+} \ No newline at end of file
diff --git a/modules/content_retriever/content_retriever.admin.inc b/modules/content_retriever/content_retriever.admin.inc
new file mode 100644
index 0000000..d4956b7
--- /dev/null
+++ b/modules/content_retriever/content_retriever.admin.inc
@@ -0,0 +1,445 @@
+<?php
+/**
+ * form containing available admin settings
+ *
+ * @return
+ * array containing the form
+ */
+function content_retriever_admin_form() {
+ //build the checkbox fields from the current local content types
+ if (variable_get('drupal_http_request_fails', FALSE) == TRUE) {
+ drupal_set_message(t('Drupal is unable to make HTTP requests. Please reset the HTTP request status.'), 'error', FALSE);
+ watchdog('integration', t('Drupal is unable to make HTTP requests. Please reset the HTTP request status.'), array(), WATCHDOG_CRITICAL);
+ }
+ $last_run = variable_get('content_retriever_last_run',format_date(time(), 'custom', 'Y-m-d H:i'));
+ $last_run = format_date($last_run, 'custom', 'Y-m-d H:i');
+ $form = array();
+ $form['endpoint'] = array(
+ '#title' => t('Web service endpoint'),
+ '#type' => 'textfield',
+ '#required' => 'true',
+ '#default_value' => variable_get('content_retriever_endpoint', 'http://cms.defaqto.com/services/xmlrpc'),
+ );
+ $form['api_key'] = array(
+ '#title' => t('Web service API key'),
+ '#type' => 'textfield',
+ '#default_value' => variable_get('content_retriever_api_key', ''),
+ );
+ $form['username'] = array(
+ '#title' => t('Web service User name'),
+ '#type' => 'textfield',
+ '#default_value' => variable_get('content_retriever_webservice_username', ''),
+ );
+ $form['password'] = array(
+ '#title' => t('Web service Password'),
+ '#type' => 'textfield',
+ '#default_value' => variable_get('content_retriever_webservice_pass', ''),
+ );
+ $form['view'] = array(
+ '#title' => t('Name of the view to query on the distribution server'),
+ '#type' => 'textfield',
+ '#required' => 'true',
+ '#default_value' => variable_get('content_retriever_viewname', 'content_distribution_queue'),
+ );
+ $form['reset_timestamp'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Reset the time the retriever was last run'),
+ '#size' => 40,
+ '#default_value' => $last_run,
+ '#description' => t('Use the Date Format: YYYY-MM-DD HH:MM. This will only be reset if you click \'Reset last run time\''),
+ );
+ $form['submit'] = array(
+ '#value' => 'Save settings',
+ '#type' => 'submit',
+ );
+ $form['retrieve'] = array(
+ '#value' => 'Retrieve content now',
+ '#type' => 'submit',
+ '#name' => 'retrieve',
+ '#submit' => array('content_retriever_admin_retrieve_now_form_submit'),
+ );
+ $form['reset_status'] = array(
+ '#value' => 'Reset HTTP request status',
+ '#type' => 'submit',
+ '#name' => 'reset_status',
+ '#submit' => array('content_retriever_admin_clear_http_status_form_submit'),
+ );
+ $form['reset_timestamp_submit'] = array(
+ '#type' => 'submit',
+ '#value' => 'Reset last run time',
+ '#validate' => array('content_retriever_admin_reset_last_run_form_validate'),
+ '#submit' => array('content_retriever_admin_reset_last_run_form_submit'),
+ );
+ return $form;
+}
+
+
+function content_retriever_admin_types_form($form_values = null) {
+
+ // retrieve array of content types for distribution from web service
+ $endpoint = variable_get('content_retriever_endpoint', 'http://cms.defaqto.com/services/xmlrpc');
+ $api_key = variable_get('content_retriever_api_key', '');
+
+ // connect to the system service to get a session id
+ $connect = content_retriever_xmlrpc($endpoint, 'system.connect', $api_key);
+ $session_id = $connect['sessid'];
+ //retrieve content types
+ $fields = content_retriever_xmlrpc($endpoint, 'node.getAllTypes', $api_key, $session_id);
+
+ // get local content types installed.
+ $local_types = array();
+ $local_types = array_keys(node_get_types());
+
+ if(!empty($fields)){
+ $not_installed = array_diff($fields, $local_types);
+
+ // get list of installed types
+ foreach($not_installed as $item){
+ unset($fields[$item]);
+ }
+ variable_set('content_retriever_content_types', $fields);
+ }else{
+ drupal_set_message(t('Could not retrieve a list of content types from the remote server'), 'error');
+ }
+
+ // check which state of the form you are in
+ // and render form depending on if you are selecting content
+ // types or installing content types
+ if((!empty($not_installed)) && (empty($form_values['skip_install']))){
+ //define our admin form
+
+ if(empty($fields)){
+ drupal_set_message(t('There are no content types currently installed which can be retrieved from the remote instance.'), 'error');
+ }
+ $form = array();
+ $default_value = array_values($not_installed);
+ $form['content_type_information'] = array(
+ '#value' => variable_get('content_type_information', t('Below is a list of content types set up for distribution by the remote instance but which are <strong>not</strong> ' .
+ 'installed on this instance. If you do not wish to install the selected content type <strong>click skip</strong> until you are presented with the <em>content types to retrieve form</em>.')),
+ );
+ $form['not_installed'] = array(
+ '#title' => 'These content types are not installed do you want to install any now',
+ '#type' => 'radios',
+ '#options' => $not_installed,
+ '#default_value' => $default_value[0],
+ );
+ $form['skip'] = array(
+ '#value' => 'Skip',
+ '#type' => 'submit',
+ '#name' => 'skip',
+ '#submit' => array('content_retriever_admin_skip_form_submit'),
+ );
+ $form['install'] = array(
+ '#value' => 'Install',
+ '#type' => 'submit',
+ '#name' => 'install',
+ '#submit' => array('content_retriever_admin_install_types_form_submit'),
+ );
+ $form['reset_status'] = array(
+ '#value' => 'Reset HTTP request status',
+ '#type' => 'submit',
+ '#name' => 'reset_status',
+ '#submit' => array('content_retriever_admin_clear_http_status_form_submit'),
+ );
+ }else{
+ $form = array();
+ if(!empty($fields)){
+ $form['content_type_information'] = array(
+ '#value' => variable_get('content_type_retrieve_information', t('Select which content types you would like to retrieve from the remote instance.')),
+ );
+ $form['content_types'] = array(
+ '#title' => 'Content types to retrieve',
+ '#type' => 'checkboxes',
+ '#options' => $fields,
+ '#default_value' => variable_get('content_retriever_content_types', array('page', 'story')),
+ );
+ $form['submit'] = array(
+ '#value' => 'Save settings',
+ '#type' => 'submit',
+ );
+ $form['retrieve'] = array(
+ '#value' => 'Retrieve content now',
+ '#type' => 'submit',
+ '#name' => 'retrieve',
+ '#submit' => array('content_retriever_admin_retrieve_now_form_submit'),
+ );
+ $form['reset_status'] = array(
+ '#value' => 'Reset HTTP request status',
+ '#type' => 'submit',
+ '#name' => 'reset_status',
+ '#submit' => array('content_retriever_admin_clear_http_status_form_submit'),
+ );
+ }else{
+ $form['content_type_information'] = array(
+ '#value' => variable_get('no_types_installed_information', t('<p>You may need to configure the content distribution module on the remote instance to allow content to be distributed.</p><p>Or you can try resetting the HTTP request status.</p>')),
+ );
+ $form['reset_status'] = array(
+ '#value' => 'Reset HTTP request status',
+ '#type' => 'submit',
+ '#name' => 'reset_status',
+ '#submit' => array('content_retriever_admin_clear_http_status_form_submit'),
+ );
+ }
+
+ }
+
+ return $form;
+}
+
+/**
+ * submit function fires the node retrieval function
+ */
+function content_retriever_admin_retrieve_now_form_submit($form_id, $form_values) {
+ $last_run = variable_get('content_retriever_last_run', (time() - (24 * 60 * 60)) );
+ if(_content_retriever_save_nodes($last_run)){
+ drupal_set_message(t('Process complete.'));
+ variable_set('content_retriever_last_run', time());
+ }
+}
+/**
+ * Submit function to skip the content type install process.
+ */
+function content_retriever_admin_skip_form_submit($form, &$form_state) {
+ // rebuild the form but go to selection of types to retrieve
+ // instead of installing
+ $form_state['rebuild'] = TRUE;
+ $form_state['skip_install'] = true;
+}
+
+/**
+ * form validate function for the reset last time run submit button
+ * validates the user entered date.
+ */
+function content_retriever_admin_reset_last_run_form_validate($form, &$form_state){
+ $reset = $form_state['values']['reset_timestamp'];
+ // get the current timestamp and convert user entered date to
+ // timestamp to compare
+ $now = time();
+ $reset_timestamp = date_convert($reset,DATE_DATETIME, DATE_UNIX);
+
+ // set form error if invalid date format
+ if(!date_is_valid($reset, $type = DATE_DATETIME)){
+ form_set_error('reset timestamp', t('Please enter a valid date format.'));
+ }else if($reset_timestamp > $now){
+ form_set_error('reset timestamp', t('Please enter a time in the past to reset the last run time to.'));
+ }
+}
+
+/**
+ * submit handler for the reset last time run form button
+ * sets the last time run variable to whatever the user input
+ * is in the form text field.
+ */
+function content_retriever_admin_reset_last_run_form_submit($form, &$form_state){
+ $time = $form_state['values']['reset_timestamp'];
+ // convert date to unix timestamp
+ $reset = date_convert($time,DATE_DATETIME, DATE_UNIX);
+ variable_set('content_retriever_last_run', $reset);
+ drupal_set_message('Content retriever last run time set to ' . $time);
+}
+
+
+
+/**
+ * Submit function to install selected content type.
+ */
+function content_retriever_admin_install_types_form_submit($form, &$form_state) {
+ $install_type = $form_state['values']['not_installed'];
+ $query = 'destination=admin/settings/retriever/types' . '&type='.$install_type;
+ // if content type installer is available use it to install content types
+ if (module_exists('defaqto_content_type_installer')) {
+ $types_available = _defaqto_content_type_installer_get_files('content');
+
+ // see if content type .inc file is available to install
+ foreach($types_available as $type){
+ if(array_search($install_type, $type) !== false){
+ $form_state['values']['content_types'] = array($install_type);
+ $form_state['values']['taxonomy'] = array();
+ defaqto_content_type_installer_admin_form_submit(&$form, &$form_state);
+ $installed = true;
+ }
+ }
+ if(!$installed){
+ // include does not exist so go to content type add form.
+ drupal_goto('admin/content/types/add', $query);
+ }
+ }
+ else{
+ // go to content type add form.
+ drupal_goto('admin/content/types/add', $query);
+ }
+}
+
+/**
+ * submit function sets the HTTP request variable back to FALSE if it has
+ * inadvertently been made TRUE by a faulty web service/bad address
+ */
+function content_retriever_admin_clear_http_status_form_submit($form_id, $form_state) {
+ variable_set('drupal_http_request_fails', FALSE);
+ drupal_set_message(t('HTTP request status reset.'));
+}
+
+/**
+ * validate function to check user-entered data from the admin form
+ */
+function content_retriever_admin_form_validate($form, &$form_state) {
+ if (strpos($form_state['values']['endpoint'], 'http') != 0) {
+ form_set_error('endpoint', t('The service endpoint must be a valid HTTP or HTTPS web address.'));
+ }
+}
+/**
+ * submit function for 'Settings' form.
+ */
+function content_retriever_admin_form_submit($form, &$form_state){
+ // save api key and endpoint variables
+ variable_set('content_retriever_endpoint', $form_state['values']['endpoint']);
+ variable_set('content_retriever_api_key', $form_state['values']['api_key']);
+ variable_set('content_retriever_viewname', $form_state['values']['view']);
+ variable_set('content_retriever_webservice_username', $form_state['values']['username']);
+ variable_set('content_retriever_webservice_pass', $form_state['values']['password']);
+
+ drupal_set_message(t('Content Retriever Settings Saved.'));
+}
+
+/**
+ * submit function for the 'Content Types' form sets variables for this module:
+ *
+ * content_retriever_content_types
+ * an array of content types to be retrieved
+ *
+ */
+function content_retriever_admin_types_form_submit($form, &$form_state) {
+ //organise the content types in to the correct format
+ $selected_types = array();
+ $selected_types = $form_state['values']['content_types'];
+ $save_types = array();
+ $count = 0;
+ foreach ($selected_types as $selected_type) {
+ if ($selected_type != '0') {
+ $count++;
+ $save_types[] = $selected_type;
+ }
+ }
+ //inform user if the module is disabled by their content choices
+ if ($count == 0) {
+ drupal_set_message(t('No content types were selected. Module disabled.'), 'error');
+ }
+
+ //save the variables
+ variable_set('content_retriever_content_types', $save_types);
+
+ drupal_set_message(t('Settings saved.'));
+ //since this is a critical module, tell watchdog settings have changed so we have an audit trail
+ watchdog(t('integration'), t('Content Retriever Type settings were changed.'), array(), WATCHDOG_NOTICE);
+
+ // rebuild the form and keep on the retrieval selection state
+ $form_state['rebuild'] = TRUE;
+ $form_state['skip_install'] = true;
+}
+
+/**
+ * Form containing the settings for the nodequeues
+ *
+ * @return $form
+ * form array
+ */
+function content_retriever_admin_queues_form(){
+ $form = array();
+
+ //make sure we can actually make HTTP requests
+ if (variable_get('drupal_http_request_fails', FALSE) == TRUE) {
+ drupal_set_message(t('Drupal is unable to make HTTP requests. Please reset the HTTP request status.'), 'error', FALSE);
+ watchdog('integration', t('Drupal is unable to make HTTP requests. Please reset the HTTP request status.'), array(), WATCHDOG_CRITICAL);
+ } else {
+ //get list of nodequeues from remote web service
+ $api_key = variable_get('content_retriever_api_key', '');
+ $endpoint = variable_get('content_retriever_endpoint', 'http://cms.defaqto.com/services/xmlrpc');
+
+ // connect to the web service to get a session id
+ $connect = content_retriever_xmlrpc($endpoint, 'system.connect', $api_key);
+ $session_id = $connect['sessid'];
+
+ $queues = content_retriever_xmlrpc($endpoint, 'nodequeue.getQueues', $api_key, $session_id);
+ if ($queues) {
+ foreach ($queues as $queue) {
+ $queue_fields[$queue['qid']] = $queue['title'];
+ }
+ } else {
+ drupal_set_message(t('No queues available.'));
+ }
+ }
+
+ $form['queues'] = array(
+ '#title' => 'Nodequeues to interrogate',
+ '#type' => 'checkboxes',
+ '#options' => $queue_fields,
+ '#default_value' => variable_get('content_retriever_nodequeues', array()),
+ );
+ $form['submit'] = array(
+ '#value' => 'Save settings',
+ '#type' => 'submit',
+ );
+ $form['retrieve'] = array(
+ '#value' => 'Retrieve content now',
+ '#type' => 'submit',
+ '#name' => 'retrieve',
+ '#submit' => array('content_retriever_admin_retrieve_now_form_submit'),
+ );
+ $form['reset_status'] = array(
+ '#value' => 'Reset HTTP request status',
+ '#type' => 'submit',
+ '#name' => 'reset_status',
+ '#submit' => array('content_retriever_admin_clear_http_status_form_submit'),
+ );
+
+ return $form;
+}
+/**
+ * Submit function for 'queues' form.
+ */
+function content_retriever_admin_queues_form_submit($form, &$form_state){
+ //collate the nodequeues ready for saving
+ $queues = array();
+ $queues = $form_state['values']['queues'];
+ $count = 0;
+ foreach ($queues as $queue) {
+ if ($queue != '0') {
+ $count++;
+ }
+ }
+ //inform user if the module is disabled by their nodequeue choices
+ if ($count == 0) {
+ drupal_set_message(t('No nodequeues were selected. Module disabled.'), 'error');
+ }
+
+ //save the variables
+ variable_set('content_retriever_nodequeues', $queues);
+ drupal_set_message(t('Nodequeue Settings Saved.'));
+}
+
+/**
+ * theme function rendering the 'settings' admin page.
+ */
+function theme_content_retriever_admin_page() {
+ $output .= '<p><span style="color:red; font-weight: bold">Important!</span> '.t('After a failed HTTP request (e.g. web service goes down, endpoint URL is wrong, etc.) Drupal assumes it has no access to the Internet and prevents itself from trying to make HTTP requests again. You may use the "Reset HTTP request status" button below to reset Drupal and allow it to make HTTP requests again. Always try this if you are not receiving data and you don\'t know why.').'</p>';
+ $output .= drupal_get_form('content_retriever_admin_form');
+ return $output;
+}
+
+/**
+ * theme function for rendering the 'content types' admin page
+ */
+function theme_content_retriever_admin_types_page() {
+ // $output .= '<p>' .t('Choose which content types are to be retrieved.').'</p>';
+ $output .= drupal_get_form('content_retriever_admin_types_form');
+ return $output;
+}
+
+/**
+ * Theme function for rendering the 'queues' admin page
+ */
+function theme_content_retriever_admin_queues_page(){
+ $output .= drupal_get_form('content_retriever_admin_queues_form');
+ return $output;
+}
+
diff --git a/modules/content_retriever/content_retriever.info b/modules/content_retriever/content_retriever.info
new file mode 100644
index 0000000..f774c7a
--- /dev/null
+++ b/modules/content_retriever/content_retriever.info
@@ -0,0 +1,7 @@
+name = Content Retriever
+description = Queries the views.getView method exposed by the Services module on an endpoint of your choice and parses and saves the resulting nodes in the local database.
+package = Content Distribution
+core = 6.x
+dependencies[] = node
+dependencies[] = distributor_service
+dependencies[] = system_service
diff --git a/modules/content_retriever/content_retriever.install b/modules/content_retriever/content_retriever.install
new file mode 100644
index 0000000..d6db5aa
--- /dev/null
+++ b/modules/content_retriever/content_retriever.install
@@ -0,0 +1,65 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+
+function content_retriever_install() {
+ // Create tables.
+ drupal_install_schema('content_retriever');
+
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function content_retriever_uninstall() {
+ // Remove tables.
+ drupal_uninstall_schema('content_retriever');
+ //remove variables
+ variable_del('content_retriever_last_run');
+ variable_del('content_retriever_content_types');
+ variable_del('content_retriever_endpoint');
+ variable_del('content_retriever_api_key');
+ variable_del('content_retriever_viewname');
+ variable_del('content_retriever_webservice_username');
+ variable_del('content_retriever_webservice_pass');
+ variable_del('content_retriever_nodequeues');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function content_retriever_schema() {
+ $schema['content_retriever'] = array(
+ 'description' => t('Stores node information on nodes which have been retrieved over XMLRPC by the retriever module'),
+ 'fields' => array(
+ 'nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => t("The node's {node}.nid."),
+ ),
+ 'original_nid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'description' => t("The retrieved node over XMLRPC's {node}.nid. ")
+ ),
+ 'published_date' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => t("The Unix timestamp node was published."),
+ ),
+ ),
+ 'primary key' => array('nid'),
+ 'indexes' => array(
+ 'original_nid' => array('original_nid'),
+ ),
+ );
+
+ return $schema;
+}
+
+
diff --git a/modules/content_retriever/content_retriever.module b/modules/content_retriever/content_retriever.module
new file mode 100644
index 0000000..8db3fbf
--- /dev/null
+++ b/modules/content_retriever/content_retriever.module
@@ -0,0 +1,585 @@
+<?php
+/**
+ * @author Greg Harvey - http://www.drupaler.co.uk
+ */
+
+/**
+ * Implementation of hook_perm().
+ * Here we add the permissions for access to our admin page.
+ */
+function content_retriever_perm() {
+ return array('administer content retrieval');
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function content_retriever_form_alter(&$form, $form_state, $form_id) {
+ if($form_id === 'node_type_form' && $_GET['type']){
+ $form['identity']['type']['#default_value'] = $_GET['type'];
+ }
+}
+/**
+ * Implementation of hook_menu().
+ */
+function content_retriever_menu() {
+ //visual output for debug purposes
+ $last_run = variable_get('content_retriever_last_run', (time() - (24 * 60 * 60)) );
+ $items['admin/content/retriever'] = array(
+ 'title' => 'Content Retriever visual output',
+ 'description' => t('If you need to physically see the retrieved content for debug purposes, touching this page runs the processes and outputs the resulting nodes to screen.'),
+ 'page callback' => '_content_retriever_save_nodes',
+ 'page arguments' => array((string) $last_run),
+ 'access arguments' => array('access content'),
+ 'weight' => -11,
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ //the admin page
+ $items['admin/settings/retriever'] = array(
+ 'title' => 'Content Retriever Settings',
+ 'description' => t('Settings for content retrieval over web services from a central distribution server.'),
+ 'page callback' => 'theme',
+ 'page arguments' => array('content_retriever_admin_page'),
+ 'access arguments' => array('administer content retrieval'),
+ 'weight' => -10,
+ 'type' => MENU_NORMAL_ITEM,
+ 'file' => 'content_retriever.admin.inc',
+ );
+
+ $items['admin/settings/retriever/settings'] = array(
+ 'title' => 'Settings',
+ 'description' => t('Settings for content retrieval over web services from a central distribution server.'),
+ 'page callback' => 'theme',
+ 'page arguments' => array('content_retriever_admin_page'),
+ 'access arguments' => array('administer content retrieval'),
+ 'weight' => -10,
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'file' => 'content_retriever.admin.inc',
+ );
+
+ $items['admin/settings/retriever/types'] = array(
+ 'title' => 'Content Types',
+ 'description' => t('Settings for content retrieval over web services from a central distribution server.'),
+ 'page callback' => 'theme',
+ 'page arguments' => array('content_retriever_admin_types_page'),
+ 'access arguments' => array('administer content retrieval'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'content_retriever.admin.inc',
+ );
+
+ $items['admin/settings/retriever/queues'] = array(
+ 'title' => 'Nodequeues',
+ 'description' => t('Nodequeues to interrogate on remote server to retrieve distributed content.'),
+ 'page callback' => 'theme',
+ 'page arguments' => array('content_retriever_admin_queues_page'),
+ 'access arguments' => array('administer content retrieval'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'content_retriever.admin.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function content_retriever_theme() {
+ return array(
+ 'content_retriever_admin_page' => array(
+ 'arguments' => array(),
+ ),
+ 'content_retriever_admin_types_page' => array(
+ 'arguments' => array(),
+ ),
+ 'content_retriever_admin_queues_page' => array(
+ 'arguments' => array(),
+ ),
+ );
+}
+
+
+/**
+ * parses and saves nodes sent back from the web service
+ *
+ * @param $last_run
+ * timestamp - Unix time of last time process executed
+ *
+ * @return
+ * string of HTML for debug purposes only
+ */
+function _content_retriever_save_nodes($last_run) {
+ global $user;
+
+ //load variables required by web service
+ $endpoint = variable_get('content_retriever_endpoint', 'http://cms.defaqto.com/services/xmlrpc');
+ $api_key = variable_get('content_retriever_api_key', '');
+
+ //fetch node data
+ $nodes = _content_retriever_fetch_nodes($last_run);
+ if ($nodes) {
+ drupal_set_message('_content_retriever_fetch_nodes ran! No. of nodes: '.count($nodes));
+ }else{
+ drupal_set_message(t('There are no new nodes to retrieve. Process complete.'));
+ return;
+ }
+
+ if ($nodes) {
+ foreach ($nodes as $node) {
+
+ // get a handle on the original_nid stored in the local database
+ // to check if we already have a version of the incoming node
+ $result = db_query("SELECT nid FROM {content_retriever} WHERE original_nid = %d",$node->nid);
+ $nid = (int)db_result($result);
+
+ //we need to be a superuser to do this next stuff
+ $temp_user = $user;
+ $user = user_load(array('uid' => 1));
+
+ //store remote nid
+ $remote_nid = $node->nid;
+
+ //unset the CMS node ID
+ unset($node->nid);
+
+ //if we have nodes carrying this CMS nid already, use that local ID so we update them as the NID
+ if(!empty($nid)){
+ $node->nid = $nid;
+ //need to get the latest revision id
+ $result = db_query("SELECT MAX(vid) FROM {node_revisions} WHERE nid = %d",$node->nid);
+ $vid = (int)db_result($result);
+ // set revision id to latest revision and tell drupal this
+ // is a new revision
+ $node->vid = $vid;
+ $node->revision = 1;
+
+ drupal_set_message(t('Updating node with nid: '. $nid));
+ watchdog(t('integration'), t('Updating node with nid: %nid.'), array('%nid' => $nid), WATCHDOG_NOTICE);
+ }else{
+ unset($node->vid);
+ }
+
+ //unset anything unique to each instance of Drupal and save the node
+ unset($node->created);
+ unset($node->changed);
+
+ unset($node->promote);
+ unset($node->sticky);
+
+ unset($node->revision_uid);
+ unset($node->revision_timestamp);
+ unset($node->field_related_items);
+
+ unset($node->path);
+
+ unset($node->comment_count);
+ unset($node->last_comment_timestamp);
+ unset($node->last_comment_name);
+
+ unset($node->queues);
+
+ //taxonomy parsing - rewrites the taxonomy object
+ if (module_exists('taxonomy_parser')) {
+ if ($node->taxonomy) {
+ // construct array of term names from the $node->taxonomy
+ $term_categories = array();
+ foreach($node->taxonomy as $term){
+ $term_categories[$term['name']]['vocab'] = $node->vocab_names[$term['tid']];
+ }
+ // parse the taxonomy and return taxonomy array to be attached to the node object
+ $node->taxonomy = taxonomy_parser_import_taxonomy($term_categories, $node->type);
+ }
+ }else{
+ unset($node->taxonomy);
+ }
+
+ // save the node
+ node_save($node);
+
+ // check whether we are inserting a new node or updating an existing one
+ if(!empty($nid)){
+ db_query("UPDATE {content_retriever} SET published_date = %d WHERE nid = %d", $node->published_date, $node->nid);
+ }else{
+ db_query("INSERT INTO {content_retriever} (nid, original_nid, published_date) VALUES (%d, %d, %d)", $node->nid, $remote_nid, $node->published_date);
+ }
+
+ //fetch ordinary upload module attached files
+ if (module_exists('upload')) {
+ $files_array = array();
+ $files_array = $node->files;
+ $file_count = count($files_array);
+ if ($file_count > 0) {
+ // get a session id from system.service module
+ $connect = content_retriever_xmlrpc($endpoint, 'system.connect', $api_key);
+ $session_id = $connect['sessid'];
+ $files = content_retriever_xmlrpc($endpoint, 'file.getNodeFiles', $api_key, $session_id, $remote_nid);
+ foreach ($files as $file) {
+ _content_retriever_save_file($file, $node->nid, 'file');
+ }
+ }
+ }
+
+ //now stop being the superuser
+ $user = $temp_user;
+
+ //tell watchdog the process has completed with content
+ $output .= '<pre>'. htmlspecialchars(print_r($node, true)) .'</pre>';
+ }
+ watchdog(t('integration'), t('Content Retriever ran and processed '.(count($nodes) /*+ $images_count*/). ' nodes.'), array(), WATCHDOG_NOTICE);
+ } else {
+ $output .= '';
+ watchdog(t('integration'), t('Content Retriever ran but there were no new nodes.'), array(), WATCHDOG_NOTICE);
+ drupal_set_message(t('No new content.'));
+ }
+ return $output;
+}
+
+/**
+ * retrieves and builds an array of nodes from the remote resource,
+ * ready to be saved to the database
+ *
+ * @param $last_run
+ * timestamp - Unix time of last time process executed
+ *
+ * @return
+ * array containing retrieved node objects
+ */
+function _content_retriever_fetch_nodes($last_run) {
+ //define arguments for xmlrpc method call
+ //variables are set in the admin form submit function
+ $viewname = variable_get('content_retriever_viewname', 'content_distribution_queue');
+ $endpoint = variable_get('content_retriever_endpoint', 'http://cms.defaqto.com/services/xmlrpc');
+ $content_types = variable_get('content_retriever_content_types', array());
+ $queues = variable_get('content_retriever_nodequeues', array());
+ $api_key = variable_get('content_retriever_api_key', '');
+ $username = variable_get('content_retriever_webservice_username', '');
+ $password = variable_get('content_retriever_webservice_pass', '');
+
+ //warn sysadm via watchdog if no safe content types are found
+ if (count($content_types) == 0) {
+ watchdog('integration', t('Content Retriever found no valid content types - doing nothing.'), array(), WATCHDOG_WARNING);
+ drupal_set_message(t('There are no available content types installed on this instance - cannot retrieve content'), 'error');
+ return false;
+ } else {
+ $nodes = array();
+ $processed_nids = array();
+
+ foreach ($content_types as $contenttype) {
+
+ foreach ($queues as $qid => $queue) {
+ if ($queue != '0') {
+ //make the call
+//print $viewname;
+//var_dump(array($qid, $contenttype,
+//var_dump($viewname, $contenttype, $qid, format_date($last_run, 'custom', 'YmdHi'));
+
+ //we need to be logged in as webservice user with
+ //'administer nodes' permissions to be able to
+ //retrieve unpublished nodes via views.get service call
+ $connect = content_retriever_xmlrpc($endpoint, 'system.connect', $api_key);
+ $session_id = $connect['sessid'];
+ //login as webservice user
+ $login = content_retriever_xmlrpc($endpoint, 'user.login', $api_key, $session_id, $username, $password);
+ $session_id = $login['sessid'];
+
+ $result = content_retriever_xmlrpc($endpoint, 'views.get', $api_key, $session_id, $viewname, array((int)$qid, $contenttype, format_date($last_run, 'custom', 'YmdHi')));
+ if($result) {
+ foreach ($result as $node_info) {
+ //load the remote node
+ $node = _content_retriever_fetch_node($node_info['nid'], $session_id);
+ //check we haven't already got this node in the $nodes array from another queue
+ foreach($nodes as $processed_node) {
+ $processed_nids[$processed_node->nid] = $processed_node->nid;
+ }
+ if (array_search($node->nid, $processed_nids) === false) {
+ $nodes[] = $node;
+ }
+ $processed_nids = array();
+ }
+ }
+ }
+ }
+ }
+ // logout as webservice user from remote site
+ content_retriever_xmlrpc($endpoint, 'user.logout', $api_key, $session_id);
+ return $nodes;
+ }
+
+}
+
+
+/**
+ * retrieves a specific node from the remote resource,
+ * ready to be saved to the database
+ *
+ * @param $nid
+ * integer node id
+ *
+ * @param $fields
+ * optional array of CCK field names
+ *
+ * @return
+ * node object
+ */
+function _content_retriever_fetch_node($nid, $session_id, $fields = array()) {
+ //define arguments for xmlrpc method call
+ //variables are set in the admin form submit function
+ $endpoint = variable_get('content_retriever_endpoint', 'http://cms.defaqto.com/services/xmlrpc');
+ $api_key = variable_get('content_retriever_api_key', '');
+
+ $result = content_retriever_xmlrpc($endpoint, 'node.get', $api_key, $session_id, (int)$nid, $fields);
+
+ //convert returned array in to a proper object
+ if ($result) {
+ $node = new stdClass();
+ foreach($result as $k => $v){
+ $node->$k = $v;
+ }
+ return $node;
+ } else {
+ return false;
+ }
+
+}
+
+
+/**
+ * @param $file
+ * array of file data
+ *
+ * @param $nid
+ * integer: the NEW node id on the local instance
+ *
+ * @param $type
+ * string denoting type of file, possible values:
+ * - image
+ * - file
+ */
+function _content_retriever_save_file($file, $nid, $type) {
+ $file_data = base64_decode($file['file']);
+ switch ($type) {
+ case 'file':
+ //set filepath to save to
+ $file['filepath'] = file_directory_path().'/'.$file['filename'];
+
+ //save file
+ //note, safe to REPLACE because we can be certain we will not have duplicates
+ //we wish to keep - cms.defaqto.com will be handling duplicate file names
+ //so we will only ever receive unique ones here
+ file_save_data($file_data, $file['filepath'], FILE_EXISTS_REPLACE);
+
+ //write record in files table
+ drupal_write_record('files', $file);
+
+ //fetch the new fid for later use in upload table
+ $result = db_query("SELECT fid FROM {files} WHERE filename = '%s'", $file['filename']);
+ $fid_int = (int)db_result($result);
+
+ //build array for upload table insertion
+ $upload = array(
+ 'fid' => $fid_int,
+ 'nid' => $nid,
+ 'vid' => $nid,
+ 'description' => $file['filename'],
+ 'list' => 1,
+ 'weight' => 0,
+ );
+
+ //write record in upload table
+ drupal_write_record('upload', $upload);
+
+ break;
+
+ //note, we do not need to save data in the database, as the image module
+ //handles this automatically using a hook_insert implementation
+ case 'image':
+ //retrieve the actual physical filename of the image variation
+ $fullname = $file['fullname'];
+
+ //set filepath to save to
+ $file['filepath'] = file_directory_path().'/images/'.$fullname;
+
+ //see comments against case 'file' for these steps
+ file_save_data($file_data, $file['filepath'], FILE_EXISTS_RENAME);
+
+ break;
+ }
+
+}
+
+/**
+ * abstraction of the service calling code needed to retrieve nodes
+ *
+ * makes and xml-rpc call to the Services module of another Drupal
+ * installation, calling the views.get method, and returns the
+ * resulting array
+ *
+ * @param $endpoint
+ * string representing the valid URI for the web service endpoint
+ *
+ * @param $method
+ * string telling us what method to call, examples:
+ * - views.get
+ * - file.getNodeFiles
+ * - nodequeue.getQueues
+ * - node.get
+ * - image.getNodeImages
+ *
+ * @param $api_key
+ * string containing a valid API key for this service
+ *
+ * @param $param1 thru $param3
+ * various: placeholder vars for passing parameters to the web
+ * service methods - can be anything valid for the method call being
+ * invoked - empty strings by default
+ *
+ * @return
+ * array containing results of the call
+ */
+function content_retriever_xmlrpc($endpoint, $method, $api_key, $session_id = '',$param1 = '', $param2 = '', $param3 = '') {
+ //check if HTTP requests are enabled and report error if not
+ if (variable_get('drupal_http_request_fails', FALSE) == TRUE) {
+ drupal_set_message(t('Drupal is unable to make HTTP requests. Please reset the HTTP request status.'), 'error', FALSE);
+ watchdog('integration', t('Drupal is unable to make HTTP requests. Please reset the HTTP request status.'), array(), WATCHDOG_CRITICAL);
+ } else {
+
+ switch ($method) {
+ case 'system.connect':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ //$param1 is view name, $param2 is array of view arguments
+ $result = xmlrpc($endpoint, $method);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key);
+ }
+ break;
+ case 'user.login':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ $result = xmlrpc($endpoint, $method, $session_id, $param1, $param2);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id, $param1, $param2);
+ }
+ break;
+ case 'user.logout':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ $result = xmlrpc($endpoint, $method, $session_id);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id);
+ }
+ break;
+ case 'views.get':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ //$param1 is view name, $param2 is array of view arguments
+ //'default' is display_id parameter hardcoded to default.
+ $result = xmlrpc($endpoint, $method, $session_id, $param1, 'default', array(), $param2, 0, 0);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id, $param1, 'default', array(), $param2);
+ }
+ break;
+
+ case 'file.getNodeFiles':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ //$param1 is nid
+ $result = xmlrpc($endpoint, $method, $session_id, $param1);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id, $param1);
+ }
+ break;
+
+ case 'nodequeue.getQueues':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ $result = xmlrpc($endpoint, $method, $session_id);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id);
+ }
+ break;
+
+ case 'node.get':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ //$param1 is nid, $param2 is array of cck field names
+ $result = xmlrpc($endpoint, $method, $session_id, $param1, $param2);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id, $param1, $param2);
+ }
+ break;
+
+ case 'image.getNodeImages':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ //$param1 is nid
+ $result = xmlrpc($endpoint, $method, $session_id, $param1);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id, $param1);
+ }
+ break;
+
+ case 'node.getAllTypes':
+ //check for an API key - if not supplied, presume not required
+ if ($api_key == '') {
+ //$param1 is nid
+ $result = xmlrpc($endpoint, $method, $session_id);
+ } else {
+ $result = xmlrpc($endpoint, $method, $api_key, $session_id);
+ }
+ break;
+
+ default:
+ $result = array();
+ watchdog('integration', t('Unknown method call attempted.'), array(), WATCHDOG_NOTICE);
+ break;
+ }
+ return $result;
+ }
+}
+
+
+/**
+ * Implementation of hook_cron().
+ */
+function content_retriever_cron() {
+ //retrieve the time content retriever last ran
+ $last_run = variable_get('content_retriever_last_run', (time() - (24 * 60 * 60)) );
+ //set time to now for next run to use as the last run time
+ variable_set('content_retriever_last_run', time());
+ //run
+ _content_retriever_save_nodes((string) $last_run);
+}
+
+
+/**
+ * Function extending the available page variables
+ *
+ * This has been implemented to expose the last time the content
+ * retriever has ran to page.tpl. This is purely for admin purposes,
+ * so they can simply visit the homepage of any site retrieving content
+ * to easily see when the last time the retriever ran was.
+ */
+function content_retriever_preprocess_page(&$variables) {
+ $last_run = variable_get('content_retriever_last_run', (time() - (24 * 60 * 60)));
+ $variables['last_run_timestamp'] = $last_run;
+ $variables['last_run'] = format_date($last_run, 'custom', 'd/m/Y H:i');
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ *
+ * delete the record from the content_retriever table when a node
+ * is deleted.
+ */
+function content_retriever_nodeapi(&$node, $op, $teaser, $page) {
+
+ switch ($op) {
+ case 'delete':
+ // remove db row from content_retriever
+ db_query('DELETE FROM {content_retriever} WHERE nid = %d', $node->nid);
+ break;
+ }
+}
+
+
+
diff --git a/modules/distributor_service/distributor_service.inc b/modules/distributor_service/distributor_service.inc
new file mode 100644
index 0000000..88380e5
--- /dev/null
+++ b/modules/distributor_service/distributor_service.inc
@@ -0,0 +1,101 @@
+<?php
+// $Id$
+/**
+ * @author gtaylor
+ * @file
+ * Link node distribution functionalities to services module.
+ */
+
+
+/**
+ * Check if the user is allowed to access the node data.
+ *
+ */
+function distributor_service_get_access() {
+ global $user;
+ if (user_access('get any node distribution data')) {
+ return TRUE;
+ }
+}
+
+/**
+ * Check if the user is allowed to delete the node.
+ *
+ */
+function distributor_service_delete_access() {
+ global $user;
+ if((user_access('delete distributed nodes'))){
+ return TRUE;
+ }
+}
+
+
+/**
+ * Returns a specified node.
+ *
+ * @param $type
+ * String. The node Type.
+ *
+ * @return
+ * Boolean. The node is set up to be distributed or not.
+ */
+function distributor_service_get_type($type) {
+ $is_distributable = variable_get('distribute_content_type_'.$type, 0);
+ if($is_distributable !== 0){
+ return true;
+ }else{
+ return false;
+ }
+
+}
+
+/**
+ * Determine whether all content types are set up
+ * for distribution.
+ *
+ * @return
+ * Array. Array of content types with a flag if
+ * their nodes are distributable or not.
+ */
+function distributor_service_get_all_types() {
+ $content_types = node_get_types();
+ foreach($content_types as $type){
+ $is_distributable = variable_get('distribute_content_type_'.$type->type,0);
+ if($is_distributable !== 0){
+ $types[$type->type] = $type->type;
+ }
+ }
+ return $types;
+}
+
+
+/**
+ * look's up the node id on the local server based on the node id which
+ * is being deleted on the other site and delete's the corresponding node
+ *
+ * @param $nid
+ * the node id to look up
+ */
+function distributor_service_delete_node($nid){
+ global $user;
+
+ //we need to be a superuser to do this next stuff
+ //this should be refactored so the webservice logs in
+ //using the user service
+ $temp_user = $user;
+ $user = user_load(array('uid' => 1));
+
+ // find the local node id.
+ $result = db_query("SELECT nid FROM {content_retriever} WHERE original_nid = %d", $nid);
+ $node_id = (int)db_result($result);
+
+ //delete the node
+ if($node_id)
+ node_delete($node_id);
+
+ $user = $temp_user;
+ return $node_id;
+}
+
+
+
diff --git a/modules/distributor_service/distributor_service.info b/modules/distributor_service/distributor_service.info
new file mode 100644
index 0000000..ab70b4e
--- /dev/null
+++ b/modules/distributor_service/distributor_service.info
@@ -0,0 +1,5 @@
+name = Distributor Service
+description = Allows other modules to poll the content distribution module and retreive data via the Services module.
+package = Content Distribution
+core = 6.x
+dependencies[] = services \ No newline at end of file
diff --git a/modules/distributor_service/distributor_service.module b/modules/distributor_service/distributor_service.module
new file mode 100644
index 0000000..2fb3453
--- /dev/null
+++ b/modules/distributor_service/distributor_service.module
@@ -0,0 +1,80 @@
+<?php
+// $Id$
+/**
+ * @author gtaylor
+ * @file
+ * Link general node distribution functionality to services module.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function disrtibutor_service_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#services_distributor':
+ return '<p>'. t('Provides node distribution methods to services applications. Requires services.module.') .'</p>';
+ case 'admin/modules#description':
+ return t('Provides node distribution methods to services applications. Requires services.module.');
+ }
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function distributor_service_perm() {
+ return array(
+ 'get any node distribution data',
+ );
+}
+
+/**
+ * Implementation of hook_service().
+ */
+function distributor_service_service() {
+ return array(
+
+ // node.getType
+ array(
+ '#method' => 'node.getType',
+ '#callback' => 'distributor_service_get_type',
+ '#access callback' => 'distributor_service_get_access',
+ '#file' => array('file' => 'inc', 'module' => 'distributor_service'),
+ '#args' => array(
+ array(
+ '#name' => 'type',
+ '#type' => 'string',
+ '#description' => t('A node type.')
+ ),
+ ),
+ '#return' => 'boolean',
+ '#help' => t('Returns a boolean.')
+ ),
+
+ // node.getAllTypes
+ array(
+ '#method' => 'node.getAllTypes',
+ '#callback' => 'distributor_service_get_all_types',
+ '#access callback' => 'distributor_service_get_access',
+ '#file' => array('file' => 'inc', 'module' => 'distributor_service'),
+ '#args' => array(),
+ '#return' => 'array',
+ '#help' => t('Obtain whether or not nodes are flagged as distributable pieces of content.')
+ ),
+
+ // node.deleteDistributedNode
+ array(
+ '#method' => 'node.deleteDistributedNode',
+ '#callback' => 'distributor_service_delete_node',
+ '#access callback' => 'distributor_service_get_access',
+ '#file' => array('file' => 'inc', 'module' => 'distributor_service'),
+ '#args' => array(
+ array(
+ '#name' => 'nid',
+ '#type' => 'int',
+ '#description' => t('A node id.')
+ ),
+ ),
+ '#return' => 'boolean',
+ '#help' => t('Returns a boolean.')),
+ );
+}