diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..57872d0f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 790ffb5d..0a05cf72 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,20 +1,44 @@ Stanford CAPx x.x-x.x, xxxx-xx-xx --------------------------------- -Stanford CAPx 7.x-1.3-php54, 2015-10-19 +php54, 2015-10-19 --------------------------------- - Guzzle downgraded to 5.3 to support PHP 5.4 -- Support for auto_nodetitle -- Create random password and set more default values for user creation -- Removed filename salting and added timestamp check -- Added Protection for bad json responses -- No more duplication of files -- Importer checks timestamps before downloading files -Stanford CAPx 7.x-1.2, 2015-09-10 + +Stanford CAPx 7.x-2.0, 2016-05-27 +--------------------------------- +- Entity relationships! +- Multiple entity creation (IE: Publications). +- Multiple field collections. +- Merged connect and settings form. +- Maintains index association on multiple item creation and wildcard json queries. +- Bug fix for field collection item mapping values that went missing after save. +- Added several new permissions for more granular control over the +- Removed superfluous required text on field mapping +- Required fields with a default value are no longer required in the mapping. +- Added auto truncating of text fields in order to avoid the PDO exception error of value too long. + +Stanford CAPx 7.x-1.3, 2016-02-05 --------------------------------- +- Bugfix: Removed hook_install views save and install time check for private files dir. +- Bugfix: Reduced duplicate warning messages. +- Bugfix: Fix UI for field collection mapping where it was only showing one. +- Added variable cache clear. +- Added block module as a dependency. +- Removed empty error messages caused by the new autonodetitle module. +- Bugfix: Fixes to taxonomy term saving. +- BASIC-1639: protection against images that were unable to be saved to disk. +- Publish orphans that were automatically unpublished. +- Bugfix: Fixes for orphans that are not on the API any more. +- CAPX-167: Expand and collapse all for the data browser. +- CAPX-113: Floating scrollable mapping sidebar. +- CAPX-113: Fixes for page offset and updates to data browser quick links. +- CAPX-89: Skip API call to count the sunet ids as we already have them. +Stanford CAPx 7.x-1.2, 2015-09-10 +--------------------------------- - Upgraded HTTP Client from Guzzle 3.7.4 -> 6.0.2 - User entities when created from CAP now get a random password assigned to them. - Fixed permission issues with the profiles list view @@ -27,6 +51,12 @@ Stanford CAPx 7.x-1.2, 2015-09-10 - Fixed a bug where updated profile images were being updated but the automatic thumnails (imagecache) were not. - Changed the Jira project the issue collector module goes in to so that goes straight into the backlog. - And a number of other smaller bug fixes and performance improvements. +- Support for auto_nodetitle +- Create random password and set more default values for user creation +- Removed filename salting and added timestamp check +- Added Protection for bad json responses +- No more duplication of files +- Importer checks timestamps before downloading files Stanford CAPx 7.x-1.1, 2015-03-06 --------------------------------- diff --git a/README.md b/README.md index 68894dca..229b76d6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Stanford CAPx +#### Version 2.x Stanford CAP Extensible module builds on some great work. This module provides an interface for administrators to pull information directly from the CAP API into Drupal. This allows profile owners to continue to manage their profile information on the CAP web service and have that information automatically reflected into a Drupal website. diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..a9c8e0ef --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "su-sws/stanford_capx", + "description": "Stanford Career Academic Profiles API integration Module", + "type": "stanford-module", + "license": "GPL-2.0+", + "authors": [ + { + "name": "Shea McKinney", + "email": "sheamck@stanford.edu" + } + ], + "require": {}, + "homepage": "https://github.com/SU-SWS/stanford_capx" +} diff --git a/includes/CAPx/Drupal/Importer/EntityImporterBatch.php b/includes/CAPx/Drupal/Importer/EntityImporterBatch.php index 17fd76f4..246f63b4 100644 --- a/includes/CAPx/Drupal/Importer/EntityImporterBatch.php +++ b/includes/CAPx/Drupal/Importer/EntityImporterBatch.php @@ -165,6 +165,11 @@ public static function processResults($results, $importer) { $message = $processor->getStatusMessage(); + // Nada. + if (empty($message)) { + continue; + } + // Log some information. // @todo This needs to be better. watchdog('stanford_capx', $message . " | " . $info['displayName'], array(), WATCHDOG_DEBUG); diff --git a/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphans.php b/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphans.php index acad4340..f453e7a2 100644 --- a/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphans.php +++ b/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphans.php @@ -69,10 +69,12 @@ public function __construct(EntityImporter $importer, Array $profiles, Array $lo $this->setProfiles($profiles); $this->setLimit(count($profiles)); + // Store all of the lookups. if (is_array($lookups)) { $this->lookups = $lookups; } + // Store all of the comparisons. if (is_array($comparisons)) { $this->comparisons = $comparisons; } @@ -87,9 +89,6 @@ public function execute() { // If the action is set to do nothing to orphaned profiles, do nothing. $options = $this->getImporterOptions(); $action = $options['orphan_action']; - - $importer = $this->getImporter(); - $options = $this->getImporterOptions(); $profiles = $this->getProfiles(); $client = $this->getClient(); $limit = $this->getLimit(); @@ -120,35 +119,42 @@ public function execute() { // Now that we have identified all of the orphans lets compare them to each // way we can import a profile to identify if an orphan in one is not an // orphan in another. Only an orphan across all ways is a true orphan. - $comparisons = $this->getComparisons(); foreach ($comparisons as $k => $comparison) { $orphans = $comparison->execute($this); $this->setOrphans($orphans); } - // If we have no orphans after all of that just end. - if (empty($orphans)) { - return; + // Special processor for multiple items. + if (isset($orphans['multiple'])) { + $this->processMultipleImporterOrphans($orphans['multiple']); + unset($orphans['multiple']); } // We have looked at everything and now it is time to process the orphans. // In order to be an orphan the orphan id has to appear in all importer // Options. So we can just take one and run the process on that. - $orphaned = array_pop($orphans); + $orphaned = end($orphans); + + // Always want to do this in case the action has changed. + $this->processAdopted($profiles, $orphaned); + $this->processAdoptedMultiple($profiles); + + // If we have no orphans after all of that just end. + if (empty($orphans)) { + return; + } // Small patch up fix. if (isset($orphans["missing"])) { $orphaned = array_merge($orphaned, $orphans["missing"]); } + // If no action is required then just skip over this and go to the adopted. if ($action !== "nothing") { - $this->processOrphans($orphaned, $importer); + $this->processOrphans($orphaned); } - // Always want to do this in case the action has changed. - $this->processAdopted($profiles, $orphaned); - } /** @@ -331,6 +337,45 @@ public function getResults() { // /////////////////////////////////////////////////////////////////////////// + /** + * @param $entityIds + */ + protected function processMultipleImporterOrphans($entityIds) { + $importer = $this->getImporter(); + $entityType = $importer->getEntityType(); + $options = $this->getImporterOptions(); + $action = $options['orphan_action']; + + // Do nothing John Snow. + if ($action == "nothing") { + return; + } + + foreach ($entityIds as $entityId) { + $entity = entity_load($entityType, array($entityId)); + $profile = entity_metadata_wrapper($entityType, $entity[$entityId]); + + switch ($action) { + // Any entity can be deleted. + case 'delete': + $profile->delete(); + break; + + // Users and nodes can have their status set to 0. + case 'block': + case 'unpublish': + $profile->status->set(0); + $profile->save(); + $this->logOrphan($profile); + break; + + default: + // Do nothing. + } + } + + } + /** * Handles what to do when a profile has been orphaned. @@ -376,6 +421,62 @@ public function processOrphans($profileIds) { } + /** + * Logs that a profile was orphaned. + * + * @param object $profile + * The loaded and wrapped entity metadata. + */ + public function logOrphan($entity) { + + // Set the flag to 1 in the capx_profiles table. + + // BEAN is returning its delta when using this. + // $id = $entity->getIdentifier(); + + $entityType = $entity->type(); + $entityRaw = $entity->raw(); + list($id, $vid, $bundle) = entity_extract_ids($entityType, $entityRaw); + + $guuid = $entityRaw->capx['guuid']; + + $importer = $this->getImporter(); + $importerName = $importer->getMachineName(); + $entityType = $importer->getEntityType(); + $bundleType = $importer->getBundleType(); + + $record = array( + 'entity_type' => $entityType, + 'bundle_type' => $bundleType, + 'entity_id' => $id, + 'importer' => $importerName, + 'orphaned' => 1, + ); + + // For the multiple entity. + if (!empty($guuid)) { + $record['guuid'] = $guuid; + } + + $keys = array( + 'entity_type', + 'entity_id', + 'importer', + 'bundle_type', + ); + + // For multiple entities. + if (!empty($guuid)) { + $keys[] = 'guuid'; + } + + $yes = drupal_write_record('capx_profiles', $record, $keys); + + if ($yes) { + watchdog('EntityImporterOrphans', "%title was orphaned from the importer %importername.", array("%title" => $entity->label(), "%importername" => $importerName), WATCHDOG_NOTICE, ''); + } + } + /** * Orphaned profiles that have returned to the importer. * @@ -432,49 +533,87 @@ public function processAdopted($profiles, $orphans) { return $orphans; } + /** - * Logs that a profile was orphaned. + * Orphaned profiles that have returned to the importer. * - * @param object $profile - * The loaded and wrapped entity metadata. + * Find and process profiles that have been added back into an import by + * removing their orphan status. + * + * @param array $profiles + * An array of profile ids + * @param array $orphans + * An array of orphaned profile ides */ - public function logOrphan($entity) { - - // Set the flag to 1 in the capx_profiles table. - - // BEAN is returning its delta when using this. - // $id = $entity->getIdentifier(); - - $entityType = $entity->type(); - $entityRaw = $entity->raw(); - list($id, $vid, $bundle) = entity_extract_ids($entityType, $entityRaw); - + public function processAdoptedMultiple($profiles) { $importer = $this->getImporter(); - $importerName = $importer->getMachineName(); + $importerMachineName = $importer->getMachineName(); $entityType = $importer->getEntityType(); $bundleType = $importer->getBundleType(); + $options = $importer->getOptions(); + $orphanAction = $options['orphan_action']; - $record = array( - 'entity_type' => $entityType, - 'bundle_type' => $bundleType, - 'entity_id' => $id, - 'importer' => $importerName, - 'orphaned' => 1, - ); + if ($orphanAction !== "unpublish") { + // Nothing actionable to perform. + return; + } - $keys = array( - 'entity_type', - 'entity_id', - 'importer', - 'bundle_type', - ); + $results = $this->getResults(); + $mapper = $this->getImporter()->getMapper(); + $guuidquery = $mapper->getGUUIDQuery(); + $parts = explode(".", $guuidquery); + $subquery = "$.." . array_pop($parts); + $remoteGUUIDs = $mapper->getRemoteDataByJsonPath($results, $subquery); + + $or = db_or()->condition('profile_id', $profiles); + $localResults = db_select("capx_profiles", "cxp") + ->fields('cxp', array('entity_id', 'guuid')) + ->condition($or) + ->condition("importer", $importerMachineName) + ->condition("orphaned", 1) + ->execute() + ->fetchAllAssoc('guuid'); + + $localGUUIDs = array_keys($localResults); + $diff = array_intersect($localGUUIDs, $remoteGUUIDs); + + // Nothing to adopt. Yay. + if (empty($diff)) { + return array(); + } - $yes = drupal_write_record('capx_profiles', $record, $keys); + foreach ($diff as $guuid) { + $entityID = $localResults[$guuid]->entity_id; + $ids = array($entityID); + $entity = array_pop(entity_load($entityType, $ids)); + $profile = entity_metadata_wrapper($entityType, $entity); + $profile->status->set(1); + $profile->save(); + + // Profile is not an orphan. Lets enable it again. + $record = array( + 'entity_type' => $entityType, + 'bundle_type' => $bundleType, + 'entity_id' => $entityID, + 'importer' => $importerMachineName, + 'orphaned' => 0, + 'guuid' => $guuid, + ); - if ($yes) { - watchdog('EntityImporterOrphans', "%title was orphaned from the importer %importername.", array("%title" => $entity->label(), "%importername" => $importerName), WATCHDOG_NOTICE, ''); + $keys = array( + 'entity_type', + 'entity_id', + 'importer', + 'bundle_type', + 'guuid', + ); + + drupal_write_record('capx_profiles', $record, $keys); } + } + + } diff --git a/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphansBatch.php b/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphansBatch.php index bfd39d06..cbf28f65 100644 --- a/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphansBatch.php +++ b/includes/CAPx/Drupal/Importer/Orphans/EntityImporterOrphansBatch.php @@ -13,7 +13,7 @@ class EntityImporterOrphansBatch { /** * Callback for batch import functionality. - + * * @param string $importerMachineName * The machine name of the importer configuration entity. * @param int $profiles diff --git a/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingFromAPI.php b/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingFromAPI.php index e24c8449..e73b8c3d 100644 --- a/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingFromAPI.php +++ b/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingFromAPI.php @@ -9,7 +9,7 @@ class LookupMissingFromApi implements LookupInterface { /** - * Checks the API server to see if the SUNET item still exists. + * Checks the API server to see if the item still exists. * * @param EntityImporterOrphans $orphaner * The orphan processor object. diff --git a/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingMultipleByGUUID.php b/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingMultipleByGUUID.php new file mode 100644 index 00000000..4cb02883 --- /dev/null +++ b/includes/CAPx/Drupal/Importer/Orphans/Lookups/LookupMissingMultipleByGUUID.php @@ -0,0 +1,62 @@ + + */ + +namespace CAPx\Drupal\Importer\Orphans\Lookups; + +class LookupMissingMultipleByGUUID implements LookupInterface { + + /** + * Checks the API server to see if the item still exists. + * + * @param EntityImporterOrphans $orphaner + * The orphan processor object. + * + * @return array + * The remaining orphans + */ + public function execute($orphaner) { + $results = $orphaner->getResults(); + $profiles = $orphaner->getProfiles(); + $importer = $orphaner->getImporter()->getMachineName(); + $mapper = $orphaner->getImporter()->getMapper(); + $guuidquery = $mapper->getGUUIDQuery(); + $parts = explode(".", $guuidquery); + $subquery = "$.." . array_pop($parts); + $remoteGUUIDs = $mapper->getRemoteDataByJsonPath($results, $subquery); + $localInfo = $this->getLocalGUUIDs($profiles, $importer); + $localGUUIDs = array_keys($localInfo); + $diff = array_diff($localGUUIDs, $remoteGUUIDs); + + // No orphans. Yay. + if (empty($diff)) { + return array(); + } + + // New column for the comparisons. + $orphans = array('multiple' => array()); + foreach ($diff as $key => $guuid) { + $orphans['multiple'][] = $localInfo[$guuid]->entity_id; + } + + return $orphans; + } + + /** + * @param $profileIDs + * @param $importerMachineName + */ + protected function getLocalGUUIds($profileIDs, $importerMachineName) { + $or = db_or()->condition('profile_id', $profileIDs); + $results = db_select("capx_profiles", "cxp") + ->fields('cxp', array('entity_id', 'guuid')) + ->condition($or) + ->condition("importer", $importerMachineName) + ->execute() + ->fetchAllAssoc('guuid'); + return $results; + } + +} diff --git a/includes/CAPx/Drupal/Mapper/EntityMapper.php b/includes/CAPx/Drupal/Mapper/EntityMapper.php index 6cdc9307..cbd7d300 100644 --- a/includes/CAPx/Drupal/Mapper/EntityMapper.php +++ b/includes/CAPx/Drupal/Mapper/EntityMapper.php @@ -7,13 +7,18 @@ namespace CAPx\Drupal\Mapper; use CAPx\Drupal\Processors\FieldProcessors\FieldProcessor; +use CAPx\Drupal\Processors\FieldProcessors\EntityReferenceFieldProcessor; use CAPx\Drupal\Processors\PropertyProcessors\PropertyProcessor; use CAPx\Drupal\Processors\FieldCollectionProcessor; +use CAPx\Drupal\Processors\EntityReferenceProcessor; +use CAPx\Drupal\Util\CAPx; use CAPx\Drupal\Util\CAPxMapper; use CAPx\Drupal\Util\CAPxImporter; class EntityMapper extends MapperAbstract { + protected $index; + /** * Execute starts the mapping process. * @@ -27,6 +32,11 @@ class EntityMapper extends MapperAbstract { */ public function execute($entity, $data) { + // Always attach the profileId to the entity + $raw = $entity->value(); + $raw->capx['profileId'] = $data['profileId']; + $entity->set($raw); + // Store this for later. $this->setEntity($entity); @@ -36,6 +46,7 @@ public function execute($entity, $data) { // is logged to watchdog so handle any errors in each of these by throwing // only one error up a level. + // FIELDS. try { $this->mapFields($data); } @@ -43,6 +54,7 @@ public function execute($entity, $data) { $this->setError($e); } + // PROPERTIES. try { $this->mapProperties($data); } @@ -50,6 +62,7 @@ public function execute($entity, $data) { $this->setError($e); } + // FIELD COLLECTIONS. try { // Field Collections are special. Special means more code. They get their // own mapProcess even though they are sort of a field. @@ -59,6 +72,14 @@ public function execute($entity, $data) { $this->setError($e); } + // REFERENCES. + try { + $this->mapReferences(); + } + catch (\Exception $e) { + $this->setError($e); + } + // Even if there were errors we should have an entity by this point. return $entity; } @@ -128,6 +149,12 @@ public function mapFields($data) { continue; } + // If we are running a multiple entity mapping we only want part of + // the result. + if ($this->isMultiple()) { + $info = $this->getMultipleIndexInfoResultField($info); + } + // Widgets can change the way the data needs to be parsed. Provide // that to the FieldProcessor. $widget = $fieldInfoInstance['widget']['type']; @@ -181,7 +208,7 @@ public function mapProperties($data) { try { $info = $this->getRemoteDataByJsonPath($data, $remoteDataPath); } - catch(\Exception $e) { + catch (\Exception $e) { $error = TRUE; $message = 'There was an exception when trying to get data by @path. Exception message is: @message.'; $message_vars = array( @@ -192,6 +219,12 @@ public function mapProperties($data) { continue; } + // If we are running a multiple entity mapping we only want part of + // the result. + if ($this->isMultiple()) { + $info = $this->getMultipleIndexInfoResultProperty($info); + } + // Let the property processor do its magic. try { $propertyProcessor = new PropertyProcessor($entity, $propertyName); @@ -228,7 +261,7 @@ public function mapFieldCollections($data) { try { $collections = $this->getConfigSetting('fieldCollections'); } - catch(\Exception $e) { + catch (\Exception $e) { // No collections. Just return. return; } @@ -262,6 +295,56 @@ public function mapFieldCollections($data) { $this->setEntity($entity); } + /** + * Process entity reference fields uniquely. + * + * Reference fields are a special field and need to be handled differently. + * Allow for the ability to look for an item to attach to from the API + * that has already been imported. + * + */ + public function mapReferences() { + + try { + $references = $this->getConfigSetting('references'); + } + catch (\Exception $e) { + // No references. Just return. + return; + } + + // Nothing to do here. + if (empty($references)) { + return; + } + + $entity = $this->getEntity(); + + // Loop through each reference field and try to match up with another + // entity from another importer. + foreach ($references as $fieldName => $values) { + $target = array_pop($values); + $processor = new EntityReferenceProcessor($entity, $this->getImporter(), $target); + $referenceEntity = $processor->execute(); + + // No possible references available. End here. + if (empty($referenceEntity)) { + continue; + } + + // wrap it up. + $referenceEntity = entity_metadata_wrapper($this->getEntityType(), $referenceEntity); + + // Map the reference. + $entityReferenceFieldProcessor = new EntityReferenceFieldProcessor($entity, $fieldName); + $entityReferenceFieldProcessor->put($referenceEntity); + + } + + // Set the entity to apply any changes this function may have had. + $this->setEntity($entity); + } + /** * Checks that fields used in this mapper still in place. * @@ -460,4 +543,106 @@ public function getAffectedImporters() { return $affected; } + /** + * Boolean to whether or not this is a multiple import or not. + * @return boolean [description] + */ + public function isMultiple() { + try { + return $this->getConfigSetting("multiple"); + } + catch (\Exception $e) { + return FALSE; + } + } + + /** + * Sets the multiple config value to passed in. + * @param $val bool + * Boolean for multiple or not + */ + public function setIsMultiple($val = TRUE) { + $this->config['multiple'] = $val; + } + + /** + * get Subquery wrapper + */ + public function getSubquery() { + try { + return $this->getConfigSetting("subquery"); + } + catch (\Exception $e) { + return FALSE; + } + } + + /** + * get guuid wrapper + */ + public function getGUUIDQuery() { + try { + return $this->getConfigSetting("guuidquery"); + } + catch (\Exception $e) { + return FALSE; + } + } + + /** + * [getIndex description] + * @return [type] [description] + */ + public function getIndex() { + return $this->index; + } + + /** + * [setIndex description] + * @param [type] $i [description] + */ + public function setIndex($i) { + if (is_int($i) && $i >= 0) { + $this->index = $i; + } + else { + throw new \Exception("Could not set index. Invalid option."); + + } + } + + /** + * [getMultipleIndexInfoResult description] + * @param [type] $info [description] + * @return [type] [description] + */ + protected function getMultipleIndexInfoResultField($info) { + $index = $this->getIndex(); + $keys = array_keys($info); + $ret = array(); + + foreach ($keys as $key) { + if (isset($info[$key][$index])) { + $ret[$key] = array($info[$key][$index]); + } + } + + return !empty($ret) ? $ret : $info; + } + + /** + * @param $info + * @return array + */ + protected function getMultipleIndexInfoResultProperty($info) { + $index = $this->getIndex(); + + // If an index value exists: + if (isset($info[$index])) { + return array($info[$index]); + } + + return $info; + } + } diff --git a/includes/CAPx/Drupal/Mapper/MapperAbstract.php b/includes/CAPx/Drupal/Mapper/MapperAbstract.php index 413fb829..8f1caeac 100644 --- a/includes/CAPx/Drupal/Mapper/MapperAbstract.php +++ b/includes/CAPx/Drupal/Mapper/MapperAbstract.php @@ -26,7 +26,6 @@ abstract class MapperAbstract implements MapperInterface { // Error storage so they can be fetched after everything has run. protected $errors = array(); - /** * Merges default configuration options with the passed in set. * @@ -52,10 +51,93 @@ public function getRemoteDataByJsonPath($data, $path) { throw new \Exception("Path cannot be empty", 1); } + // Wildcard paths need to have index association maintained. JSONPath does + // not provide much help in this and we need to do funny things. + if (strpos($path, "*")) { + $parsed = $this->getRemoteDataWithWildcard($data, $path); + } + // No wildcard, just get stuff. + else { + $parsed = $this->getRemoteDataRegular($data, $path); + } + + return $parsed; + + } + + /** + * Returns an array of data that matched the given path. + * + * @param $data + * @param $path + * @return array + */ + protected function getRemoteDataRegular($data, $path) { $jsonParser = new JsonParser($data); $parsed = $jsonParser->get($path); return $parsed; + } + + /** + * Returns an array of data that matched the given path with empty strings + * for missing leaf items. + * @param $data + * @param $path + */ + protected function getRemoteDataWithWildcard($data, $path) { + + // To maintain index association over wildcards we need to return an empty + // result on a missing leaf. JSONPATH does not provide a nice way of doing + // this out of the box at this point in time so we are going to have to + // break up the requests in to parts. + + $pathParts = explode("*", $path); + $lastPart = array_pop($pathParts); + $countQuery = implode("*", $pathParts); + $countQuery .= "*"; + + $jsonParser = new JsonParser($data); + $results = $jsonParser->get($path); + $wildResults = $jsonParser->get($countQuery); + + // If the number of results matches the number of wildcards then we have no + // Missing items. Just return the results. + if (count($results) == count($wildResults)) { + return $results; + } + + // Darn, we have a mismatch in wildcards and leafs. Find out who is missing + // and add an empty value for them in the return array. + $parsed = array(); + $tmpData = $data; + // Iterate over each of the wild card queries and evaluate the expression. + foreach ($pathParts as $i => $part) { + + // Tack on the wildcard character to the end of the first expression. + if ($i == 0) { + $part .= "*"; + } + else { + $part = "$.*" . $part . "*"; + } + + $jsonParser = new JsonParser($tmpData); + $tmpData = $jsonParser->get($part); + } + + foreach ($tmpData as $values) { + $jsonParser = new JsonParser($values); + $result = $jsonParser->get("$" . $lastPart); + if ($result) { + $parsed[] = array_pop($result); + } + else { + $parsed[] = ""; + } + } + + return $parsed; } /** @@ -97,8 +179,6 @@ public function getConfig() { public function addConfig($settings) { $config = $this->getConfig(); -// $mapper = $this->getMapper(); - $settings['fieldCollections'] = array(); if (isset($settings['collections'])) { @@ -111,6 +191,9 @@ public function addConfig($settings) { $mapper->settings['fields'] = $fields; $mapper->settings['properties'] = array(); $mapper->settings['collections'] = array(); + $mapper->settings['multiple'] = FALSE; + $mapper->settings['subquery'] = ''; + $mapper->settings['guuidquery'] = ''; $settings['fieldCollections'][$fieldName] = new FieldCollectionMapper($mapper); $settings['fieldCollections'][$fieldName]->addConfig($mapper->settings); @@ -222,5 +305,27 @@ public function getErrors() { return FALSE; } + /** + * [getMultipleEntityCount description] + * @return [type] [description] + */ + public function getMultipleEntityCountBySubquery($data) { + $result = $this->getRemoteDataByJsonPath($data, $this->getConfigSetting("subquery")); + return count($result); + } + + /** + * [getGUUID description] + * @param [type] $index [description] + * @return [type] [description] + */ + public function getGUUID($data, $index) { + $path = $this->getConfigSetting("guuidquery"); + if (empty($path)) { + return ''; + } + $guuids = $this->getRemoteDataByJsonPath($data, $path); + return $guuids[$index]; + } } diff --git a/includes/CAPx/Drupal/Processors/EntityProcessor.php b/includes/CAPx/Drupal/Processors/EntityProcessor.php index 77fbc651..eaa1f33b 100644 --- a/includes/CAPx/Drupal/Processors/EntityProcessor.php +++ b/includes/CAPx/Drupal/Processors/EntityProcessor.php @@ -15,7 +15,7 @@ class EntityProcessor extends ProcessorAbstract { protected $entity; // Skip etag check. - protected $force = FALSE; + protected $force; /** * Process entity. @@ -31,8 +31,168 @@ class EntityProcessor extends ProcessorAbstract { * The new or updated wrapped entity. */ public function execute($force = FALSE) { + + // Set this force. + $this->force == $force; + + try { + $multi = $this->getMapper()->getConfigSetting('multiple'); + } + catch (\Exception $e) { + $multi = FALSE; + } + + // Sometimes we need to create one, other times we need moar. + if (!empty($multi) && $multi == 1) { + $entity = $this->executeMultiple($force); + } + else { + $entity = $this->executeSingle($force); + } + + return $entity; + } + + /** + * Process the execution and creation of multiple entities per profile. + * @param [type] $force [description] + * @return [type] [description] + */ + protected function executeMultiple($force = FALSE) { + + $mapper = $this->getMapper(); $data = $this->getData(); + $numEntities = $mapper->getMultipleEntityCountBySubquery($data); + + // Let the Orphan cron runs take care of the clean up. We just need to stop. + if ($numEntities <= 0) { + return; + } + + // Time to loop through our results and either update or create entities. + $entityImporter = $this->getEntityImporter(); + $importerMachineName = $entityImporter->getMachineName(); + $entityType = $mapper->getEntityType(); + $bundleType = $mapper->getBundleType(); + + // Check to see what entities we have for this profile. + $entities = CAPx::getProfiles($entityType, array('profile_id' => $data['profileId'], 'importer' => $importerMachineName)); + + // Looks like we have a whole new batch to create. + if (empty($entities)) { + $this->multipleCreateNewEntity($numEntities, $entityType, $bundleType, $data, $mapper); + return; + } + + // Check if we even need to update. A matching etag will allow us to + // avoid a costly update routine. + if (!$this->isETagDifferent() && !$this->skipEtagCheck()) { + // Nothing to do, same etag and no forceful update. + return; + } + + // At this point we have saved entities and the etag changed or we are + // forcing an update. + + // Strategy: If the user can provide a GUID (fingerprint) then we can try to + // update in place. If the user cannot provide a GUID then we go with the + // delete everything and replace strategy. + + try { + $guuidPath = $mapper->getConfigSetting("guuidquery"); + } + catch (\Exception $e) { + // An older mapper that has not yet been updated. + // @todo: Think of a way to update the older mappers with an update hook. + } + + // NO GUUID available. Delete all of the existing entities and replace with + // new ones. + if (empty($guuidPath)) { + $this->multipleDeleteEntities($entities); + $this->multipleCreateNewEntity($numEntities, $entityType, $bundleType, $data, $mapper); + return; + } + + // GUUID available lets check to see if it matches the numEntities count. + $numGUUIDs = count($mapper->getRemoteDataByJsonPath($data, $guuidPath)); + + // Ok, we are in good shape at this point. We have results, we have ids, and + // now we can update them in place! + $this->multipleUpdateEntities($numEntities, $entityType, $bundleType, $data, $mapper); + + } + + /** + * Create a bunch of new entities! + * @param [type] $numEntities [description] + * @param [type] $entityType [description] + * @param [type] $data [description] + * @param [type] $mapper [description] + * @return [type] [description] + */ + protected function multipleCreateNewEntity($numEntities, $entityType, $bundleType, $data, $mapper) { + $i = 0; + while ($i < $numEntities) { + // Setting the index tells the mapper which values to save. + $mapper->setIndex($i); + $guuid = $mapper->getGUUID($data, $i); + $entity = $this->newEntity($entityType, $bundleType, $data, $mapper, $guuid); + $i++; + } + return TRUE; + } + + /** + * Deletes all entities passed to this function. + * @param [type] $entities [description] + * @return [type] [description] + */ + protected function multipleDeleteEntities($entityType, $entities) { + entity_delete_multiple($entityType, $entities); + } + + /** + * Update function for when there are multiple entities being created per + * person. + * @param [type] $numEntities [description] + * @param [type] $entityType [description] + * @param [type] $bundleType [description] + * @param [type] $data [description] + * @param [type] $mapper [description] + * @return [type] [description] + */ + protected function multipleUpdateEntities($numEntities, $entityType, $bundleType, $data, $mapper) { + $i = 0; + while ($i < $numEntities) { + // Setting the index tells the mapper which values to save. + $mapper->setIndex($i); + $guuid = $mapper->getGUUID($data, $i); + $importerMachineName = $this->getEntityImporter()->getMachineName(); + $profileId = $data['profileId']; + + $entity = CAPx::getEntityIdByGUUID($importerMachineName, $profileId, $guuid); + if ($entity) { + $entity = entity_metadata_wrapper($entityType, $entity); + $entity = $this->updateEntity($entity, $data, $mapper); + } + else { + $entity = $this->newEntity($entityType, $bundleType, $data, $mapper, $guuid); + } + + $i++; + } + return TRUE; + } + + /** + * Process the execution and creation of a single entity per profile response. + * @param [type] $force [description] + * @return [type] [description] + */ + protected function executeSingle($force = FALSE) { $mapper = $this->getMapper(); + $data = $this->getData(); $entityImporter = $this->getEntityImporter(); $importerMachineName = $entityImporter->getMachineName(); @@ -126,7 +286,8 @@ public function updateEntity($entity, $data, $mapper) { $entityImporter = $this->getEntityImporter(); $importerMachineName = $entityImporter->getMachineName(); - CAPx::updateProfileRecord($entity, $data['profileId'], $data['meta']['etag'], $importerMachineName); + $guuid = $mapper->getGUUID($data, $mapper->getIndex()); + CAPx::updateProfileRecord($entity, $data['profileId'], $data['meta']['etag'], $importerMachineName, $guuid); drupal_alter('capx_post_update_entity', $entity); @@ -148,11 +309,13 @@ public function updateEntity($entity, $data, $mapper) { * The data to be mapped to the new entity * @param object $mapper * The EntityMapper instance + * @param mixed $guuid + * The genuine unique id for this entity of other than profileId. * * @return object * The new entity after it has been saved. */ - public function newEntity($entityType, $bundleType, $data, $mapper) { + public function newEntity($entityType, $bundleType, $data, $mapper, $guuid = NULL) { $properties = array( 'type' => $bundleType, @@ -191,7 +354,7 @@ public function newEntity($entityType, $bundleType, $data, $mapper) { // Write a new record. $entityImporter = $this->getEntityImporter(); $importerMachineName = $entityImporter->getMachineName(); - CAPx::insertNewProfileRecord($entity, $data['profileId'], $data['meta']['etag'], $importerMachineName); + CAPx::insertNewProfileRecord($entity, $data['profileId'], $data['meta']['etag'], $importerMachineName, $guuid); return $entity; } @@ -237,6 +400,13 @@ public function skipEtagCheck($bool = NULL) { if (is_bool($bool)) { $this->force = $bool; } + + // Allow a debug force var. + $debug = variable_get("stanford_capx_debug_always_force_etag", -1); + if ($debug !== -1) { + return $debug; + } + return $this->force; } diff --git a/includes/CAPx/Drupal/Processors/EntityReferenceProcessor.php b/includes/CAPx/Drupal/Processors/EntityReferenceProcessor.php new file mode 100644 index 00000000..0bfe17b1 --- /dev/null +++ b/includes/CAPx/Drupal/Processors/EntityReferenceProcessor.php @@ -0,0 +1,76 @@ + + */ + +namespace CAPx\Drupal\Processors; + +use CAPx\Drupal\Mapper\EntityMapper; +use CAPx\Drupal\Util\CAPx; + +/** + * Entity references are a bit different than normal. + */ +class EntityReferenceProcessor { + + protected $entity; + protected $importer; + protected $target; + + /** + * Creates an entityReferenceProcessor to handle entity reference fields. + * + * @param object $entity + * The entity we are currently working on to save. + * @param object $importer + * The importer object and all its glory. + * @param string $target + * The target importer where the relative entity lives. + */ + public function __construct($entity, $importer, $target) { + $this->entity = $entity; + $this->importer = $importer; + $this->target = $target; + } + + /** + * Returns a list of possible matches. + * + * @return mixed + * An empty array if no matches or a fully loaded entity. + */ + public function execute() { + + // Get the profile ID of this entity as the profile id will be the same + // for other importers and entity/bundle types. + + $profile_id = $this->entity->value()->capx['profileId']; + + // Did not find one. It could be that is hasn't been created yet and may + // take another cycle or two to come up. + if (!$profile_id) { + throw new \Exception('Could not find profileId. Did something change in the API?'); + } + + $match = db_select("capx_profiles", 'capx') + ->fields('capx') + ->condition('profile_id', $profile_id) + ->condition('importer', $this->target) + ->orderBy('id', 'DESC') + ->execute() + ->fetchAssoc(); + + if (empty($match)) { + return array(); + } + + // Try to load it. + $entity = entity_load_single($match['entity_type'], $match['entity_id']); + + // Return the result. + return empty($entity) ? array() : $entity; + } + + +} diff --git a/includes/CAPx/Drupal/Processors/FieldCollectionProcessor.php b/includes/CAPx/Drupal/Processors/FieldCollectionProcessor.php index 9727201c..9e60f64d 100644 --- a/includes/CAPx/Drupal/Processors/FieldCollectionProcessor.php +++ b/includes/CAPx/Drupal/Processors/FieldCollectionProcessor.php @@ -12,7 +12,7 @@ class FieldCollectionProcessor extends EntityProcessor { // The field collection entity - protected $fieldCollectionEntity = null; + protected $fieldCollectionEntities = array(); // The parent entity protected $parentEntity = null; @@ -29,28 +29,77 @@ public function execute() { $mapper = $this->getMapper(); $entityType = $mapper->getEntityType(); $bundleType = $mapper->getBundleType(); - - $entity = $this->newEntity($entityType, $bundleType, $data, $mapper); - return $entity; + $parent = $this->getParentEntity(); + + // Loop through an create new field collections based on the number of + // results for each field. Keep looping through the index of data until + // there is no more data to add to the entity. We can accomplish this by + // checking to see if the last FC that was created is exactly the same as + // the one just created. If they are identical then no new data is available + // and the loop should be stopped. + + $mapper->setIsMultiple(true); + $lastEntity = NULL; + + $i = 0; + $hasValues = TRUE; + while($hasValues) { + $mapper->setIndex($i); + $entity = $this->newEntity($entityType, $bundleType, $data, $mapper); + drupal_alter('capx_new_fc', $entity); + $entity = $mapper->execute($entity, $data); + + // Here we check to see if anything came out the other end. + $hash = md5(serialize($entity)); + if ($hash == $lastEntity) { + $hasValues = FALSE; + break; + } + + // Not the same. Store the current entity as the last entity before saving + // or we will pollute the object with ids. + $lastEntity = $hash; + + // Save. + $entity->save(); + + // Storage for something that may need it. + $this->addFieldCollectionEntity($entity); + + // Add to index for next one. + $i++; + } + + // Remove the last two items from the entities array and from the parent + // entity as they are full of duplicate or not complete data. + $fieldName = $mapper->getBundleType(); + array_pop($this->fieldCollectionEntities); + array_pop($this->fieldCollectionEntities); + $rawParent = $parent->raw(); + array_pop($rawParent->{$fieldName}[LANGUAGE_NONE]); + array_pop($rawParent->{$fieldName}[LANGUAGE_NONE]); + $parent->set($rawParent); + + // Return all the things we just created. + return $this->getFieldCollectionEntities(); } - /** * New entity override as FieldCollections have some different defualts. - * @see parent:newEntity(); + * @see parent:newEntity(); */ public function newEntity($entityType, $bundleType, $data, $mapper) { $properties = array( 'type' => $bundleType, - 'uid' => 1, // @TODO - set this to something else - 'status' => 1, // @TODO - allow this to change - 'comment' => 0, // Any reason to set otherwise? + 'uid' => 1, // @TODO - set this to something else. + 'status' => 1, // @TODO - allow this to change. + 'comment' => 0, // Any reason to set otherwise?. 'promote' => 0, // Fogetaboutit. 'field_name' => $bundleType, ); - // Create an empty entity + // Create an empty entity. $entity = entity_create($entityType, $properties); $hostEntity = $this->getParentEntity(); @@ -59,32 +108,32 @@ public function newEntity($entityType, $bundleType, $data, $mapper) { // Wrap it up baby! $entity = entity_metadata_wrapper($entityType, $entity); - $entity = $mapper->execute($entity, $data); - $entity->save(); - - drupal_alter('capx_new_fc', $entity); - - // Storage for something that may need it. - $this->setFieldCollectionEntity($entity); - return $entity; } + /** + * Setter function + * @param Array $entities an array of field collection items + */ + protected function addFieldCollectionEntity($entity) { + $this->fieldCollectionEntities[] = $entity; + } + /** * Setter function - * @param FieldCollectionItem $entity the field collection item to be acted on + * @param Array $entities an array of field collection items */ - public function setFieldCollectionEntity($entity) { - $this->fieldCollectionEntity = $entity; + public function setFieldCollectionEntities($entities) { + $this->fieldCollectionEntities = $entities; } /** * Getter function * @return FieldCollectionItem the field collection item. */ - public function getFieldCollectionEntity() { - return $this->fieldCollectionEntity; + public function getFieldCollectionEntities() { + return $this->fieldCollectionEntities; } /** diff --git a/includes/CAPx/Drupal/Processors/FieldProcessors/EntityReferenceFieldProcessor.php b/includes/CAPx/Drupal/Processors/FieldProcessors/EntityReferenceFieldProcessor.php new file mode 100644 index 00000000..70a636e0 --- /dev/null +++ b/includes/CAPx/Drupal/Processors/FieldProcessors/EntityReferenceFieldProcessor.php @@ -0,0 +1,44 @@ + + */ + +namespace CAPx\Drupal\Processors\FieldProcessors; + +class EntityReferenceFieldProcessor { + + protected $entity; + protected $fieldName; + + public function __construct($entity, $fieldName) { + $this->entity = $entity; + $this->fieldName = $fieldName; + } + + /** + * Default implementation of put. + */ + public function put($relatedEntity) { + $id = $relatedEntity->getIdentifier(); + $entity = $this->entity; + $field = $entity->{$this->fieldName}; + + // Metadata wrapper is smarter than plain field info. + switch (get_class($field)) { + // Structure wrapper assumes we providing multiple columns. + case 'EntityStructureWrapper': + case 'EntityListWrapper': + $field->set(array($id)); + break; + + // Value wrapper assumes we providing single value. + case 'EntityDrupalWrapper': + case 'EntityValueWrapper': + $field->set($id); + break; + } + } + + +} diff --git a/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessor.php b/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessor.php index d3c4a8f7..6186b7df 100644 --- a/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessor.php +++ b/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessor.php @@ -37,7 +37,6 @@ * number_float number * taxonomy_term_reference options_select * - * * text text_textfield * text_long text_textarea * text_with_summary text_textarea_with_summary @@ -119,6 +118,10 @@ public function field($type) { $processor = new TextAreaFieldProcessor($entity, $fieldName, $type); break; + case "entityreference": + $processor = new EntityReferenceFieldProcessor($entity, $fieldName, $type); + break; + default: $processor = $this; } diff --git a/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessorAbstract.php b/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessorAbstract.php index 1199d470..e965ca9d 100644 --- a/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessorAbstract.php +++ b/includes/CAPx/Drupal/Processors/FieldProcessors/FieldProcessorAbstract.php @@ -86,7 +86,13 @@ public function put($data) { // Value wrapper assumes we providing single value. case 'EntityDrupalWrapper': case 'EntityValueWrapper': - $field->set(array_shift($data)); + + // Only Shift if it is an array. + if (is_array($data)) { + $data = array_shift($data); + } + + $field->set($data); break; } } diff --git a/includes/CAPx/Drupal/Processors/FieldProcessors/LinkFieldProcessor.php b/includes/CAPx/Drupal/Processors/FieldProcessors/LinkFieldProcessor.php index e0c7d76a..6f3c353a 100644 --- a/includes/CAPx/Drupal/Processors/FieldProcessors/LinkFieldProcessor.php +++ b/includes/CAPx/Drupal/Processors/FieldProcessors/LinkFieldProcessor.php @@ -30,12 +30,31 @@ public function put($data) { public function prepareData($data) { foreach ($data as $column => $values) { foreach ($values as $delta => $value) { + if (!is_string($value)) { $data[$column][$delta] = ''; // @todo: Do we really want to log this? // Keep in mind that log will be polluted. $this->logIssue(new \Exception(t('Received not allowed value, expecting string got %type.', array('%type' => gettype($value))))); } + + // If autotruncating is enabled lets do that here. This is to help + // avoid issues when trying to map data to the API. + if (variable_get("stanford_capx_autotruncate_textfields", TRUE)) { + + $maxlength = 2048; // Default for url. + + // Title has custom setting. + if ($column == "title") { + $info = field_info_field($this->fieldName); + $maxlength = $info['settings']['title_maxlength']; + } + + if (strlen($value) > $maxlength) { + $data[$column][$delta] = substr($value, 0, $maxlength); + } + } + } } diff --git a/includes/CAPx/Drupal/Processors/ProcessorAbstract.php b/includes/CAPx/Drupal/Processors/ProcessorAbstract.php index ed24a2bf..526f6918 100644 --- a/includes/CAPx/Drupal/Processors/ProcessorAbstract.php +++ b/includes/CAPx/Drupal/Processors/ProcessorAbstract.php @@ -17,8 +17,10 @@ abstract class ProcessorAbstract implements ProcessorInterface { /** * construction method - * @param EntityMapper $mapper EntityMapper - * @param Array $capData an array of cap data + * @param $mapper EntityMapper + * The mapper. + * @param $data array + * An array of cap data */ public function __construct($mapper, $data) { $this->setMapper($mapper); diff --git a/includes/CAPx/Drupal/Processors/PropertyProcessors/PropertyProcessorAbstract.php b/includes/CAPx/Drupal/Processors/PropertyProcessors/PropertyProcessorAbstract.php index e2b92ee5..4531deab 100644 --- a/includes/CAPx/Drupal/Processors/PropertyProcessors/PropertyProcessorAbstract.php +++ b/includes/CAPx/Drupal/Processors/PropertyProcessors/PropertyProcessorAbstract.php @@ -42,6 +42,15 @@ public function put($data) { return; } + // If autotruncating is enabled lets do that here. This is to help + // avoid issues when trying to map data to the API. + if (variable_get("stanford_capx_autotruncate_textfields", TRUE)) { + $maxlength = 255; // Default. + if (strlen($data) > $maxlength) { + $data = substr($data, 0, $maxlength); + } + } + try { $entity->{$propertyName}->set($data); } diff --git a/includes/CAPx/Drupal/Util/CAPx.php b/includes/CAPx/Drupal/Util/CAPx.php index 4d43e53b..13c8b07f 100644 --- a/includes/CAPx/Drupal/Util/CAPx.php +++ b/includes/CAPx/Drupal/Util/CAPx.php @@ -200,6 +200,30 @@ public static function getProfileIdByEntity($entity) { return isset($query['profile_id']) ? $query['profile_id'] : FALSE; } + /** + * Returns a loaded entity or false. + * + */ + public static function getEntityIdByGUUID($importerMachineName, $profileId, $guuid) { + + $query = db_select("capx_profiles", 'capx') + ->fields('capx', array('entity_type', 'entity_id')) + ->condition('importer', $importerMachineName) + ->condition('profile_id', $profileId) + ->condition('guuid', $guuid) + ->orderBy('id', 'DESC') + ->execute() + ->fetchAssoc(); + + // If we gots one send it back. + if (isset($query['entity_id'])) { + $entities = entity_load($query['entity_type'], array($query['entity_id'])); + return array_pop($entities); + } + + return FALSE; + } + /** * Create new profile record. * @@ -209,7 +233,7 @@ public static function getProfileIdByEntity($entity) { * @param Entity $entity * The entity that was just saved. */ - public static function insertNewProfileRecord($entity, $profileId, $etag, $importer) { + public static function insertNewProfileRecord($entity, $profileId, $etag, $importer, $guuid = '') { // BEAN is returning its delta when using this. // $entityId = $entity->getIdentifier(); @@ -229,6 +253,7 @@ public static function insertNewProfileRecord($entity, $profileId, $etag, $impor 'sync' => 1, 'last_sync' => $time, 'orphaned' => 0, + 'guuid' => $guuid, ); $yes = drupal_write_record('capx_profiles', $record); @@ -247,7 +272,7 @@ public static function insertNewProfileRecord($entity, $profileId, $etag, $impor * @param [type] $importer [description] * @return [type] [description] */ - public static function updateProfileRecord($entity, $profileId, $etag, $importer) { + public static function updateProfileRecord($entity, $profileId, $etag, $importer, $guuid = '') { $time = time(); // BEAN is returning its delta when using this. @@ -265,6 +290,7 @@ public static function updateProfileRecord($entity, $profileId, $etag, $importer 'profile_id' => $profileId, 'etag' => $etag, 'last_sync' => $time, + 'guuid' => $guuid, ); $keys = array( @@ -272,6 +298,7 @@ public static function updateProfileRecord($entity, $profileId, $etag, $importer 'entity_id', 'importer', 'profile_id', + 'guuid', ); $yes = drupal_write_record('capx_profiles', $record, $keys); diff --git a/includes/CAPx/Drupal/Util/CAPxConnection.php b/includes/CAPx/Drupal/Util/CAPxConnection.php index f4685c3f..406c6ab7 100644 --- a/includes/CAPx/Drupal/Util/CAPxConnection.php +++ b/includes/CAPx/Drupal/Util/CAPxConnection.php @@ -116,6 +116,10 @@ public static function testApiConnection($token = null, $endpoint = null) { $client = new HTTPClient(); $client->setEndpoint($endpoint); $client->setApiToken($token); + $opts = $client->getHttpOptions(); + $opts['connect_timeout'] = 2.00; + $opts['timeout'] = 5.00; + $client->setHttpOptions($opts); try { $results = $client->api('orgs')->getOrg('BSWS'); diff --git a/includes/CAPx/Drupal/Util/CAPxImporter.php b/includes/CAPx/Drupal/Util/CAPxImporter.php index 4f1419dd..3af744d6 100644 --- a/includes/CAPx/Drupal/Util/CAPxImporter.php +++ b/includes/CAPx/Drupal/Util/CAPxImporter.php @@ -26,6 +26,7 @@ use CAPx\Drupal\Importer\Orphans\Lookups\LookupMissingFromSunetList; use CAPx\Drupal\Importer\Orphans\Lookups\LookupOrgOrphans; use CAPx\Drupal\Importer\Orphans\Lookups\LookupWorkgroupOrphans; +use CAPx\Drupal\Importer\Orphans\Lookups\LookupMissingMultipleByGUUID; class CAPxImporter { @@ -155,39 +156,59 @@ public static function getCronOptions() { * @return [type] [description] */ public static function getEntityOrphanator($importerName, $profiles = array()) { - $orphanator = NULL; + $orphanator = NULL; $importer = CAPxImporter::loadEntityImporter($importerName); - if ($importer) { - $lookups = array(); - $comparisons = array(); + // If we couldn't load the importer we need to log and error out. + if (!$importer) { + $vars = array( + '%name' => $importerName, + '!log' => l(t('log messages'), 'admin/reports/dblog'), + ); + drupal_set_message(t('There was an issue loading the importer with %name machine name. Check !log.', $vars), 'error'); + return; + } + + // Track wether this is a multiple entity importer or not. The checks differ + // for these types. + $mapper = $importer->getMapper(); + $multiple = $mapper->isMultiple(); + $lookups = array(); + $comparisons = array(); - // Load all the lookups... - $lookups[] = new LookupMissingFromAPI(); + // Load the lookups... + $lookups[] = new LookupMissingFromAPI(); + + // Only want these if not a multiple import. + if (!$multiple) { $lookups[] = new LookupMissingFromSunetList(); $lookups[] = new LookupOrgOrphans(); $lookups[] = new LookupWorkgroupOrphans(); + } - // Load all the comparisons... - $comparisons[] = new CompareMissingFromAPI(); + // Load the comparisons... + $comparisons[] = new CompareMissingFromAPI(); + + // Only want these if not a multiple import. + if (!$multiple) { $comparisons[] = new CompareOrgCodesSunet(); $comparisons[] = new CompareOrgCodesWorkgroups(); $comparisons[] = new CompareSunetOrgCodes(); $comparisons[] = new CompareSunetWorkgroups(); $comparisons[] = new CompareWorkgroupsOrgCodes(); $comparisons[] = new CompareWorkgroupsSunet(); - - $orphanator = new EntityImporterOrphans($importer, $profiles, $lookups, $comparisons); } - else { - $vars = array( - '%name' => $importerName, - '!log' => l(t('log messages'), 'admin/reports/dblog'), - ); - drupal_set_message(t('There was an issue loading the importer with %name machine name. Check !log.', $vars), 'error'); + + // For multiple entity importers we need to check not only if the profile + // exists but the part of the profile that is being imported still exists. + if ($multiple) { + $lookups[] = new LookupMissingMultipleByGUUID(); } + // Load it up and send it along the way. + $orphanator = new EntityImporterOrphans($importer, $profiles, $lookups, $comparisons); + return $orphanator; } } diff --git a/modules/capx_auto_nodetitle/capx_auto_nodetitle.info b/modules/capx_auto_nodetitle/capx_auto_nodetitle.info index 97581e9a..6256954d 100644 --- a/modules/capx_auto_nodetitle/capx_auto_nodetitle.info +++ b/modules/capx_auto_nodetitle/capx_auto_nodetitle.info @@ -2,7 +2,7 @@ name = CAPX Auto Node Title Support description = Allows support for the auto_nodetitle module core = 7.x package = Stanford -version = 7.x-1.2-dev +version = 7.x-1.3 project = capx_auto_nodetitle dependencies[] = auto_nodetitle dependencies[] = stanford_capx diff --git a/modules/capx_issue_collector/capx_issue_collector.info b/modules/capx_issue_collector/capx_issue_collector.info index 92f390a1..61217b9d 100644 --- a/modules/capx_issue_collector/capx_issue_collector.info +++ b/modules/capx_issue_collector/capx_issue_collector.info @@ -1,6 +1,6 @@ name = Stanford CAPx Issue Collector description = Provides a feedback link to the CAPx Working Group Jira project. core = 7.x -version = 7.x-1.2-dev +version = 7.x-1.3 package = Stanford CAPx ; scripts[] = capx_issue_collector.js diff --git a/stanford_capx.blocks.inc b/stanford_capx.blocks.inc index ad3ebd1b..b758dc5e 100644 --- a/stanford_capx.blocks.inc +++ b/stanford_capx.blocks.inc @@ -153,7 +153,7 @@ function stanford_capx_connection_status_block() { $content .= '

' . t('Not Connected') . '

'; if ($connection->code == 0) { - $content .= t("Please go to the !connt page to connect to the CAP API.", array("!connt" => l(t("CAPx connection"), "admin/config/capx/connect"))); + $content .= t("Please go to the !connt page to connect to the CAP API.", array("!connt" => l(t("CAPx settings"), "admin/config/capx/settings"))); } else { $vars = array('@code' => $connection->code, '@message' => $connection->message); @@ -218,6 +218,7 @@ function stanford_capx_data_browser_launch_block() { $rows[] = array('Graduation year', '$.education.*.yearIssued'); $rows[] = array('Personal info links title', '$.internetLinks.*.label.text'); $rows[] = array('Personal info links url', '$.internetLinks.*.url'); + $rows[] = array('Lab website url', '$.internetLinks.[?(@.type="Lab Site")].url'); $rows[] = array('Fields of interest', '$.professionalInterests.text'); $rows[] = array('Mailing address', '$.primaryContact.address'); $rows[] = array('Mailing address city', '$.primaryContact.city'); diff --git a/stanford_capx.forms.inc b/stanford_capx.forms.inc index ea5dde06..dd450c6a 100644 --- a/stanford_capx.forms.inc +++ b/stanford_capx.forms.inc @@ -13,83 +13,6 @@ use CAPx\Drupal\Util\CAPxMapper; use CAPx\Drupal\Util\CAPxImporter; use \Peekmo\JsonPath\JsonStore as JsonParser; -// ///////////////////////////////////////////////////////////////////////////// -// CONNECT FORM -// ///////////////////////////////////////////////////////////////////////////// - - -/** - * Connection details form builder. - */ -function stanford_capx_forms_connect_form($form, &$form_state) { - - $username = CAPx::getAuthUsername(); - $connection = CAPxConnection::testConnection(); - - $form['status'] = array( - '#type' => 'fieldset', - '#title' => t('Connection Status'), - '#collapsible' => TRUE, - ); - - $form['status']['status'] = array( - '#markup' => stanford_capx_connection_status_block(), - ); - - $form['auth'] = array( - '#type' => 'fieldset', - '#title' => t('Authorization'), - '#collapsible' => TRUE, - '#collapsed' => $connection->status, - ); - - $form['auth']['description'] = array( - '#markup' => t('Please enter your authentication information for the CAP API.'), - ); - - $form['auth']['stanford_capx_username'] = array( - '#type' => 'textfield', - '#title' => t('Client ID:'), - '#default_value' => $username, - '#required' => TRUE, - ); - - $form['auth']['stanford_capx_password'] = array( - '#type' => 'password', - '#title' => t('Password:'), - ); - - $form['advanced'] = array( - '#type' => 'fieldset', - '#title' => t('Advanced'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#description' => t('Advanced setting for CAP API and authentication URIs'), - ); - - $form['advanced']['stanford_capx_api_base_url'] = array( - '#type' => 'textfield', - '#title' => t('Endpoint'), - '#description' => t('CAP API endpoint URI, only useful when switching between development/production environment.'), - '#default_value' => CAPx::getAPIEndpoint(), - '#required' => TRUE, - ); - - $form['advanced']['stanford_capx_api_auth_uri'] = array( - '#type' => 'textfield', - '#title' => t('Authentication URI'), - '#description' => t('CAP API authentication URI.'), - '#default_value' => CAPx::getAuthEndpoint(), - '#required' => TRUE, - ); - - $form['submit'] = array( - '#type' => 'submit', - '#value' => t('Save connection settings'), - ); - - return $form; -} /** * [stanford_capx_forms_connect_form_validate description] @@ -104,46 +27,17 @@ function stanford_capx_forms_connect_form_validate($form, &$form_state) { $endpoint = $form_state['values']['stanford_capx_api_base_url']; $authpoint = $form_state['values']['stanford_capx_api_auth_uri']; - // If password changed validate auth. - if (!empty($password)) { - $auth_test = CAPxConnection::testAuthConnection($username, $password, $authpoint); - if (!$auth_test->status) { - form_set_error('stanford_capx_username', t('Could not authenticate with server. Invalid credentials. Please check your username and password.')); - form_set_error('stanford_capx_password'); - } - } - - // In case the end points change we need to get pass from db. - $password = !empty($password) ? $password : CAPx::getAuthPassword(); - - // If authpoint changed. - if ($authpoint !== "https://authz.stanford.edu/oauth/token") { - - if (!isset($auth_test)) { - $auth_test = CAPxConnection::testAuthConnection($username, $password, $authpoint); - } - - if (!$auth_test->status) { - form_set_error("stanford_capx_api_auth_uri", t("Please check your authorization url.")); - } - + // Do not process a change on an empty password. Re-apply the password from + // the database because the system_form will save an empty value otherwise. + if (empty($password)) { + return; } - // If endpoint changed. - if ($endpoint !== "https://api.stanford.edu") { - - if (!isset($auth_test)) { - $auth_test = CAPxConnection::testAuthConnection($username, $password, $authpoint); - } - - if (isset($auth_test->token)) { - $token = $auth_test->token; - $end_test = CAPxConnection::testApiConnection($token, $endpoint); - if (!$end_test->status) { - form_set_error("stanford_capx_api_base_url", t("Could not connect to API endpoint.")); - } - } - + // If password changed validate auth. + $auth_test = CAPxConnection::testAuthConnection($username, $password, $authpoint); + if (!$auth_test->status) { + form_set_error('stanford_capx_username', t('Could not authenticate with server. Invalid credentials. Please check your username and password.')); + form_set_error('stanford_capx_password'); } } @@ -156,7 +50,7 @@ function stanford_capx_forms_connect_form_validate($form, &$form_state) { */ function stanford_capx_forms_connect_form_submit($form, &$form_state) { - drupal_set_message(t('Configuration options were saved.')); + drupal_set_message(t("Configuration options saved."), "status"); // Username and Pass. if (!empty($form_state['values']['stanford_capx_username']) @@ -167,16 +61,15 @@ function stanford_capx_forms_connect_form_submit($form, &$form_state) { variable_set('stanford_capx_username', $username); variable_set('stanford_capx_password', $password); + // In order to get here we have to have a valid connection. + // Lets get the org codes and schema. + stanford_capx_schema_refresh(); + stanford_capx_organizations_sync(); } // Endpoints. variable_set('stanford_capx_api_base_url', $form_state['values']['stanford_capx_api_base_url']); variable_set('stanford_capx_api_auth_uri', $form_state['values']['stanford_capx_api_auth_uri']); - - // In order to get here we have to have a valid connection. Lets get the org codes - // and schema. - stanford_capx_schema_refresh(); - stanford_capx_organizations_sync(); } // ///////////////////////////////////////////////////////////////////////////// @@ -314,11 +207,45 @@ function stanford_capx_mapper_form_get_entity_mapping_form($form, &$form_state, } + $form['entity-mapping']['multiple-entities'] = array( + '#type' => 'checkbox', + '#title' => t("Would you like to create multiple entities per imported bundle for the type of content you're importing?"), + '#description' => t('By default, a mapper will create one entity per person. If you wanted to import a persons publications you would need multiple entities per person. Check this box for multiple.'), + '#default_value' => isset($mapper->multiple) ? $mapper->multiple : FALSE, + ); + + $form['entity-mapping']['subquery'] = array( + '#type' => 'textfield', + '#title' => t("Multiple entity creation sub query"), + '#description' => t('Which part of the schema will you be using to create multiple entities. Ie: for publications enter: $.publications.*'), + '#default_value' => isset($mapper->subquery) ? $mapper->subquery : '', + '#states' => array( + 'visible' => array( + ':input[name="multiple-entities"]' => array('checked' => TRUE), + ), + ), + ); + + $form['entity-mapping']['guuid-query'] = array( + '#type' => 'textfield', + '#title' => t("Unique ID of entity"), + '#description' => t('Do the entities you are creating have a unique id? For example, publications have a publicationId and $.publications.*.publicationId would be the value to add to this field. If this is provided then CAPx can update the entity in place. If there is no unique ID available then the CAPx module will delete and re-import the entities each time there is a change to the profile.'), + '#default_value' => isset($mapper->guuidquery) ? $mapper->guuidquery : '', + '#states' => array( + 'visible' => array( + ':input[name="multiple-entities"]' => array('checked' => TRUE), + ), + ), + ); + return $form; } /** * [stanford_capx_mapper_form_get_field_mapping_form description] + * + * @todo: Refactor this stupidly long function. + * * @param [type] $form [description] * @param [type] $form_state [description] * @return [type] [description] @@ -327,6 +254,7 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ $entity_types = capx_entity_get_info(); $field_collections = array(); + $references = array(); global $CAP_RESTRICTED_FIELD_PROPERTIES; // Add some styles and javascript @@ -430,6 +358,7 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ continue; } + // Bundles. foreach ($entity_info['bundles'] as $bundle_machine_name => $bundle_info) { // Skip bundles that dont match a loaded mapper. @@ -455,6 +384,7 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ ), ); + // Loop through all the fields on this entity / bundle. foreach ($fields as $field_machine_name => $field_info) { // Field collections need their own fieldset and fields. @@ -463,6 +393,13 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ continue; } + // Entity References are special and need special handling. + if (strpos($field_info['type'], 'list') !== FALSE) { + $references[$entity_machine_name][$bundle_machine_name][$field_machine_name] = $field_info; + continue; + } + + // Get field base and instance information. $field_info_field = field_info_field($field_machine_name); $field_instance_info = field_info_instance($entity_machine_name, $field_machine_name, $bundle_machine_name); @@ -475,43 +412,8 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ '#theme_wrappers' => array('stanford_capx_columns_to_the_right'), ); - // Do not give all options for the file field. File field processor - // will take care of most. - if ($field_info_field['type'] == "file" || $field_info_field['type'] == "image") { - $form['field-mapping'][$field_container][$field_machine_name][0] = array( - '#type' => 'textfield', - '#title' => t('File Information'), - '#description' => t('In your JSON Path selector please pass in the whole array of image information. eg: $.profilePhotos.bigger'), - '#default_value' => @$mapper->fields[$field_machine_name][0], - ); - - if ($field_instance_info['required']) { - $form['field-mapping'][$field_container][$field_machine_name]['#attributes']['class'][] = "required"; - } - - continue; - } - - // Relation module doesn't have this column key set. - if (!isset($field_info_field['columns'])) { - continue; - } - - foreach ($field_info_field['columns'] as $column_key => $column_info) { - if (!in_array($column_key, $CAP_RESTRICTED_FIELD_PROPERTIES)) { - - $form['field-mapping'][$field_container][$field_machine_name][$column_key] = array( - '#type' => 'textfield', - '#title' => t("@var", array("@var" => $column_key)), - '#description' => !empty($column_info['description']) ? t("@var", array("@var" => $column_info['description'])) : "", - '#default_value' => isset($mapper->fields[$field_machine_name][$column_key]) ? $mapper->fields[$field_machine_name][$column_key] : "", - ); - - if ($field_instance_info['required']) { - $form['field-mapping'][$field_container][$field_machine_name]['#attributes']['class'][] = "required"; - } - } - } + // Get this forms fields for the entity/bundle fields. + stanford_capx_mapper_form_get_fields_fields($form['field-mapping'][$field_container][$field_machine_name], $form_state, $field_info_field, $field_instance_info, $field_machine_name, $mapper); } @@ -524,11 +426,10 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ foreach ($field_collections as $entity_machine_name => $bundles) { foreach ($bundles as $bundle_machine_name => $fields) { foreach ($fields as $field_machine_name => $field_info) { - $instances = field_info_instances('field_collection_item', $field_machine_name); $collection_container = $entity_machine_name . "-" . $bundle_machine_name . "-collections"; - // Fieldset for all fields on this entity -> bundle -> collection field + // Fieldset for all fields on this entity -> bundle -> collection field. $form['field-mapping'][$collection_container][$field_machine_name] = array( '#type' => 'fieldset', '#title' => t('@name Field Collection', array("@name" => $field_machine_name)), @@ -546,7 +447,6 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ foreach ($instances as $fc_field_name => $instance_info) { $fc_field_info = field_info_field($fc_field_name); - $form['field-mapping'][$collection_container][$field_machine_name][$fc_field_name] = array( '#type' => 'container', '#title' => t("@var", array("@var" => $instance_info['label'])), @@ -556,34 +456,57 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ '#theme_wrappers' => array('stanford_capx_columns_to_the_right'), ); - // Do not give all options for the file field. File field processor - // will take care of most. - if ($fc_field_info['type'] == "file" || $fc_field_info['type'] == "image") { - $form['field-mapping'][$collection_container][$field_machine_name][$fc_field_name][0] = array( - '#type' => 'textfield', - '#title' => t('File Info'), - '#description' => t('In your JSON Path selector please pass in the whole array of image information. eg: $.profilePhotos.bigger'), - '#default_value' => @$mapper->collections[$field_machine_name][$fc_field_name][0], - ); - continue; - } + // Run through the field. + stanford_capx_mapper_form_get_fields_fields($form['field-mapping'][$collection_container][$field_machine_name][$fc_field_name], $form_state, $fc_field_info, $instance_info, $field_machine_name, $mapper); + + } + + } + } + } - foreach ($fc_field_info['columns'] as $column_key => $column_info) { - if (!in_array($column_key, $CAP_RESTRICTED_FIELD_PROPERTIES)) { - $form['field-mapping'][$collection_container][$field_machine_name][$fc_field_name][$column_key] = array( - '#type' => 'textfield', - '#title' => t("@var", array("@var" => $column_key)), - '#description' => t(@$column_info['description']), - '#default_value' => @$mapper->collections[$field_machine_name][$fc_field_name][$column_key], - ); + // ENTITY REFERENCES + // ---------------------------------------------------------------------- - if ($instance_info['required']) { - $form['field-mapping'][$collection_container][$field_machine_name][$fc_field_name][$column_key]["#attributes"]["class"] = "required"; - } + foreach ($references as $entity_machine_name => $bundles) { + foreach ($bundles as $bundle_machine_name => $fields) { - } - } - } + // Create the container. + $reference_container = $entity_machine_name . "-" . $bundle_machine_name . "-references"; + + // Fieldset for all fields on this entity -> bundle -> reference field. + $form['field-mapping'][$reference_container] = array( + '#type' => 'fieldset', + '#title' => t('Entity References', array("@name" => $field_machine_name)), + '#description' => t(''), + '#collapsed' => TRUE, + '#collapsible' => TRUE, + '#states' => array( + 'visible' => array( + 'select[name="entity-type"]' => array('value' => $entity_machine_name), + 'select[name="bundle-' . $entity_machine_name . '"]' => array('value' => $bundle_machine_name), + ), + ), + ); + + // Now loop through the fields. + foreach ($fields as $field_machine_name => $field_info) { + + // Get field base and instance information. + $field_info_field = field_info_field($field_machine_name); + $field_instance_info = field_info_instance($entity_machine_name, $field_machine_name, $bundle_machine_name); + + $form['field-mapping'][$reference_container][$field_machine_name] = array( + '#type' => 'container', + '#title' => t("@var", array("@var" => $field_instance_info['label'])), + '#description' => t("@var", array("@var" => $field_instance_info['description'])), + '#field_info' => $field_info_field, + '#field_instance_info' => $field_instance_info, + '#theme_wrappers' => array('stanford_capx_columns_to_the_right'), + ); + + // Get this forms fields for the entity/bundle fields. + stanford_capx_mapper_form_get_fields_fields($form['field-mapping'][$reference_container][$field_machine_name], $form_state, $field_info_field, $field_instance_info, $field_machine_name, $mapper); } } @@ -592,6 +515,104 @@ function stanford_capx_mapper_form_get_field_mapping_form($form, &$form_state, $ return $form; } +/** + * [stanford_capx_mapper_form_get_fields_fields description] + * @param [type] $form [description] + * @param [type] $form_state [description] + * @param [type] $field_info_field [description] + * @param [type] $field_container [description] + * @param [type] $mapper [description] + * @return [type] [description] + */ +function stanford_capx_mapper_form_get_fields_fields(&$form_tree, &$form_state, $field_info_field, $field_instance_info, $field_machine_name, $mapper) { + + global $CAP_RESTRICTED_FIELD_PROPERTIES; + + // Relation module doesn't have this column key set. + if (!isset($field_info_field['columns'])) { + return; + } + + // FILE FIELD + // --------------------------------------------------------------------------- + // Do not give all options for the file field. File field processor + // will take care of most. + if ($field_info_field['type'] == "file" || $field_info_field['type'] == "image") { + $form_tree[0] = array( + '#type' => 'textfield', + '#title' => t('File Information'), + '#description' => t('In your JSON Path selector please pass in the whole array of image information. eg: $.profilePhotos.bigger'), + '#default_value' => @$mapper->fields[$field_machine_name][0], + ); + + if ($field_instance_info['required'] && empty($field_instance_info['default_value'])) { + $form_tree['#attributes']['class'][] = "required"; + } + return; + } + + // ENTITY REFERENCE FIELDS + // --------------------------------------------------------------------------- + if ($field_info_field['type'] == "entityreference") { + + $importers = CAPxImporter::getAllEntityImporters(); + $options = array('none' => t("- None -")); + $options += array_map('stanford_capx_key_name_array_map', $importers); + + $form_tree[0] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("Referencing Importer"), + '#description' => t("Select the importer with the reference you would like to attach this item to."), + '#default_value' => isset($mapper->references[$field_machine_name]) ? $mapper->references[$field_machine_name][0] : '', + ); + + return; + } + + + // ALL OTHER FIELDS + // --------------------------------------------------------------------------- + + foreach ($field_info_field['columns'] as $column_key => $column_info) { + if (!in_array($column_key, $CAP_RESTRICTED_FIELD_PROPERTIES)) { + + // Get the default value for the field. If the field was in a field + // collection then we need to adjust. + $default_value = isset($mapper->fields[$field_machine_name][$column_key]) ? $mapper->fields[$field_machine_name][$column_key] : ""; + if (isset($field_info_field['bundles']['field_collection_item']) && !empty($field_info_field['bundles']['field_collection_item'])) { + if (!empty($mapper->collections[$field_machine_name][$field_info_field['field_name']][$column_key])) { + $default_value = $mapper->collections[$field_machine_name][$field_info_field['field_name']][$column_key]; + } + } + + $form_tree[$column_key] = array( + '#type' => 'textfield', + '#title' => t("@var", array("@var" => $column_key)), + '#description' => !empty($column_info['description']) ? t("@var", array("@var" => $column_info['description'])) : "", + '#default_value' => $default_value, + ); + + // Only require a field if it is required and there is no default value. + if ($field_instance_info['required'] && empty($field_instance_info['default_value'])) { + $form_tree['#attributes']['class'][] = "required"; + } + } + } + +} + +/** + * [stanford_capx_key_name_array_map description] + * @param [type] $keys [description] + * @param [type] $importers [description] + * @return [type] [description] + */ +function stanford_capx_key_name_array_map($importer) { + return $importer->getImporter()->title; +} + + /** * [stanford_capx_mapper_machine_name_exits description] * @param [type] $name [description] @@ -747,7 +768,7 @@ function stanford_capx_mapper_form_validate($form, $form_state) { } // Set the error messages after everything has been looped through. - stanford_capx_mapper_form_validate_element_set_messages(); + // stanford_capx_mapper_form_validate_element_set_messages(); // Ensure all required fields were posted. Because of the way the form works // we cannot place a required on to each of the form element fields as there @@ -778,7 +799,6 @@ function stanford_capx_mapper_form_validate_required_fields(&$form, &$form_state // Get all the properties of the entity type. $entity_info = entity_get_property_info($entity); $properties = $entity_info['properties']; - $fields = isset($entity_info['bundles']) ? $entity_info['bundles'][$bundle]['properties'] : array(); // Properties validation. @@ -792,17 +812,31 @@ function stanford_capx_mapper_form_validate_required_fields(&$form, &$form_state // Field validation. foreach ($fields as $field_name => $value) { + // Check if the value is required. if (isset($value['required']) && $value['required']) { + // Check if the value exists. if (!isset($form_state['values']['field-mapping'][$entity . "-" . $bundle . "-fields"][$field_name])) { continue; } + // Check if there is a required class on the field as we add this to + // bypass the multiple hide/show options and required fields. + if (!isset($form['field-mapping'][$entity . "-" . $bundle . "-fields"]['#attributes']['class'])) { + continue; + } + + // No css class, no need to validate. + if (!in_array("required", $form['field-mapping'][$entity . "-" . $bundle . "-fields"]['#attributes']['class'])) { + continue; + } + + $columns = $form_state['values']['field-mapping'][$entity . "-" . $bundle . "-fields"][$field_name]; // Loop through the columns and check for values. foreach ($columns as $column_name => $value) { - if (empty($value) && $column_name != 'summary') { + if (empty($value) && $column_name !== 'summary') { $element = "field-mapping][" . $entity . "-" . $bundle . "-" . "fields][" . $field_name . "][" . $column_name; form_set_error($element, t("@name field is required", array("@name" => $field_name))); break; @@ -874,32 +908,32 @@ function stanford_capx_mapper_form_validate_element($element, $parents = array() /** * Sets the message for the element validation. */ -function stanford_capx_mapper_form_validate_element_set_messages() { - $form_error = &drupal_static("stanford_capx_mapper_form_validate_element_error", FALSE); - $form_field_warnings = &drupal_static("stanford_capx_mapper_form_validate_element_warnings", FALSE); - - // Set the error message once when the recursive calls have finished. - if ($form_error) { - $field_warnings_list = theme('item_list', array( - 'title' => '', - 'items' => $form_field_warnings, - 'type' => 'ul', - 'attributes' => array() - )); - $browser_link = l(t('Data Browser'), - 'admin/config/capx/data-browser', array( - 'attributes' => array('target' => '_blank') - )); - - drupal_set_message( - t('The following selectors did not match an element in the profile schema but may be valid. If you know the selector is valid please disregard this message. Try using the !databrowser to find a valid selector.!warnings', - array( - '!warnings' => $field_warnings_list, - '!databrowser' => $browser_link, - ) - ), 'warning'); - } -} +// function stanford_capx_mapper_form_validate_element_set_messages() { +// $form_error = &drupal_static("stanford_capx_mapper_form_validate_element_error", FALSE); +// $form_field_warnings = &drupal_static("stanford_capx_mapper_form_validate_element_warnings", FALSE); + +// // Set the error message once when the recursive calls have finished. +// if ($form_error) { +// $field_warnings_list = theme('item_list', array( +// 'title' => '', +// 'items' => $form_field_warnings, +// 'type' => 'ul', +// 'attributes' => array() +// )); +// $browser_link = l(t('Data Browser'), +// 'admin/config/capx/data-browser', array( +// 'attributes' => array('target' => '_blank') +// )); + +// drupal_set_message( +// t('The following selectors did not match an element in the profile schema but may be valid. If you know the selector is valid please disregard this message. Try using the !databrowser to find a valid selector.!warnings', +// array( +// '!warnings' => $field_warnings_list, +// '!databrowser' => $browser_link, +// ) +// ), 'warning'); +// } +// } /** * Submit handler for the mapper form. Create and save the mapper entity. @@ -924,12 +958,16 @@ function stanford_capx_mapper_form_submit($form, $form_state) { $settings['fields'] = array(); $settings['properties'] = array(); $settings['collections'] = array(); + $settings['references'] = array(); $entity = $values['entity-type']; $bundle = $values['bundle-' . $entity]; $settings['entity_type'] = $entity; $settings['bundle_type'] = $bundle; + $settings['multiple'] = $values['multiple-entities']; + $settings['subquery'] = $values['subquery']; + $settings['guuidquery'] = $values['guuid-query']; // If there are properties... $properties = @$values['field-mapping'][$entity . "-properties"]; @@ -943,6 +981,13 @@ function stanford_capx_mapper_form_submit($form, $form_state) { $settings['fields'] = $fields; } + // If there are collections... + $references = @$values['field-mapping'][$entity . "-" . $bundle . "-references"]; + // We have values! + if (!empty($references)) { + $settings['references'] = $references; + } + // If there are collections... $collections = @$values['field-mapping'][$entity . "-" . $bundle . "-collections"]; @@ -1373,6 +1418,43 @@ function stanford_capx_importer_form($form, &$form_state, $importer = NULL) { return $form; } +/** + * [stanford_capx_config_settings_data_sync_form description] + * @param [type] $form [description] + * @param [type] $form_state [description] + * @return [type] [description] + */ +function stanford_capx_config_settings_data_sync_form($form, $form_state) { + $form['orggroup'] = array( + '#type' => "fieldset", + '#title' => t("Organizations & Schema"), + '#description' => t("The CAPx module needs some information from the CAP API in order to function properly. Below are actions that require communication with the CAP API server and you must be connected before you can run these tasks."), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + // Organizations. + $form['orggroup']['orgcodeinfo']['#markup'] = "

Organizations

"; + $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . t("Organization codes come from the CAP API and are saved to a taxonomy. This taxonomy powers the organization code autocomplete in the import section.") . "

"; + $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . t("Last sync: @date", array("@date" => variable_get('capx_last_orgs_sync', 'never'))) . "

"; + $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . t("View all organization codes: !link", array("!link" => l(t('Organization taxonomy'), 'admin/structure/taxonomy/capx_organizations'))) . "

"; + $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . l(t('Get organization data'), 'admin/config/capx/organizations/sync', array("attributes" => array('class' => array('btn button')), 'query' => array('destination' => current_path()))) . "

"; + + // Schema. + $form['orggroup']['schemainfo']['#markup'] = "

Schema Information

"; + $form['orggroup']['schemainfo']['#markup'] .= "

" . t("The CAP API provides a schema of the information available. The CAPx module needs this in order to power the data browser that is found on a mapping page.") . "

"; + $form['orggroup']['schemainfo']['#markup'] .= "

" . t("Last sync: @date", array("@date" => variable_get('capx_last_schema_sync', 'never'))) . "

"; + $form['orggroup']['submit'] = array( + '#type' => 'button', + '#value' => 'Get schema information', + '#op' => 'submit', + // '#element_validate' => array("stanford_capx_schema_refresh"), // Yes, I know this is abuse. + ); + + // $form['#submit'][] = "stanford_capx_schema_refresh"; + return $form; +} + /** * AJAX callback. */ @@ -1546,7 +1628,7 @@ function stanford_capx_admin_config_importer_delete($form, &$form_state, $machin $form['profile_action'] = array( '#type' => 'select', '#title' => t('What would you like to do with the items that are associated with this importer?'), -// '#description' => t('Perform an action on all of the items that have been imported by this importer.'), + // '#description' => t('Perform an action on all of the items that have been imported by this importer.'), '#options' => array( 'nothing' => t('Do nothing and leave the items as they are'), 'delete' => t('Delete all of the items'), @@ -1924,6 +2006,70 @@ function stanford_capx_schema_format_validation($schema = NULL) { return $formatted_schema; } +/** + * Auth settings form. Was previously the connect page. + * @param [type] $form [description] + * @param [type] &$form_state [description] + * @return [type] [description] + */ +function stanford_capx_config_auth_settings_form($form, &$form_state) { + $username = CAPx::getAuthUsername(); + $connection = CAPxConnection::testConnection(); + + $form['auth'] = array( + '#type' => 'fieldset', + '#title' => t('Authorization'), + '#collapsible' => TRUE, + '#collapsed' => $connection->status, + '#weight' => -10, + ); + + $form['auth']['description'] = array( + '#markup' => t('Please enter your authentication information for the CAP API. If you don\'t have these credentials yet you can !helpsu.', array('!helpsu' => l("File a HelpSU request", "https://helpsu.stanford.edu/helpsu/3.0/auth/helpsu-form?pcat=CAP_API&dtemplate=CAP-OAuth-Info"))), + ); + + $form['auth']['stanford_capx_username'] = array( + '#type' => 'textfield', + '#title' => t('Client ID:'), + '#default_value' => $username, + '#required' => TRUE, + ); + + $form['auth']['stanford_capx_password'] = array( + '#type' => 'password', + '#title' => t('Password:'), + ); + + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Advanced setting for CAP API and authentication URIs'), + '#access' => user_access("capx advanced administrator"), + ); + + $form['advanced']['stanford_capx_api_base_url'] = array( + '#type' => 'textfield', + '#title' => t('Endpoint'), + '#description' => t('CAP API endpoint URI, only useful when switching between development/production environment.'), + '#default_value' => CAPx::getAPIEndpoint(), + '#required' => TRUE, + ); + + $form['advanced']['stanford_capx_api_auth_uri'] = array( + '#type' => 'textfield', + '#title' => t('Authentication URI'), + '#description' => t('CAP API authentication URI.'), + '#default_value' => CAPx::getAuthEndpoint(), + '#required' => TRUE, + ); + + $form['#validate'][] = "stanford_capx_forms_connect_form_validate"; + $form['#submit'][] = "stanford_capx_forms_connect_form_submit"; + + return $form; +} /** * The global configuration settings for CAPx form builder. @@ -1959,11 +2105,23 @@ function stanford_capx_config_settings_form($form, &$form_state) { '#default_value' => variable_get('stanford_capx_default_field_format', 'filtered_html'), ); - $form = system_settings_form($form); - $form['actions']['submit']['#value'] = t("Save settings"); + $form["#submit"][] = "stanford_capx_config_settings_form_submit"; + return $form; } +/** + * Submit function for stanford_capx_config_settings_form + * @param [type] $form [description] + * @param [type] &$form_state [description] + * @return [type] [description] + */ +function stanford_capx_config_settings_form_submit($form, &$form_state) { + $values = $form_state["values"]; + variable_set("stanford_capx_batch_limit", check_plain($values['stanford_capx_batch_limit'])); + variable_set("stanford_capx_default_field_format", check_plain($values['stanford_capx_default_field_format'])); +} + /** * Validates wether or not a path is acceptible. * diff --git a/stanford_capx.info b/stanford_capx.info index 3ce9d958..8e2383fe 100644 --- a/stanford_capx.info +++ b/stanford_capx.info @@ -3,7 +3,8 @@ description = Provides the ability to import profiles into existing entity types core = 7.x package = Stanford CAPx project = stanford_capx -version = 7.x-1.3-php54 +version = 7.x-2.x-php54 + dependencies[] = entity dependencies[] = block diff --git a/stanford_capx.install b/stanford_capx.install index f9ed1bc2..4fef21b5 100644 --- a/stanford_capx.install +++ b/stanford_capx.install @@ -21,6 +21,8 @@ function stanford_capx_uninstall() { variable_del('stanford_capx_token'); variable_del("capx_last_orgs_sync"); variable_del("capx_last_schema_sync"); + variable_del("stanford_capx_debug_always_force_etag"); + variable_del("stanford_capx_autotruncate_textfields"); } /** @@ -76,6 +78,12 @@ function stanford_capx_schema() { 'not null' => TRUE, 'default' => '', ), + 'guuid' => array( + 'description' => 'A guuid that is not the profile id. Used for multiple creation.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), 'sync' => array( 'description' => 'Does this profile needs to be synced?', 'type' => 'int', @@ -103,7 +111,7 @@ function stanford_capx_schema() { ), ); - // Configuration Entity + // Configuration Entity. $schema['capx_cfe'] = array( 'description' => 'The base table for configuration entities.', 'fields' => array( @@ -266,3 +274,22 @@ function stanford_capx_requirements($phase) { } return $requirements; } + +/** + * Add the guuid field to the capx_profiles table. + */ +function stanford_capx_update_7100() { + + $table = "capx_profiles"; + $field = "guuid"; + $spec = array( + 'description' => 'A guuid that is not the profile id. Used for multiple creation.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ); + + db_add_field($table, $field, $spec); + +} diff --git a/stanford_capx.module b/stanford_capx.module index 53ee0600..2dc87315 100644 --- a/stanford_capx.module +++ b/stanford_capx.module @@ -69,7 +69,19 @@ function stanford_capx_permission() { return array( 'administer capx' => array( 'title' => t('Administer CAPx'), - 'description' => t('Administer and Configure CAPx settings.'), + 'description' => t('Administer and configure CAPx settings.'), + ), + // 'capx administer importers' => array( + // 'title' => t('Administer CAPx Importers'), + // 'description' => t('Administer and configure CAPx importers.'), + // ), + // 'capx administer mappers' => array( + // 'title' => t('Administer CAPx Mappers'), + // 'description' => t('Administer and configure CAPx mappers.'), + // ), + 'capx advanced administrator' => array( + 'title' => t('Administer Advanced CAPx settings'), + 'description' => t('Administer and Ccnfigure CAPx advanced settings.'), ), ); } @@ -109,15 +121,6 @@ function stanford_capx_menu() { 'type' => MENU_NORMAL_ITEM, ); - // Connect page. - $items['admin/config/capx/connect'] = array( - 'title' => 'Connect', - 'page callback' => 'stanford_capx_admin_config_connect', - 'access arguments' => array('administer capx'), - 'type' => MENU_NORMAL_ITEM, - 'weight' => -10, - ); - // Settings Page. $items['admin/config/capx/settings'] = array( 'title' => 'Settings', diff --git a/stanford_capx.pages.inc b/stanford_capx.pages.inc index da96f8aa..6f0a14af 100644 --- a/stanford_capx.pages.inc +++ b/stanford_capx.pages.inc @@ -83,53 +83,36 @@ function stanford_capx_admin_config_settings() { drupal_set_message(t('The organization codes are not available. You should sync with the API now.'), 'warning', FALSE); } - // Organizations - // --------------------------------------------------------------------------- + $form = drupal_get_form("stanford_capx_admin_config_settings_get_all_the_forms_form"); - $form = drupal_get_form("stanford_capx_config_settings_data_sync_form"); - $output['content']["orgsform"] = $form; - - // Settings form - // --------------------------------------------------------------------------- - - $form = drupal_get_form('stanford_capx_config_settings_form'); - $output["content"]["settingsform"] = $form; + $output['content']['submitbuttons'] = $form; return $output; } /** - * [stanford_capx_config_settings_data_sync_form description] + * Returns all the forms that make up the settings page. * @param [type] $form [description] * @param [type] $form_state [description] * @return [type] [description] */ -function stanford_capx_config_settings_data_sync_form($form, $form_state) { - $form['orggroup'] = array( - '#type' => "fieldset", - '#title' => t("Organizations & Schema"), - '#description' => t("The CAPx module needs some information from the CAP API in order to function properly. Below are actions that require communication with the CAP API server and you must be connected before you can run these tasks."), - '#collapsible' => TRUE, - '#collapsed' => FALSE, - ); +function stanford_capx_admin_config_settings_get_all_the_forms_form($form, &$form_state) { + + // $form = system_settings_form($form); + $form = stanford_capx_config_auth_settings_form($form, $form_state); - // Organizations. - $form['orggroup']['orgcodeinfo']['#markup'] = "

Organizations

"; - $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . t("Organization codes come from the CAP API and are saved to a taxonomy. This taxonomy powers the organization code autocomplete in the import section.") . "

"; - $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . t("Last sync: @date", array("@date" => variable_get('capx_last_orgs_sync', 'never'))) . "

"; - $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . t("View all organization codes: !link", array("!link" => l(t('Organization taxonomy'), 'admin/structure/taxonomy/capx_organizations'))) . "

"; - $form['orggroup']['orgcodeinfo']['#markup'] .= "

" . l(t('Get organization data'), 'admin/config/capx/organizations/sync', array("attributes" => array('class' => array('btn button')), 'query' => array('destination' => current_path()))) . "

"; - - // Schema. - $form['orggroup']['schemainfo']['#markup'] = "

Schema Information

"; - $form['orggroup']['schemainfo']['#markup'] .= "

" . t("The CAP API provides a schema of the information available. The CAPx module needs this in order to power the data browser that is found on a mapping page.") . "

"; - $form['orggroup']['schemainfo']['#markup'] .= "

" . t("Last sync: @date", array("@date" => variable_get('capx_last_schema_sync', 'never'))) . "

"; - $form['orggroup']['submit'] = array( + $connection = CAPxConnection::testConnection(); + if ($connection->status) { + $form = stanford_capx_config_settings_form($form, $form_state); + $form = stanford_capx_config_settings_data_sync_form($form, $form_state); + } + + + $form['actions']['submit'] = array( + '#value' => t("Save settings"), '#type' => 'submit', - '#value' => 'Get schema information', - '#op' => 'submit', ); - $form['#submit'][] = "stanford_capx_schema_refresh"; + return $form; } @@ -167,7 +150,7 @@ function stanford_capx_admin_config_help() { $content = "

" . t("Getting started") . "

"; $content .= "

" . t("Importing content from CAP can be completed in 3 steps:") . "

"; $content .= "
  1. "; - $content .= t("!connect - Connect to the CAP API using your authentication credentials.", array("!connect" => l(t("Connect"), "admin/config/capx/connect"))); + $content .= t("!connect - Connect to the CAP API using your authentication credentials.", array("!connect" => l(t("Settings"), "admin/config/capx/settings"))); $content .= "
  2. "; $content .= t("!mapping - Create a mapping that links CAP fields to your own fields.", array("!mapping" => l(t("Mapping"), "admin/config/capx/mapper"))); $content .= "
  3. "; diff --git a/stanford_capx.theme.inc b/stanford_capx.theme.inc index 3c99cd0e..c21eeb18 100644 --- a/stanford_capx.theme.inc +++ b/stanford_capx.theme.inc @@ -53,7 +53,6 @@ function theme_stanford_capx_columns_to_the_right($elements) { $req = "" . t("TRUE") . ""; $element['#children'] = preg_replace("/\<\/label>/", " *$0", $element['#children']); } - $info .= "

    Required: " . $req . "

    "; // Set up the rows even though there will only be one. $rows = array();