You are here

cmis_sync.module in CMIS API 6.2

File

cmis_sync/cmis_sync.module
View source
<?php

/**
 * Config sample:
 *
 * $conf['cmis_sync_map'] = array(
 *  'page' => array(
 *    'enabled' => FALSE,
 *    'cmis_type' => 'document',
 *    'cmis_root' => '/Company Home/Guest Home/',
 *    'content_field' => 'body',
 *    'fields' => array(
 *      'title'=>'title',
 *      array('drupal'=>'nid', 'cmis'=>'custom_field', 'cmis to drupal' => FALSE, 'drupal to cmis' => TRUE)
 *    )
 *   )
 *  );
 */

/**
 * Implementation of hook_menu() for CMIS sync module.
 */
function cmis_sync_menu() {
  foreach (node_get_types() as $type) {
    $type_name = $type->type;
    $items['admin/settings/cmis/sync/' . $type_name] = array(
      'title' => 'Sync ' . $type_name,
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        'cmis_sync_admin_form',
        $type_name,
      ),
      'access arguments' => array(
        'administer cmis',
      ),
      'file' => 'cmis_sync.admin.inc',
      'type' => MENU_NORMAL_ITEM,
      'weight' => 1,
    );
  }
  return $items;
}

/**
 * Implementation of hook_theme() for CMIS sync module.
 */
function cmis_sync_theme() {
  return array(
    'cmis_sync_admin_field_map_table' => array(
      'arguments' => array(
        'form' => NULL,
      ),
      'file' => 'cmis_sync.theme.inc',
    ),
  );
}

/**
 * Implementation of hook_nodeapi() for CMIS sync module.
 */
function cmis_sync_nodeapi(&$node, $op, $teaser, $page) {
  if (in_array($op, array(
    'insert',
    'update',
    'delete',
  ))) {
    $cmis_object = _cmis_sync_drupal_to_cmis_prepare($node);
    if ($cmis_object && !isset($node->cmis_sync_disabled)) {
      module_load_include('api.inc', 'cmis');
      switch ($op) {

        // Node created
        case 'insert':
          try {
            $repository = cmisapi_getRepositoryInfo();

            // Destination folder properties. Each Drupal node content type has it own synchronization folder.
            $cmis_parent = cmisapi_getProperties($repository->repositoryId, $cmis_object->parentId);

            // Creating a new document
            $cmis_objectId = cmisapi_createDocument($repository->repositoryId, $cmis_object->type, $cmis_object->properties, $cmis_parent->id, $cmis_object->content);

            // Updating CMIS reference object properties
            cmisapi_updateProperties($repository->repositoryId, $cmis_objectId, NULL, $cmis_object->properties);
          } catch (CMISException $e) {
            cmis_error_handler('cmis_sync_nodeapi', $e);
            return;
          }

          // Saving CMIS reference id in {cmis_sync_node} table
          db_query('INSERT INTO {cmis_sync_node} (nid, cmis_objectId, changed_timestamp) VALUES (%d, \'%s\', %d)', $node->nid, $cmis_objectId, $_SERVER['REQUEST_TIME']);
          break;

        // Node updated
        case 'update':

          // Send updates only if the current node has a CMIS reference id.
          if ($cmis_objectId = db_result(db_query('SELECT cmis_objectId FROM {cmis_sync_node} WHERE nid = %d', $node->nid))) {
            try {
              $repository = cmisapi_getRepositoryInfo();

              // Updating CMIS reference object content
              cmisapi_setContentStream($repository->repositoryId, $cmis_objectId, TRUE, $cmis_object->content, $cmis_object->properties);

              // Updating CMIS reference object properties
              cmisapi_updateProperties($repository->repositoryId, $cmis_objectId, NULL, $cmis_object->properties);
            } catch (CMISException $e) {
              cmis_error_handler('cmis_sync_nodeapi', $e);
              return;
            }

            // Update changed timestamp
            db_query('UPDATE {cmis_sync_node} SET changed_timestamp=%d WHERE nid = %d', $_SERVER['REQUEST_TIME'], $node->nid);
          }
          break;

        // Node delete
        case 'delete':
          if ($cmis_object->sync_deletes && ($cmis_objectId = db_result(db_query('SELECT cmis_objectId FROM {cmis_sync_node} WHERE nid = %d', $node->nid)))) {
            try {

              // Delete CMIS reference object content
              $repository = cmisapi_getRepositoryInfo();
              $cmis_object_properties = cmisapi_getProperties($repository->repositoryId, $cmis_objectId);
              cmisapi_deleteObject($repository->repositoryId, $cmis_object_properties->id);
            } catch (CMISException $e) {
              cmis_error_handler('cmis_sync_nodeapi', $e);
              return;
            }

            // Delete sync refs
            db_query('DELETE FROM {cmis_sync_node} WHERE nid = %d', $node->nid);
          }
          break;
      }
    }
  }
}

/**
 * Implementation of hook_cron
 *  
 * @todo 
 *  Synchronize individual node as well. Maybe exporting a hook, might allow other modules to implement their own sync logic.  
 *  Handle SQL injection for CMIS query calls. Low priority.   
 *  
 */
function cmis_sync_cron() {
  module_load_include('api.inc', 'cmis');
  module_load_include('inc', 'node', 'node.pages');
  try {
    $repository = cmisapi_getRepositoryInfo();
  } catch (CMISException $e) {
    cmis_error_handler('cmis_sync_cron', $e);
    return;
  }
  $sync_map = variable_get('cmis_sync_map', array());
  $sync_map_changed = FALSE;
  foreach ($sync_map as $node_type => $sync_map_type) {

    // Check if sync is enabled for this Drupal content type
    if (!array_key_exists('enabled', $sync_map_type) || !$sync_map_type['enabled']) {
      continue;
    }
    try {

      // Handle CMIS updates
      _cmis_sync_cmis_to_drupal_handle_updates($repository, $sync_map_type, $node_type);

      // Handle CMIS deletes
      if ($sync_map_type['deletes']) {
        _cmis_sync_cmis_to_drupal_handle_deletes($repository, $sync_map_type, $node_type);
      }

      // Update CMIS sync setting
      if ($sync_map_type['full_sync_next_cron']) {
        $sync_map[$node_type]['full_sync_next_cron'] = 0;
        $sync_map_changed = TRUE;
      }
    } catch (CMISException $e) {
      cmis_error_handler('cmis_sync_cron', $e);
    }
  }

  // Save CMIS sync settings
  if ($sync_map_changed) {
    variable_set('cmis_sync_map', $sync_map);
  }
}

/**
 * Maps a drupal node to a cmis_object
 * 
 * @param $node
 * @return stdClass $cmis_object wrapper
 */
function _cmis_sync_drupal_to_cmis_prepare($node) {
  $sync_map = variable_get('cmis_sync_map', array());

  // Is cmis sync enabled for this node type?
  if ($sync_map[$node->type] && $sync_map[$node->type]['enabled']) {
    $cmis_object = new stdClass();

    // CMIS destination type
    $cmis_object->type = $sync_map[$node->type]['cmis_type'];

    // CMIS destination folder id
    $cmis_object->parentId = drupal_urlencode($sync_map[$node->type]['cmis_root']);

    // Map Drupal node fields to cmis object properties
    $cmis_object->properties = array(
      'title' => $node->title,
    );
    foreach ($sync_map[$node->type]['fields'] as $drupal_field => $cmis_field) {
      if (is_string($cmis_field)) {
        $cmis_object->properties[$cmis_field] = _cmis_sync_node_field_value($nodem, $drupal_field);
      }
      elseif (is_array($cmis_field)) {
        if (array_key_exists('drupal to cmis', $cmis_field) && $cmis_field['drupal to cmis'] === False) {
          continue;
        }
        $cmis_object->properties[$cmis_field['cmis']] = _cmis_sync_node_field_value($node, $cmis_field['drupal']);
      }
      else {
        throw new CMISException(t('Unknown field map type. Expects "string" or "array". Received @type', array(
          '@type' => gettype($cmis_field),
        )));
      }
    }

    // Map Drupal node field as object's content
    if (array_key_exists('content_field', $sync_map[$node->type])) {
      $cmis_object->content = _cmis_sync_node_field_value($node, $sync_map[$node->type]['content_field']);

      // Setting content's content-type
      $cmis_object->properties['content-type'] = 'text/html';
      $content_type = content_types($node->type);
      $content_field_name = $sync_map[$node->type]['content_field'];
      if (array_key_exists($content_field_name, $content_type['fields'])) {
        if ($content_type['fields'][$content_field_name]['type'] == 'filefield') {
          $content_field = $node->{$content_field_name};
          $cmis_object->properties['content-type'] = $content_field[0]['filemime'];
        }
      }
    }

    // Sync deletes flag
    $cmis_object->sync_deletes = $sync_map[$node->type]['deletes'];
    return $cmis_object;
  }
  return FALSE;
}

/**
 * Maps a cmis_object to a drupal node.
 *   
 * @param $cmis_repository 
 * @param $sync_map_type Sync rules for current type
 * @param $cmis_object
 * @return $drupal_node
 * 
 * @todo 
 *  Add workflow properties
 */
function _cmis_sync_cmis_to_drupal_prepare($repository, $sync_map_type, $node_type, $cmis_object) {
  module_load_include('api.inc', 'cmis');
  if ($sync_map_type['enabled']) {
    $drupal_nid = NULL;

    // Identify Drupal nid
    if (!array_key_exists('nid', $sync_map_type['fields'])) {
      if ($cmis_sync_node = db_fetch_object(db_query('SELECT nid FROM {cmis_sync_node} WHERE cmis_objectId = \'%s\'', $cmis_object->id))) {
        $drupal_nid = $cmis_sync_node->nid;
      }
    }
    else {
      $drupal_nid = $cmis_object->properties[$sync_map_type['fields']['nid']];
    }

    // Load Drupal node
    $node = node_load($drupal_nid);
    $node->type = $node_type;

    // Map cmis properties to drupal node fields
    foreach ($sync_map_type['fields'] as $node_field => $cmis_field) {
      if (is_string($cmis_field)) {
        _cmis_sync_node_field_value($node, $node_field, $cmis_object->properties[$cmis_field]);
      }
      elseif (is_array($cmis_field)) {
        if (array_key_exists('cmis to drupal', $cmis_field) && $cmis_field['cmis to drupal'] === False) {
          continue;
        }
        _cmis_sync_node_field_value($node, $cmis_field['drupal'], $cmis_object->properties[$cmis_field['cmis']]);
      }
      else {
        throw new CMISException(t('Unknown field map type. Expects "string" or "array". Received "@type"', array(
          '@type' => gettype($cmis_field),
        )));
      }
    }

    // Load content field
    if (array_key_exists('content_field', $sync_map_type)) {
      _cmis_sync_node_field_value($node, $sync_map_type['content_field'], cmisapi_getContentStream($repository->repositoryId, $cmis_object->id), array(
        'cmis' => $cmis_object,
      ));
    }
    return $node;
  }
  return FALSE;
}

/**
 * Creates/updates Drupal nodes with CMIS content.
 * 
 * @param $repository
 * @param $sync_map_type
 * @param $node_type
 */
function _cmis_sync_cmis_to_drupal_handle_updates($repository, $sync_map_type, $node_type) {

  // Get CMIS object properties
  $cmis_folder = cmisapi_getProperties($repository->repositoryId, drupal_urlencode($sync_map_type['cmis_root']));

  // Select updated objects
  $sync_subfolders_rule = $sync_map_type['subfolders'] ? 'IN_TREE' : 'IN_FOLDER';
  $sync_full_rule = $sync_map_type['full_sync_next_cron'] ? '' : sprintf('AND LastModificationDate > \'%s\'', date_create('12 hour ago')
    ->format('Y-m-d\\TH:i:s.000-00:00'));

  // Grab last updates
  $cmis_query = sprintf('SELECT * FROM %s WHERE %s(\'%s\') %s', $sync_map_type['cmis_type'], $sync_subfolders_rule, $cmis_folder->id, $sync_full_rule);
  $cmis_updates = cmisapi_query($repository->respositoryId, $cmis_query);
  foreach ($cmis_updates as $cmis_update) {

    // Build/lookup Drupal node
    $drupal_node = _cmis_sync_cmis_to_drupal_prepare($repository, $sync_map_type, $node_type, $cmis_update);

    // Unable to map current CMIS object to any Drupal content type
    if (FALSE === $drupal_node) {
      continue;
    }

    // Mark the Drupal node in order to bypass nodeapi cmis_sync hook
    $drupal_node->cmis_sync_disabled = TRUE;

    // Save Drupal node
    node_save($drupal_node);

    // Update/insert changed timestamp
    if (db_fetch_object(db_query('SELECT nid FROM {cmis_sync_node} WHERE cmis_objectId = \'%s\'', $cmis_update->id))) {
      db_query('UPDATE {cmis_sync_node} SET changed_timestamp=%d, nid=%d WHERE cmis_objectId = \'%s\'', $_SERVER['REQUEST_TIME'], $drupal_node->nid, $cmis_update->id);
      watchdog('cmis_sync_cron', 'Updated nid @nid', array(
        '@nid' => $drupal_node->nid,
      ));
    }
    else {
      db_query('INSERT INTO {cmis_sync_node} (nid, cmis_objectId, changed_timestamp) VALUES (%d, \'%s\', %d)', $drupal_node->nid, $cmis_update->id, $_SERVER['REQUEST_TIME']);
      watchdog('cmis_sync_cron', 'Added nid @nid', array(
        '@nid' => $drupal_node->nid,
      ));
    }
  }
}

/**
 * Deletes Drupal nodes referencing to CMIS deleted objects. 
 * 
 * @param $repository
 * @param $sync_map_type
 */
function _cmis_sync_cmis_to_drupal_handle_deletes($repository, $sync_map_type, $node_type) {

  // Get node list
  $sync_nodes = array();
  $sync_nodes_query = db_query('SELECT nid, cmis_objectId FROM {cmis_sync_node}');
  while ($sync_node = db_fetch_object($sync_nodes_query)) {
    if (node_load($sync_node->nid)->type == $node_type) {
      $sync_nodes[$sync_node->cmis_objectId] = $sync_node->nid;
    }
  }
  if (count($sync_nodes)) {

    // Identify existing CMIS objects
    $cmis_objects = cmisapi_query($repository, sprintf('SELECT ObjectId FROM %s WHERE ObjectId IN (\'%s\')', $sync_map_type['cmis_type'], join('\',\'', array_keys($sync_nodes))));
    foreach ($cmis_objects as $cmis_object) {
      if (array_key_exists($cmis_object->id, $sync_nodes)) {
        unset($sync_nodes[$cmis_object->id]);
      }
    }

    // Delete CMIS - Drupal reference
    db_query('DELETE FROM {cmis_sync_node} WHERE nid IN (\'%s\')', join('\',\'', array_values($sync_nodes)));

    // Delete Drupal nodes
    foreach ($sync_nodes as $cmis_objectId => $drupal_nid) {
      node_delete($drupal_nid);
    }
  }
}

/**
 * Utility that gets/sets node field value.
 * Only supports regular, text and filefield fields types.
 */
function _cmis_sync_node_field_value(&$node, $field_name, $field_value = NULL, $context = array()) {
  $content_type = content_types($node->type);
  $value = NULL;
  if (array_key_exists($field_name, $content_type['fields'])) {

    // CCK field
    $content_field = $node->{$field_name};
    switch ($content_type['fields'][$field_name]['type']) {
      case 'filefield':
        if ($field_value == NULL) {
          $value = file_get_contents($content_field[0]['filepath']);
        }
        else {
          if (is_array($node->{$field_name})) {

            // Update drupal file node on filesystem
            file_put_contents($content_field[0]['filepath'], $field_value);
          }
          else {
            if (array_key_exists('cmis', $context)) {

              // Create file
              $file_drupal_path = file_directory_path() . '/cmis_' . basename($context['cmis']->properties['ObjectId']) . '_' . $context['cmis']->title;
              file_put_contents($file_drupal_path, $field_value);

              // Create file field
              $file = new stdClass();
              $file->filename = basename($file_drupal_path);
              $file->filepath = $file_drupal_path;
              $file->filemime = $context['cmis']->contentMimeType;
              $file->filesize = filesize($file_drupal_path);
              $file->status = FILE_STATUS_PERMANENT;
              $file->timestamp = time();
              drupal_write_record('files', $file);

              // create new filefield
              $node->{$field_name} = array(
                array(
                  'fid' => $file->fid,
                  'title' => $context['cmis']->title,
                  'filename' => $file->filename,
                  'filepath' => $file->filepath,
                  'filesize' => $file->filesize,
                  'filemime' => $context['cmis']->contentMimeType,
                  'list' => 1,
                ),
              );
            }
          }
        }
        break;
      case 'text':
        if ($field_value == NULL) {
          $value = $content_field[0]['value'];
        }
        else {
          $content_field[0]['value'] = $field_value;
        }
        break;
    }
  }
  else {

    // Regular node field value
    if ($field_value == NULL) {
      $value = $node->{$field_name};
    }
    else {
      $node->{$field_name} = $field_value;
    }
  }
  return $value;
}

Functions

Namesort descending Description
cmis_sync_cron Implementation of hook_cron
cmis_sync_menu Implementation of hook_menu() for CMIS sync module.
cmis_sync_nodeapi Implementation of hook_nodeapi() for CMIS sync module.
cmis_sync_theme Implementation of hook_theme() for CMIS sync module.
_cmis_sync_cmis_to_drupal_handle_deletes Deletes Drupal nodes referencing to CMIS deleted objects.
_cmis_sync_cmis_to_drupal_handle_updates Creates/updates Drupal nodes with CMIS content.
_cmis_sync_cmis_to_drupal_prepare Maps a cmis_object to a drupal node.
_cmis_sync_drupal_to_cmis_prepare Maps a drupal node to a cmis_object
_cmis_sync_node_field_value Utility that gets/sets node field value. Only supports regular, text and filefield fields types.