In Drupal 7, I can easily switch a node's content type by using Node convert. Node Convert, however, has not been ported to Drupal 8, nor does there appear to be much enthusiasm for the port.
If I have two content types with identical fields, how can I convert a node from a content type to another one, in Drupal 8? What is the code I should use for Drupal 8, which is equivalent to the following Drupal 7 code used by the Node convert module? (See node_convert_node_convert() in node_convert.util.inc.)
// $nid, $destination_node_type, $source_fields, $destination_fields, // $no_fields_flag, and $hook_options are the parameters passed to the function. $node = node_load($nid); if ($node == FALSE) { return FALSE; } // Change the node type in the DB db_update('node')->fields(array('type' => $destination_node_type))->condition('nid', $nid)->execute(); // If there are fields that can be converted if ($no_fields_flag == FALSE) { // Conversion process for each field $re_save_body_field = FALSE; // Use node revisions to extract all field revision in node_convert_field_convert $node_revisions = node_revision_list($node); foreach ($source_fields as $key => $field) { $replaced_body = node_convert_field_convert($node, $field, $destination_fields[$key], $destination_node_type, $node_revisions); if ($replaced_body == REPLACE_BODY) { $re_save_body_field = TRUE; } } // If something was appended to the body, or replaced the body, we update body field. if ($re_save_body_field == TRUE) { $field_body = field_info_fields(); $field_body = $field_body['body']; $field_ids = array($field_body['id'] => $field_body['id']); module_invoke($field_body['storage']['module'], 'field_storage_write', 'node', $node, FIELD_STORAGE_UPDATE, $field_ids); } } // Omissis. // Clear the node cache, so we have the latest information when saving the // node. $controller = entity_get_controller('node'); /* @var $controller DrupalEntityControllerInterface */ $controller->resetCache(array($node->nid)); cache_clear_all('field:node:' . $node->nid, 'cache_field');
node_convert_field_convert() contains the following code.
// &$node, $source_field, $destination_field, $destination_node_type, // and $node_revisions are the parameters passed to node_convert_field_convert(). $field_info_source = field_info_fields(); // Get source field information $field_info_source = $field_info_source[$source_field]; $db_info_source = $field_info_source['storage']; // Get DB specific source field information if ($destination_field == 'discard') { // Delete node info in the separate field table node_convert_invoke_field_storage_delete($field_info_source, $db_info_source, $node); return NULL; } $field_info_destination = array(); $db_info_destination = array(); if (!in_array($destination_field, array('discard', APPEND_TO_BODY, REPLACE_BODY))) { $field_info_destination = field_info_fields($destination_field); // Get destination field information $field_info_destination = $field_info_destination[$destination_field]; // Get destination field information $db_info_destination = $field_info_destination['storage']; // Get DB specific destination field information } // We save each field value from the DB for transfer. (this only applies to the current revision of the field) $source_values = field_get_items('node', $node, $source_field); if (count($node_revisions) > 1 && !in_array($destination_field, array(APPEND_TO_BODY, REPLACE_BODY))) { // Get all field revisions for current node $field_revision_values = array(); $field_revision_source_table = current(array_keys($db_info_source['details']['sql']['FIELD_LOAD_REVISION'])); $field_revision_destination_table = current(array_keys($db_info_destination['details']['sql']['FIELD_LOAD_REVISION'])); $source_columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); foreach ($field_info_source['columns'] as $column => $attributes) { $source_columns[] = _field_sql_storage_columnname($source_field, $column); } $revision_query = db_select($field_revision_source_table, 'r', array('fetch' => PDO::FETCH_ASSOC)) ->condition('entity_type', 'node') ->condition('bundle', $node->type) ->condition('entity_id', $node->nid) ->condition('revision_id', $node->vid, '<>') ->fields('r', $source_columns)->execute(); // Change the bundle to the destination type of the node foreach ($revision_query as $row) { $row['bundle'] = $destination_node_type; $field_revision_values[] = $row; } // Remove all field revisions for current field in DB node_convert_invoke_field_storage_delete($field_info_source, $db_info_source, $node); // Reinsert the field revisions in the destination field revision table $query = db_insert($field_revision_destination_table); $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); foreach ($field_info_destination['columns'] as $column => $attributes) { $columns[] = _field_sql_storage_columnname($destination_field, $column); } $query->fields($columns); foreach ($field_revision_values as $row) { $query->values(array_values($row)); } $query->execute(); } else { // After getting the source field values, we delete the values stored in the DB (this deletes values for all field revisions) node_convert_invoke_field_storage_delete($field_info_source, $db_info_source, $node); } // The source field value should be appended to the body or replaced. if ($destination_field == APPEND_TO_BODY || $destination_field == REPLACE_BODY) { static $node_body = ''; //static $node_teaser = ''; // We try to get the node body from a static variable, which means we did some body manipulations, otherwise load it. if (empty($node_body)) { $node_body_field = field_get_items('node', $node, 'body'); $node_body = $node_body_field[0]['value']; //$node_teaser = $node_body_field[0]['summary']; } // Double check we have values in the field. if (is_array($source_values)) { // Get the field value. $field_value = node_convert_format_field_value($node, $field_info_source, TRUE); if ($destination_field == APPEND_TO_BODY) { $node_body = $node_body . "\n" . $field_value; //$node_teaser = $node_teaser . "\n" . $field_value['value']; } elseif ($destination_field == REPLACE_BODY) { $node_body = $field_value; //$node_teaser = $field_value['value']; } $lang_code = field_language('node', $node, $source_field); $node->body[$lang_code][0]['value'] = $node_body; //$node->body[$lang_code][0]['summary'] = $node_teaser; } return REPLACE_BODY; } // We put each field value back into the DB // To do it we first get the id of the field, then we find its language code from the source value // We add $source_values into the node object, and invoke field_storage write $field_ids = array($field_info_destination['id'] => $field_info_destination['id']); $lang_code = field_language('node', $node, $source_field); // Make sure that we actually have values in the source field if ($source_values !== FALSE) { $node->{$destination_field}[$lang_code] = $source_values; } else { $node->{$destination_field} = array(); } // Give possibility to fields to pre-process their data // (e.g., Link module transforms attribute array into a serialized array before insertion) field_attach_presave('node', $node); // For some reason link_field_presave doesn't exist anymore, so we have to call it the processing function used inside manually. if ($field_info_destination['type'] == 'link_field') { $instances = field_info_instances('node', $destination_node_type); link_field_update('node', $node, $field_info_destination, $instances[$destination_field], $lang_code, $node->{$destination_field}[$lang_code]); }