From 52fde74213a62a919de0d52ad8a4443e23f8f536 Mon Sep 17 00:00:00 2001 From: andrew morton Date: Sun, 25 Sep 2011 08:40:49 -0400 Subject: [PATCH] Issue #1119478 by drewish: Destination for Webform Submission. --- README.txt | 1 + migrate_extras.info | 1 + webform.inc | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 0 deletions(-) create mode 100644 webform.inc diff --git a/README.txt b/README.txt index 1bc8b4b..6d9e09f 100644 --- a/README.txt +++ b/README.txt @@ -23,6 +23,7 @@ Profile2 Rules User Relationships Voting API +Webform Compatibility ------------- diff --git a/migrate_extras.info b/migrate_extras.info index abfa7ef..2f22caa 100644 --- a/migrate_extras.info +++ b/migrate_extras.info @@ -15,5 +15,6 @@ files[] = og.inc files[] = privatemsg.inc files[] = votingapi.inc files[] = user_relationships.inc +files[] = webform.inc files[] = tests/date.test files[] = tests/pathauto.test diff --git a/webform.inc b/webform.inc new file mode 100644 index 0000000..abb5f6b --- /dev/null +++ b/webform.inc @@ -0,0 +1,292 @@ + 'value' as an + * argument to try to match the value) + * - textfield + * - textarea + * - time ('H:i:s') + * Untested/needs work: + * - grid + * - hidden + */ +class MigrateDestinationWebformSubmission extends MigrateDestination { + static public function getKeySchema() { + return array( + 'sid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + ), + ); + } + + /** + * The webform of the destination. + * + * @var string + */ + protected $node; + public function getWebform() { + return $this->node; + } + + /** + * An array mapping our custom names to component ids. + * + * @var array + */ + protected $component_cids; + + /** + * Constructs a destination for a given webform node. + * + * @param object $node + * A node object that's type has been enabled for webform use. + */ + public function __construct($node) { + parent::__construct(); + + if (empty($node)) { + throw new Exception(t("You must provide a webform node")); + } + // Make sure it's a webform node. + $types = webform_variable_get('webform_node_types'); + if (!in_array($node->type, $types)) { + throw new Exception(t("The node must be configured to accept webform submissions but %type was not", array('%type' => $node->type))); + } + $this->node = $node; + + // Webform expects the component values to be keyed by cid, so we need a + // hash to map prefixed field names to cid. + $this->component_cids = array(); + foreach ($this->node->webform['components'] as $component) { + $this->component_cids['data_' . $component['form_key']] = $component['cid']; + } + + // We use the functions in this file in import() but load it here so we + // only do it once. + module_load_include('inc', 'webform', 'includes/webform.submissions'); + } + + public function __toString() { + return t('Submission for the %title Webform', array( + '!link' => url('node/' . $this->node->nid), + '%title' => $this->node->title, + )); + } + + /** + * Returns a list of fields available to be mapped. + * + * @return array + * Keys: machine names of the fields (to be passed to addFieldMapping) + * Values: Human-friendly descriptions of the fields. + */ + public function fields() { + // Fields defined by the schema. nid is omitted since it should come from + // $this->node. + $fields = array( + 'sid' => t('The unique identifier for this submission.'), + 'uid' => t('The id of the user that completed this submission.'), + 'is_draft' => t('Is this a draft of the submission?'), + 'submitted' => t('Timestamp of when the form was submitted.'), + 'remote_addr' => t('The IP address of the user that submitted the form.'), + ); + + // Create a field for each component on the webform. + foreach ($this->node->webform['components'] as $component) { + // TODO: Seems like we should skip over page break components. + $fields['data_' . $component['form_key']] = t('@type: @name', array('@type' => $component['type'], '@name' => $component['name'])); + } + + // Then add in anything provided by handlers. + $fields += migrate_handler_invoke_all('WebformSubmission', 'fields', $this->node); + + return $fields; + } + + /** + * Give handlers a shot at modifying the object before saving it. + * + * @param $entity + * Webform submission object to build. Prefilled with any fields mapped in + * the Migration. + * @param $source_row + * Raw source data object - passed through to prepare handlers. + */ + public function prepare($entity, stdClass $source_row) { + $migration = Migration::currentMigration(); + $entity->migrate = array( + 'machineName' => $migration->getMachineName(), + ); + // Call any general object handlers. + migrate_handler_invoke_all('WebformSubmission', 'prepare', $entity, $source_row, $this->node); + + // Move the data from our custom keys back to webform's component ids. + $data = array(); + foreach ($this->component_cids as $field_name => $cid) { + if (isset($entity->$field_name)) { + $data[$cid] = $entity->$field_name; + } + unset($entity->$field_name); + } + $entity->data = webform_submission_data($this->node, $data); + + // Then call any prepare handler for this specific Migration. + if (method_exists($migration, 'prepare')) { + $migration->prepare($entity, $source_row, $this->node); + } + } + + /** + * Give handlers a shot at modifying the object (or taking additional action) + * after saving it. + * + * @param $entity + * Webform submission object to build. This is the complete object after + * saving. + * @param $source_row + * Raw source data object - passed through to complete handlers. + */ + public function complete($entity, stdClass $source_row) { + $migration = Migration::currentMigration(); + // Call any general object handlers. + migrate_handler_invoke_all('WebformSubmission', 'complete', $entity, $source_row, $this->node); + // Then call any complete handler for this specific Migration. + if (method_exists($migration, 'complete')) { + $migration->complete($entity, $source_row, $this->node); + } + } + + /** + * Import a record. + * + * @param $entity + * Webform submission object to build. This is the complete object after + * saving. + * @param $source_row + * Raw source data object - passed through to complete handlers. + */ + public function import(stdClass $entity, stdClass $row) { + // Updating previously-migrated content? + $migration = Migration::currentMigration(); + if (isset($row->migrate_map_destid1)) { + if (isset($entity->sid) && $entity->sid != $row->migrate_map_destid1) { + throw new MigrateException(t("Incoming sid !sid and map destination sid !destid1 don't match", + array('!sid' => $entity->sid, '!destid1' => $row->migrate_map_destid1))); + } + else { + $entity->sid = $row->migrate_map_destid1; + } + } + + $entity->nid = $this->node->nid; + + // Move the data from our custom keys back to webform's component ids. + $data = array(); + foreach ($this->component_cids as $field_name => $cid) { + if (isset($entity->$field_name)) { + // Move the arguments out and kill any extraneous wrapper arrays. + $value = $entity->$field_name; + $arguments = array(); + if (is_array($value) && isset($value['arguments'])) { + $arguments = (array) $value['arguments']; + unset($value['arguments']); + $value = count($value) ? reset($value) : $value; + } + // Avoid a warning if they passed in an empty array. + $arguments += array('source_type' => 'key'); + + // By default passed to select components are assumed to be the + // key. If the key should be looked up use the add a + // array('source_type' => 'value') argument to the field mapping. + $component = $this->node->webform['components'][$cid]; + if ($component['type'] == 'select' && $arguments['source_type'] == 'value') { + $options = _webform_select_options($component); + $id = array_search($value, $options); + $data[$cid] = ($id === FALSE) ? NULL : $id; + } + else { + $data[$cid] = $value; + } + unset($entity->$field_name); + } + } + $entity->data = webform_submission_data($this->node, $data); + + // Invoke migration prepare handlers + $this->prepare($entity, $row); + + migrate_instrument_start('webform_submission_update/insert'); + // Determine if it's an insert or update. + if (empty($entity->sid)) { + $updating = TRUE; + $sid = webform_submission_insert($this->node, $entity); + } + else { + // If the sid was specified but doesn't exist we'll need to stick an + // empty record in so webform's update has something to stick to. + $status = db_merge('webform_submissions') + ->key(array( + 'sid' => $entity->sid, + )) + ->insertFields(array( + 'sid' => $entity->sid, + 'nid' => $entity->nid, + 'submitted' => $entity->submitted, + 'remote_addr' => $entity->remote_addr, + 'is_draft' => $entity->is_draft, + 'bundle' => $entity->bundle, + )) + ->execute(); + // If db_merge() makes no changes $status is NULL so make a less + // elegant comparison. + $updating = MergeQuery::STATUS_INSERT !== $status; + $sid = webform_submission_update($this->node, $entity); + } + migrate_instrument_stop('webform_submission_update/insert'); + + if (isset($sid)) { + $entity->sid = $sid; + + if ($updating) { + $this->numUpdated++; + } + else { + $this->numCreated++; + } + $return = array($sid); + } + else { + $return = FALSE; + } + + // Invoke migration complete handlers + $this->complete($entity, $row); + + return $return; + } + + /** + * Delete a batch of submissions at once. + * + * @param $sids + * Array of submission IDs to be deleted. + */ + public function bulkRollback(array $sids) { + migrate_instrument_start(__METHOD__); + foreach (webform_get_submissions(array('sid' => $sids)) as $submission) { + webform_submission_delete($this->node, $submission); + } + migrate_instrument_stop(__METHOD__); + } +} \ No newline at end of file -- 1.7.0.4