array( 'type' => 'int', 'not null' => TRUE, 'unsigned' => TRUE, ), ); } /** * The webform of the destination. * * In some cases this may be a class with only type and webform properties. * This is useful when you're doing a migration that creates submissions for * multiple webforms of the same type. Of course when you don't have * individual webforms you can't map fields to components. * * @var object */ 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 $node_or_type * A node object or string that indicates the node type that's has been * enabled for webform use. * @param $options * Array of options passed to MigrateDestinationEntity's constructor. */ public function __construct($node_or_type, array $options = array()) { if (is_string($node_or_type)) { // If they specified a type build a little, mock node. $this->node = (object) array( 'type' => $node_or_type, 'webform' => array(), ); } else if (is_object($node_or_type) && !empty($node_or_type->type)) { $this->node = $node_or_type; } // Make sure the node type is on webform's list. $types = webform_entity_node_types(); if (!in_array($this->node->type, $types)) { throw new Exception(t("You need to provide either a node object or string node type that is configured to accept webform submissions. We found %type wanting.", array('%type' => $this->node->type))); } parent::__construct('webform_submission_entity', $this->node->type, $options); // 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(); if (isset($this->node->webform['components'])) { 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() { if (empty($this->node->nid)) { return t('Submissions to the %type webforms', array( '!link' => url('admin/config/content/webform/entities/' . strtr($this->node->type, array('_' => '-')) . '/fields'), '%type' => node_type_get_name($this->node->type), )); } return t('Submissions to 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. $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.'), ); // The nid is omitted when we can load it from from $this->node. if (empty($this->node->nid)) { $fields['nid'] = t('The webform node id.'); } // Create a field for each component on the webform. if (isset($this->node->webform['components'])) { 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); $fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle); return $fields; } /** * 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->bundle = $this->bundle; if (empty($this->node->nid)) { $entity->data = array(); } else { // Hard code the node id so it doesn't need to be set via mapping. $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') { module_load_include('inc', 'webform', 'components/select.inc'); $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); // We've done what we can without a webform but now have to have one, if // it hasn't been set by now bail. $webform = empty($this->node->nid) ? node_load($entity->nid) : $this->node; if (empty($webform)) { // Cannot create a submission with out a webform node id. return FALSE; } migrate_instrument_start('webform_submission_update/insert'); // Determine if it's an insert or update. if (empty($entity->sid)) { if (empty($entity->data)) { $entity->data[] = array( array( 'value' => '', ), ); } $updating = FALSE; $sid = webform_submission_insert($webform, $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($webform, $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__); $this->prepareRollback($sids); foreach (webform_get_submissions(array('sid' => $sids)) as $submission) { // Gotta have a webform. $webform = empty($this->node->nid) ? node_load($submission->nid) : $this->node; webform_submission_delete($webform, $submission); } $this->completeRollback($sids); migrate_instrument_stop(__METHOD__); } }