monster_menus-9.0.x-dev/src/ImportExport.php

src/ImportExport.php
<?php
namespace Drupal\monster_menus;

use Drupal\content_sync\ContentSyncManager;
use Drupal\content_sync\ContentSyncManagerInterface;
use Drupal\content_sync\Importer\ContentImporter;
use Drupal\Core\Database\Database;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\monster_menus\GetTreeIterator\MMExportIter;
use Drupal\monster_menus\MMCreatePath\MMCreatePath;
use Drupal\monster_menus\MMCreatePath\MMCreatePathCat;
use Drupal\monster_menus\MMCreatePath\MMCreatePathGroup;
use Drupal\node\NodeInterface;
use Symfony\Component\Yaml\Parser;

/**
 * Functions to import/export a section of the MM tree.
 */

class ImportExport {

  final public const MM_IMPORT_ADD     = 'add';
  final public const MM_IMPORT_UPDATE  = 'update';
  final public const MM_IMPORT_DELETE  = 'delete';
  final public const MM_IMPORT_VERSION = '2.0';

  /**
   * Export a section of the MM tree.
   *
   * @param int $mmtid
   *   Starting location.
   * @param bool $include_nodes
   *   If TRUE, include node contents.
   *
   * @return string
   *   PHP source code for the exported data.
   * @throws MMImportExportException
   *   Any error condition.
   */
  public static function export($mmtid, $include_nodes = FALSE) {
    if ($include_nodes && !mm_module_exists('content_sync')) {
      throw new MMImportExportException('The content_sync module is required to export nodes.');
    }

    $params = [
      Constants::MM_GET_TREE_RETURN_BLOCK => TRUE,
      Constants::MM_GET_TREE_RETURN_FLAGS => TRUE,
      Constants::MM_GET_TREE_RETURN_NODE_COUNT => TRUE,
      Constants::MM_GET_TREE_RETURN_PERMS => TRUE,
      Constants::MM_GET_TREE_ITERATOR => new MMExportIter($include_nodes),
    ];
    mm_content_get_tree($mmtid, $params);

    return $params[Constants::MM_GET_TREE_ITERATOR]->dump();
  }

  /**
   * Import a section of the MM tree.
   *
   * @param string $code
   *   The code block, from mm_export().
   * @param int $start_mmtid
   *   The MM Tree ID of the location at which to import. The imported section
   *   is added (or updated) as a child of this location.
   * @param string $mode
   *   One of the constants:
   *   - MM_IMPORT_ADD: Add-only: Don't change existing nodes or pages, just add
   *       anything new.
   *   - MM_IMPORT_UPDATE: Update: Overwrite existing nodes and pages, if
   *       different; does not modify any pre-existing nodes/pages not part of
   *       the import.
   *   - MM_IMPORT_DELETE: Delete first: Move any existing nodes and pages to a
   *       recycle bin before importing.
   * @param bool $test
   *   If TRUE, go through the motions, but do not make any changes.
   * @param bool $include_nodes
   *   If TRUE, insert or update any nodes contained in the $code.
   * @param array []|string &$stats
   *   (optional) Array with which to populate statistics:
   *   - nodes:
   *     An array indexed by nid, containing sub-arrays with the elements
   *     "message" and "vars", which describe the nodes that were acted upon.
   *   - pages:
   *     An array indexed by mmtid, containing an array of sub-arrays each with
   *     the elements "message" and "vars", which describe the pages that were
   *     acted upon.
   *   - groups:
   *     An array indexed by mmtid, containing an array of sub-arrays each with
   *     the elements "message" and "vars", which describe the groups that were
   *     acted upon.
   *   - errors:
   *     An array containing sub-arrays with the elements "message" and "vars",
   *     which describe any errors that occurred.
   *   A count of the number of pages/nodes acted upon can be derived using the
   *   count() function.
   *
   * @throws MMImportExportException
   *   Any error condition.
   */
  public static function import($code, $start_mmtid, $mode = self::MM_IMPORT_ADD, $test = FALSE, $include_nodes = FALSE, &$stats = 'undef') {
    $parser = new Parser();
    try {
      $parsed = $parser->parse($code);
    }
    catch (\Exception $e) {
      throw new MMImportExportException($e->getMessage());
    }
    $code = '';   // Save memory.
    if (!isset($parsed['version']) ||
      !isset($parsed['nodes']) || !is_array($parsed['nodes']) ||
      !isset($parsed['page_nodes']) || !is_array($parsed['page_nodes']) ||
      !isset($parsed['pool']) || !is_array($parsed['pool']) ||
      !isset($parsed['pages']) || !is_array($parsed['pages'])) {
      throw new MMImportExportException('mm_import: The imported data does not include all necessary sections.');
    }
    if (version_compare($parsed['version'], (float) self::MM_IMPORT_VERSION, '!=')) {
      throw new MMImportExportException('mm_import: Incompatible version.');
    }
    if ($include_nodes && !mm_module_exists('content_sync')) {
      throw new MMImportExportException('mm_import: The content_sync module is required to import nodes.');
    }
    if ($mode == self::MM_IMPORT_DELETE && $test) {
      throw new MMImportExportException('mm_import: The test option cannot be used with "delete first" mode.');
    }

    // Import all pages in the pool into the correct object.
    $groups_root = new MMCreatePathGroup(mm_content_get(mm_content_groups_mmtid()));
    foreach ($parsed['pool'] as $mmtid => $page) {
      $parsed['pool'][$mmtid] = $page['type'] == 'group' ? new MMCreatePathGroup($page) : new MMCreatePathCat($page);
    }
    // Prepend the correct groups tree root to any permission groups.
    foreach ($parsed['pool'] as $page) {
      foreach ([Constants::MM_PERMS_WRITE, Constants::MM_PERMS_SUB, Constants::MM_PERMS_APPLY, Constants::MM_PERMS_READ] as $m) {
        if (isset($page->perms[$m]['groups']) && is_array($page->perms[$m]['groups'])) {
          foreach ($page->perms[$m]['groups'] as &$g) {
            if (is_array($g)) {
              foreach ($g as $key => $gid) {
                $g[$key] = &$parsed['pool'][$gid];
              }
              array_unshift($g, $groups_root);
            }
          }
        }
      }
    }

    /** @var MMCreatePath $mm_create_path */
    $mm_create_path = \Drupal::service('monster_menus.mm_create_path');
    $mm_create_path->setStats($stats);

    $add_only = $mode == self::MM_IMPORT_ADD;
    $is_first = TRUE;
    foreach ($parsed['pages'] as $elems) {
      foreach ($elems as $i => $elem) {
        $elems[$i] = &$parsed['pool'][$elem];
      }
      if ($is_first) {
        $start = new MMCreatePathCat(mm_content_get($start_mmtid));
        if ($mode == self::MM_IMPORT_DELETE) {
          $existing = mm_content_get(['parent' => $start_mmtid, 'alias' => $elems[0]->alias]);
          if ($existing) {
            mm_content_move_to_bin($existing[0]->mmtid);
            mm_content_update_sort_queue();
          }
        }
        array_unshift($elems, $start);
        $is_first = FALSE;
      }
      $mm_create_path->createPath($elems, $test, $add_only);
    }

    if ($include_nodes && $parsed['page_nodes']) {
      if (is_array($stats) && !empty($stats['errors'])) {
        _mm_report_error('Nodes were not imported, due to errors when processing pages/groups.', [], $stats);
      }
      else {
        /** @var ContentSyncManagerInterface $cs_manager */
        $cs_manager = \Drupal::service('content_sync.manager');
        $entity_type_manager = $cs_manager->getEntityTypeManager();
        $cs_context = [];
        $changed_uuids = [];
        $created_deps = [];

        if (!empty($parsed['dependencies'])) {
          foreach ($parsed['dependencies'] as $key => $depend) {
            [$type, $bundle, $uuid] = explode(ContentSyncManager::DELIMITER, $key);
            if ($changed_uuids) {
              // Change the UUIDs of any referenced entities.
              array_walk_recursive($depend, function(&$val, $key) use ($changed_uuids) {
                if ($key == 'target_uuid' && isset($changed_uuids[$val])) {
                  $val = $changed_uuids[$val];
                }
              });
            }

            if ($entities = $entity_type_manager->getStorage($type)->loadByProperties(['uuid' => $uuid])) {
              // Entity already exists. If updating, do nothing.
              if ($mode != self::MM_IMPORT_UPDATE) {
                foreach ($entities as $entity) {
                  $clone = $entity->createDuplicate();
                  if ($test) {
                    // When testing, track entities that would have been created.
                    $created_deps[$clone->uuid()] = TRUE;
                  }
                  else {
                    $clone->save();
                  }
                  $changed_uuids[$uuid] = $clone->uuid();
                }
              }
            }
            else {
              // Create new entity.
              $entity_type = $entity_type_manager->getDefinition($type);
              $depend += ['type' => $bundle];
              try {
                $entity = $cs_manager->getSerializer()->denormalize($depend, $entity_type->getClass(), 'yaml', $cs_context);
              }
              catch (\Exception $e) {
                $stats['errors'][] = ['message' => 'Could not create entity @id. Error: :error', 'vars' => ['@id' => $key, ':error' => $e->getMessage()]];
                continue;
              }

              if ($test) {
                // When testing, track entities that would have been created.
                $created_deps[$uuid] = TRUE;
              }
              else {
                $entity->save();
              }
            }
          }
        }

        $entity_type = $entity_type_manager->getDefinition('node');
        foreach ($parsed['page_nodes'] as $mmtid => $nodes) {
          $new_mmtid = $parsed['pool'][$mmtid]->mmtid;
          foreach ($nodes as $nid) {
            $node = $parsed['nodes'][$nid];
            if (is_array($node)) {
              if ($changed_uuids) {
                array_walk_recursive($node, function(&$val, $key) use ($changed_uuids) {
                  if ($key == 'target_uuid' && isset($changed_uuids[$val])) {
                    $val = $changed_uuids[$val];
                  }
                });
              }

              // If testing, we need to remove any references to entities that
              // would have been created so that the denormalize step won't fail
              // when they don't exist.
              if ($created_deps) {
                array_walk_recursive($node, function(&$val, $key) use ($created_deps) {
                  if ($key == 'target_uuid' && isset($created_deps[$val])) {
                    $val = NULL;
                  }
                });
              }

              try {
                $node = $parsed['nodes'][$nid] = $cs_manager->getSerializer()->denormalize($node, $entity_type->getClass(), 'yaml', $cs_context);
              }
              catch (\Exception $e) {
                $stats['errors'][] = ['message' => 'Could not create node @id. Error: :error', 'vars' => ['@id' => $nid, ':error' => $e->getMessage()]];
                unset($parsed['nodes'][$nid]);
                continue;
              }
              // Create any necessary groups.
              $new_groups_w = [];
              foreach ($node->groups_w as $g) {
                $path = [$groups_root];
                foreach ($g as $gid) {
                  $path[] = &$parsed['pool'][$gid];
                }
                $mm_create_path->createPath($path, $test, $add_only);
                $new_groups_w[end($path)->mmtid] = '';
              }
              $node->groups_w = $new_groups_w;

              $node->mm_catlist = $node->_mm_import_catlist = [];
            }
            $node->mm_catlist[$new_mmtid] = '';         // Used by MM
            $node->_mm_import_catlist[] = $new_mmtid;   // Internal use
          }
        }

        if (is_array($stats)) {
          $stats['nodes'] = [];
        }
        /** @var ContentImporter $content_importer */
        $content_importer = \Drupal::service('content_sync.importer');
        foreach ($parsed['nodes'] as $nid => $node) {
          /** @var NodeInterface $imported_node */
          $imported_node = $content_importer->prepareEntity($node);
          $imported_node->__set('mm_catlist', $node->mm_catlist);
          $imported_node->__set('mm_import_test', $test);

          $validations = $imported_node->validate();
          foreach ($validations as $i => $validation) {
            // Skip spurious "This value should not be null" errors.
            if ($validation->getCode() != 'ad32d13f-c3d4-423b-909a-857b961eb720') {
              $validation_message = $validation->getMessage();
              $stats['errors'][] = ['message' => $validation_message->getUntranslatedString(), 'vars' => $validation_message->getArguments()];
            }
          }

          if ($mode == self::MM_IMPORT_DELETE) {
            $is_new = TRUE;
            $imported_node->__set('is_new', $is_new);
            $imported_node->__set('uuid', \Drupal::service('uuid')->generate());
          }
          else {
            $exists = Database::getConnection()->query('SELECT n2.nid FROM {mm_node2tree} n2 INNER JOIN {node} n ON n.nid = n2.nid WHERE n2.mmtid IN(:mmtid[]) AND n.type = :type AND (n.uuid = :uuid OR n.nid = :nid) ORDER BY n.uuid = :uuid DESC LIMIT 1', [
              ':mmtid[]' => $node->_mm_import_catlist,
              ':type' => $node->getType(),
              ':uuid' => $node->uuid(),
              ':nid' => $nid,
            ])->fetchField();

            if ($add_only && $exists) {
              // Don't save or update.
              continue;
            }

            if ($exists) {
              // Update with a new revision.
              $imported_node->enforceIsNew($is_new = FALSE);
              $imported_node->__set('nid', $exists);
              $imported_node->__set('uuid', $node->uuid());
              $imported_node->setNewRevision();
            }
            else {
              $is_new = TRUE;
              $imported_node = $imported_node->createDuplicate();
            }
          }

          if (!$test) {
            $imported_node->save();
          }
          if (is_array($stats)) {
            if ($test) {
              $msg = $is_new ? 'Would have created @title' : 'Would have updated @link';
            }
            else {
              $msg = $is_new ? 'Created @link' : 'Updated @link';
            }
            $title = mm_ui_fix_node_title($imported_node->label());
            $stats['nodes'][] = [
              'message' => $msg,
              'vars' => ['@link' => $imported_node->id() ? Link::fromTextAndUrl($title, Url::fromRoute('entity.node.canonical', ['node' => $imported_node->id()]))->toString() : $title, '@title' => $title],
            ];
          }
        }
      }
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc