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; }