content_sync-8.x-2.x-dev/src/Form/ContentExportTrait.php
src/Form/ContentExportTrait.php
<?php namespace Drupal\content_sync\Form; use Drupal\Core\Archiver\ArchiveTar; use Drupal\content_sync\Content\ContentDatabaseStorage; use Drupal\Core\Entity\ContentEntityType; use Drupal\Core\Serialization\Yaml; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Url; /** * Defines the content export form. */ trait ContentExportTrait { /** * @var ArchiveTar */ protected $archiver; /** * @param $entities * * @param $serializer_context * export_type: * Tar -> YML to Tar file * Snapshot -> YML to content_sync table. * Directory -> YML to content_sync_directory_entities. * * content_sync_directory_entities: * path for the content sync directory. * * content_sync_directory_files: * path to store media/files. * * content_sync_file_base_64: * Include file as a data in the YAML. * * @return array */ public function generateExportBatch($entities, $serializer_context = []) { $serializer_context['content_sync_directory_entities'] = content_sync_get_content_directory('sync') . "/entities"; if (isset($serializer_context['include_files'])) { if ($serializer_context['include_files'] == 'folder') { $serializer_context['content_sync_directory_files'] = content_sync_get_content_directory('sync') . "/files"; } if ($serializer_context['include_files'] == 'base64') { $serializer_context['content_sync_file_base_64'] = TRUE; } unset($serializer_context['include_files']); } //Set batch operations by entity type/bundle $operations = []; $operations[] = [[$this, 'generateSiteUUIDFile'], [0 => $serializer_context]]; foreach ($entities as $entity) { $operations[] = [[$this, 'processContentExportFiles'], [[$entity], $serializer_context]]; } $title = 'Exporting content'; if (isset($serializer_context['export_type']) && $serializer_context['export_type'] == 'snapshot') { $title = 'Building Snapshot'; } // Set Batch $batch = [ 'operations' => $operations, 'title' => $this->t($title), 'init_message' => $this->t('Starting content export.'), 'progress_message' => $this->t('Completed @current step of @total.'), 'error_message' => $this->t('Content export has encountered an error.'), ]; if (isset($serializer_context['export_type']) && $serializer_context['export_type'] == 'tar') { $batch['finished'] = [$this,'finishContentExportBatch']; } return $batch; } /** * Processes the content archive export batch * * @param $files * The batch content to persist. * @param $serializer_context * @param array $context * The batch context. */ public function processContentExportFiles($entities, $serializer_context, &$context) { //Initialize Batch if (empty($context['sandbox'])) { $context['sandbox']['progress'] = 0; $context['sandbox']['current_number'] = 0; $context['sandbox']['queue'] = $entities; $context['sandbox']['max'] = count($entities); } $item = array_pop($context['sandbox']['queue']); // Get submitted values $entity_type = $item['entity_type']; //Validate that it is a Content Entity $instances = $this->getEntityTypeManager()->getDefinitions(); if (!(isset($instances[$entity_type]) && $instances[$entity_type] instanceof ContentEntityType)) { $context['results']['errors'][] = $this->t('Entity type @entity_type does not exist or it is not a content instance.', ['@entity_type' => $entity_type]); } else { if (isset($item['entity_uuid'])){ $entity_id = $item['entity_uuid']; $entity = $this->getEntityTypeManager()->getStorage($entity_type) ->loadByProperties(['uuid' => $entity_id]); $entity = array_shift($entity); }else{ $entity_id = $item['entity_id']; $entity = $this->getEntityTypeManager()->getStorage($entity_type) ->load($entity_id); } //Make sure the entity exist for import if(empty($entity)){ $context['results']['errors'][] = $this->t('Entity does not exist:') . $entity_type . "(".$entity_id.")"; }else{ // Create the name $bundle = $entity->bundle(); $uuid = $entity->uuid(); $name = $entity_type . "." . $bundle . "." . $uuid; if (!isset($context['exported'][$name])) { // Generate the YAML file. $exported_entity = $this->getContentExporter() ->exportEntity($entity, $serializer_context); if (isset($serializer_context['export_type'])){ if ($serializer_context['export_type'] == 'snapshot') { //Save to cs_db_snapshot table. $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot'); $activeStorage->cs_write($name, Yaml::decode($exported_entity), $entity_type.'.'.$bundle); }else{ // Compate the YAML from the snapshot. // If for some reason is not on our snapshoot then add it. // Or if the new YAML is different then update it. $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot'); $exported_entity_snapshoot = $activeStorage->cs_read($name); if (!$exported_entity_snapshoot || Yaml::encode($exported_entity_snapshoot) !== $exported_entity ){ //Save to cs_db_snapshot table. $activeStorage->cs_write($name, Yaml::decode($exported_entity), $entity_type.'.'.$bundle); } if ($serializer_context['export_type'] == 'tar') { // YAML in Archive . $this->getArchiver()->addString("entities/$entity_type/$bundle/$name.yml", $exported_entity); // Include Files to the archiver. if (method_exists($entity, 'getFileUri') && !empty($serializer_context['content_sync_directory_files']) ) { $uri = $entity->getFileUri(); $scheme = \Drupal::service('stream_wrapper_manager')->getScheme($uri); $destination = "{$serializer_context['content_sync_directory_files']}/{$scheme}/"; $destination = str_replace($scheme . '://', $destination, $uri); $strip_path = str_replace('/files' , '', $serializer_context['content_sync_directory_files'] ); // Exception for when the file doesn't exist // TODO: add a notice/log about it. if (file_exists($destination)) { $this->getArchiver()->addModify([$destination], '', $strip_path); } } } if( $serializer_context['export_type'] == 'folder') { // YAML in a directory. $path = $serializer_context['content_sync_directory_entities']."/$entity_type/$bundle"; $destination = $path . "/$name.yml"; \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY); $file = \Drupal::service('file_system')->saveData($exported_entity, $destination, FileSystemInterface::EXISTS_REPLACE); } // Invalidate the CS Cache of the entity. $cache = \Drupal::cache('content')->invalidate($entity_type.".".$bundle.":".$name); if (isset($serializer_context['include_dependencies']) && $serializer_context['include_dependencies']) { //Include Dependencies $context['exported'][$name] = $name; if (!isset($context['sandbox']['dependencies'][$name])) { $exported_entity = Yaml::decode($exported_entity); $queue = $this->contentSyncManager->generateExportQueue( [$name => $exported_entity], $context['exported']); $context['sandbox']['dependencies'] = array_merge((array) $context['sandbox']['dependencies'],$queue); unset($queue[$name]); if(!empty($queue)){ // Update the batch operations number $context['sandbox']['max'] = $context['sandbox']['max'] + count($queue); $context['sandbox']['queue'] = $queue; } } } } } } } } $context['message'] = $name; $context['results'][] = $name; $context['sandbox']['progress']++; $context['finished'] = $context['sandbox']['max'] > 0 && $context['sandbox']['progress'] < $context['sandbox']['max'] ? $context['sandbox']['progress'] / $context['sandbox']['max'] : 1; } /** * Generate UUID YAML file * To use for site UUID validation. * * @param $data * The batch content to persist. * @param array $context * The batch context. */ public function generateSiteUUIDFile($serializer_context, &$context) { //Include Site UUID to YML file $site_config = \Drupal::config('system.site'); $site_uuid_source = $site_config->get('uuid'); $entity['site_uuid'] = $site_uuid_source; // Set the name $name = "site.uuid"; if (isset($serializer_context['export_type'])){ if ($serializer_context['export_type'] == 'snapshot') { //Save to cs_db_snapshot table. $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot'); $activeStorage->write($name, $entity); }elseif( $serializer_context['export_type'] == 'tar') { // Add YAML to the archiver $this->getArchiver()->addString("entities/$name.yml", Yaml::encode($entity)); }elseif( $serializer_context['export_type'] == 'folder') { $path = $serializer_context['content_sync_directory_entities']; $destination = $path . "/$name.yml"; \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY); $file = \Drupal::service('file_system')->saveData(Yaml::encode($entity), $destination, FileSystemInterface::EXISTS_REPLACE); } } $context['message'] = $name; $context['results'][] = $name; $context['finished'] = 1; } /** * Finish batch. * * Provide information about the Content Batch results. */ public function finishContentExportBatch($success, $results, $operations) { if ($success) { if (isset($results['errors'])){ $errors = $results['errors']; unset($results['errors']); } $results = array_unique($results); // Log all the items processed foreach ($results as $key => $result) { if ($key != 'errors') { //drupal_set_message(t('Processed UUID @title.', array('@title' => $result))); $this->getExportLogger() ->info('Processed UUID @title.', [ '@title' => $result, 'link' => 'Export', ]); } } if (isset($errors) && !empty($errors)) { // Log the errors $errors = array_unique($errors); foreach ($errors as $error) { $this->getExportLogger()->error($error); } // Log the note that the content was exported with errors. \Drupal::messenger()->addWarning($this->t('The content was exported with errors. <a href=":content-overview">Logs</a>', [':content-overview' => Url::fromRoute('content.overview')->toString()])); $this->getExportLogger() ->warning('The content was exported with errors.', ['link' => 'Export']); } else { // Log the new created export link if applicable. \Drupal::messenger()->addStatus($this->t('The content was exported successfully. <a href=":export-download">Download tar file</a>', [':export-download' => Url::fromRoute('content.export_download')->toString()])); $this->getExportLogger() ->info('The content was exported successfully. <a href=":export-download">Download tar file</a>', [ ':export-download' => Url::fromRoute('content.export_download')->toString(), 'link' => 'Export', ]); } } else { // Log that there was an error. $message = $this->t('Finished with an error.<a href=":content-overview">Logs</a>', [':content-overview' => Url::fromRoute('content.overview')->toString()]); \Drupal::messenger()->addStatus($message); $this->getExportLogger() ->error('Finished with an error.', ['link' => 'Export']); } } protected function getArchiver() { if (!isset($this->archiver)) { $this->archiver = new ArchiveTar($this->getTempFile(), 'gz'); } return $this->archiver; } protected function getTempFile() { return \Drupal::service('file_system')->getTempDirectory() . '/content.tar.gz'; } /** * @return \Drupal\Core\Entity\EntityTypeManagerInterface */ abstract protected function getEntityTypeManager(); /** * @return \Drupal\content_sync\Exporter\ContentExporterInterface */ abstract protected function getContentExporter(); /** * @return \Psr\Log\LoggerInterface */ abstract protected function getExportLogger(); }