Export and Import Drupal Content with Related Entities Using Content Sync

Learn how to export and import Drupal content, including nodes, images, paragraphs, blocks, and SEO metadata, using the Content Sync module.

1. Custom Module Definition

Create a custom module named my_custom_export_import:

my_custom_export_import.info.yml:
name: 'My Custom Export Import'
type: module
description: 'Custom module to export and import nodes with related entities including SEO metadata using Content Sync.'
core_version_requirement: ^8 || ^9
dependencies:
  - content_sync:content_sync
  - paragraph:paragraphs
  - media:media
  - block:block

2. Export Functionality

Add the export functionality to my_custom_export_import.module:

/**
 * Implements hook_content_sync_export().
 */
function my_custom_export_import_content_sync_export(&$data, $export_type) {
  if ($export_type == 'full') {
    // Call the custom export function.
    $data['all_entities'] = my_custom_export_all_entities();
  }
}

/**
 * Custom function to export nodes and their related entities.
 */
function my_custom_export_all_entities() {
  $export_data = [];
  // Export nodes with related entities.
  $export_data['nodes'] = my_custom_export_nodes();
  return $export_data;
}

/**
 * Custom function to export nodes and their related entities.
 */
function my_custom_export_nodes() {
  $nodes = [];
  $query = \Drupal::entityQuery('node');
  $node_ids = $query->execute();

  foreach ($node_ids as $node_id) {
    $node = \Drupal\node\Entity\Node::load($node_id);
    $node_data = $node->toArray();

    // Export related images.
    $node_data['images'] = my_custom_export_related_images($node);

    // Export related paragraphs.
    $node_data['paragraphs'] = my_custom_export_related_paragraphs($node);

    // Export related blocks (if applicable).
    $node_data['blocks'] = my_custom_export_related_blocks($node);

    // Export SEO fields.
    $node_data['seo_title'] = $node->get('field_seo_title')->value;
    $node_data['seo_description'] = $node->get('field_seo_description')->value;
    $node_data['seo_keywords'] = $node->get('field_seo_keywords')->value;

    $nodes[] = $node_data;
  }

  return $nodes;
}

/**
 * Custom function to export related images for a node.
 */
function my_custom_export_related_images($node) {
  $images = [];
  foreach ($node->getFields() as $field_name => $field) {
    if ($field->getFieldDefinition()->getType() == 'image') {
      foreach ($field->getValue() as $item) {
        $file = \Drupal\file\Entity\File::load($item['target_id']);
        $images[] = [
          'id' => $file->id(),
          'uri' => $file->getFileUri(),
        ];
      }
    }
  }
  return $images;
}

/**
 * Custom function to export related paragraphs for a node.
 */
function my_custom_export_related_paragraphs($node) {
  $paragraphs = [];
  foreach ($node->getFields() as $field_name => $field) {
    if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions') {
      foreach ($field->getValue() as $item) {
        $paragraph = \Drupal\paragraphs\Entity\Paragraph::load($item['target_id']);
        $paragraphs[] = $paragraph->toArray();
      }
    }
  }
  return $paragraphs;
}

/**
 * Custom function to export related blocks for a node (if applicable).
 */
function my_custom_export_related_blocks($node) {
  // Adjust according to how blocks are used in your site.
  $blocks = [];
  // Logic to export blocks if applicable.
  return $blocks;
}

3. Import Functionality

Add the import functionality to my_custom_export_import.module:

/**
 * Implements hook_content_sync_import().
 */
function my_custom_export_import_content_sync_import(&$data, $import_type) {
  if ($import_type == 'full') {
    // Call the custom import function.
    my_custom_import_all_entities($data['all_entities']);
  }
}

/**
 * Custom function to import all entities.
 */
function my_custom_import_all_entities($data) {
  // Import nodes and their related entities.
  if (isset($data['nodes'])) {
    my_custom_import_nodes($data['nodes']);
  }
}

/**
 * Function to import nodes and their related entities.
 */
function my_custom_import_nodes($nodes) {
  foreach ($nodes as $node_data) {
    // Handle images and paragraphs before creating the node to ensure references are correct.
    if (isset($node_data['images'])) {
      my_custom_import_related_images($node_data['images']);
    }

    if (isset($node_data['paragraphs'])) {
      my_custom_import_related_paragraphs($node_data['paragraphs']);
    }

    if (isset($node_data['blocks'])) {
      my_custom_import_related_blocks($node_data['blocks']);
    }

    // Import the node itself.
    // Ensure you handle the creation of the node with necessary checks and updates.
    $node = \Drupal\node\Entity\Node::create($node_data);

    // Import SEO fields.
    if (isset($node_data['seo_title'])) {
      $node->set('field_seo_title', $node_data['seo_title']);
    }
    if (isset($node_data['seo_description'])) {
      $node->set('field_seo_description', $node_data['seo_description']);
    }
    if (isset($node_data['seo_keywords'])) {
      $node->set('field_seo_keywords', $node_data['seo_keywords']);
    }

    $node->save();
  }
}

/**
 * Function to import related images.
 */
function my_custom_import_related_images($images) {
  foreach ($images as $image_data) {
    // Check if the file already exists to avoid duplicates.
    $existing_file = \Drupal\file\Entity\File::load($image_data['id']);
    if (!$existing_file) {
      $file = \Drupal\file\Entity\File::create([
        'uri' => $image_data['uri'],
        'status' => 1,
      ]);
      $file->save();
    }
  }
}

/**
 * Function to import related paragraphs.
 */
function my_custom_import_related_paragraphs($paragraphs) {
  foreach ($paragraphs as $paragraph_data) {
    // Check if the paragraph already exists to avoid duplicates.
    $existing_paragraph = \Drupal\paragraphs\Entity\Paragraph::load($paragraph_data['id']);
    if (!$existing_paragraph) {
      $paragraph = \Drupal\paragraphs\Entity\Paragraph::create($paragraph_data);
      $paragraph->save();
    }
  }
}

/**
 * Function to import related blocks (if applicable).
 */
function my_custom_import_related_blocks($blocks) {
  // Adjust according to your block usage.
  foreach ($blocks as $block_data) {
    // Logic to import blocks, if applicable.
  }
}

4. User Interface for Export and Import

Create a controller to handle the UI and form submissions for export and import:

src/Controller/ExportImportController.php:

namespace Drupal\my_custom_export_import\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

class ExportImportController extends ControllerBase {

  /**
   * Export content.
   */
  public function exportContent() {
    // Trigger the export.
    $data = [];
    my_custom_export_import_content_sync_export($data, 'full');
    
    // Convert to JSON and download.
    $json_data = json_encode($data['all_entities'], JSON_PRETTY_PRINT);
    $response = new Response($json_data);
    $response->headers->set('Content-Type', 'application/json');
    $response->headers->set('Content-Disposition', 'attachment; filename="exported_content.json"');
    return $response;
  }

  /**
   * Import content.
   */
  public function importContent(Request $request) {
    // Handle file upload.
    $file = $request->files->get('import_file');
    if ($file && $file->isValid()) {
      $data = json_decode(file_get_contents($file->getPathname()), TRUE);
      if (isset($data['all_entities'])) {
        my_custom_import_all_entities($data['all_entities']);
        return new JsonResponse(['status' => 'Import successful!']);
      }
      return new JsonResponse(['status' => 'Invalid data format.'], 400);
    }
    return new JsonResponse(['status' => 'No file uploaded.'], 400);
  }
}

5. Define Routes

Define routes in my_custom_export_import.routing.yml:

my_custom_export_import.export:
  path: '/admin/export-content'
  defaults:
    _controller: '\Drupal\my_custom_export_import\Controller\ExportImportController::exportContent'
    _title: 'Export Content'
  requirements:
    _permission: 'administer site configuration'

my_custom_export_import.import:
  path: '/admin/import-content'
  defaults:
    _controller: '\Drupal\my_custom_export_import\Controller\ExportImportController::importContent'
    _title: 'Import Content'
  requirements:
    _permission: 'administer site configuration'
  methods: [POST]

6. Add Export and Import Form

Create a simple form to handle export and import in your theme or as a block:

templates/export_import_form.html.twig:
<div class="export-import-form">
  <h2>Export Content</h2>
  <form action="/admin/export-content" method="get">
    <button type="submit">Export</button>
  </form>

  <h2>Import Content</h2>
  <form action="/admin/import-content" method="post" enctype="multipart/form-data">
    <input type="file" name="import_file" required />
    <button type="submit">Import</button>
  </form>
</div>

References:

Published By: Krishanu Jadiya
Updated at: 2024-07-25 15:56:14

Card Image

How to Set Up a Local SSL Certificate on Apache: Step-by-Step Guide

Learn how to set up a local SSL certificate on Apache with this comprehensive step-by-step guide. Secure your local development environment with HTTPS.

Card Image

Latest Features of Coding Technology

Explore the latest features and advancements in coding technology, including new programming languages, frameworks, DevOps tools, AI integration, and more.

Card Image

Understanding Laravel Mix Webpack Configuration: Step-by-Step Guide

Step-by-step explanation of a Laravel Mix Webpack configuration file, including asset management for JavaScript, CSS, and Vue.js support.

Card Image

How Emojis Can Enhance Your Git Commits | Gitmoji Guide

Discover how to enhance your Git commits with emojis. Learn about the best practices for creating informative and visually distinctive commit messages.