content_packager-8.x-1.x-dev/src/BatchOperations.php
src/BatchOperations.php
<?php namespace Drupal\content_packager; use Drupal\views\Views; /** * Batch operations to apply to Views for content packaging. * * @package content_packager */ class BatchOperations { /** * Implements callback_batch_operation(). * * Prepares the destination defined in the module configuration. * * @param string $packageUri * The URI to package files into. * @param array|\ArrayAccess $context * The batch context array. */ public static function prepareDestination($packageUri, &$context) { if (!isset($context['results']['prepared'])) { $context['results']['prepared'] = FALSE; } /** @var \Drupal\Core\File\FileSystem $fileSystem */ $fileSystem = \Drupal::service('file_system'); // We really shouldn't be doing something destructive like a recursive // delete if we're encounting any problems. if (!\Drupal::service('stream_wrapper_manager')->isValidUri($packageUri) || !$fileSystem->getDestinationFilename($packageUri, $fileSystem::EXISTS_RENAME)) { $msg = t('Packager directory might not be configured correctly.'); \Drupal::messenger()->addError($msg); return; } if (!$fileSystem->deleteRecursive($packageUri)) { $msg = t('Packager directory could not be deleted.'); \Drupal::messenger()->addError($msg); } $errors = content_packager_prepare_directory($packageUri); foreach ($errors as $error) { \Drupal::messenger()->addError($error); } $context['results']['prepared'] = count($errors) === 0; } /** * Implements callback_batch_operation(). * * First renders a view and then stores its rendered output onto * the filesystem. * * @param string $view_id * A view to render. * @param string $display_id * The specific display ID to render on the view. * @param string $data_uri * A URI destination. * @param array|\ArrayAccess $context * The batch context array. */ public static function renderAndSaveViewOutput($view_id, $display_id, $data_uri, &$context) { if (!isset($context['results']['rendered'])) { $context['results']['rendered'] = FALSE; $context['results']['failed'] = []; $context['results']['completed_count'] = 0; } $view = Views::getView($view_id); $view->setDisplay($display_id); $render_array = $view->preview(); $context['results']['rendered'] = TRUE; // Must be time to save output! $output = $render_array['#markup']; if (!isset($context['results']['prepared']) || $context['results']['prepared'] !== TRUE) { $context['results']['failed']['copy'] = ['id' => $data_uri, 'type' => 'data export']; return; } if (!file_put_contents($data_uri, $output)) { $context['results']['failed']['copy'] = ['id' => $data_uri, 'type' => 'data export']; $msg = t('Failed to output the data to file!'); \Drupal::messenger()->addError($msg); } $context['results']['completed_count']++; } /** * Implements callback_batch_operation(). * * Renders all available pages related to a JSON:API uri and saves each as a * separate JSON file. * * @param string $api_uri * The JSON:API uri to begin processing for entity info. * @param string $path * A URI destination. * @param string $filename * The first portion a JSON filename. Comes before the file extension. * @param string $filename_suffix * The final piece of a JSON filename, including the ".json" file extension. * @param array|\ArrayAccess $context * The batch context array. */ public static function renderAndSaveJsonApiOutput($api_uri, $path, $filename, $filename_suffix, &$context) { if (!isset($context['results']['rendered'])) { $context['results']['rendered'] = FALSE; $context['results']['failed'] = []; $context['results']['completed_count'] = 0; } $context['results']['rendered'] = TRUE; // Don't take this step if the folder preparation did not succeed. if (!isset($context['results']['prepared']) || $context['results']['prepared'] !== TRUE) { $context['results']['failed']['copy'] = ['id' => $path, 'type' => 'data export']; return; } // Must be time to save output! $pages = JsonApiHelper::retrievePagesFromUri($api_uri); $page_number = 0; foreach ($pages as $page) { $page_number++; $destination = $path . $filename . $page_number . $filename_suffix; if (!file_put_contents($destination, $page)) { $context['results']['failed']['copy'] = ['id' => $path, 'type' => 'data export']; $msg = t('Failed to output the data to file!'); \Drupal::messenger()->addError($msg); } } $context['results']['completed_count']++; } /** * Implements callback_batch_operation(). * * @param array $entity_infos * An array of entity id and entity type pairs. * @param string $package_uri * The URI where the content is being packaged to. * @param array $options * The image styles we want to generate URIs for and the fields we want * to ignore. * ['image_styles' => [], 'field_blacklist' => []]. * @param array $context * The batch context array. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public static function copyEntityFiles(array $entity_infos, $package_uri, array $options, array &$context) { if (!isset($context['results']['failed']['copy'])) { $context['results']['failed']['copy'] = []; } $uris = []; foreach ($entity_infos as $info) { $id = $info['id']; $type = $info['type']; if (!isset($context['results']['prepared']) || $context['results']['prepared'] !== TRUE) { $context['results']['failed']['copy'][] = ['id' => $id, 'type' => $type]; return; } $entity = \Drupal::entityTypeManager()->getStorage($type)->load($id); $uris = array_merge($uris, EntityProcessor::processEntity($entity, $options)); } /** @var \Drupal\Core\File\FileSystem $filesystem */ $filesystem = \Drupal::service('file_system'); foreach ($uris as $source) { $source_uri = \Drupal::service('stream_wrapper_manager')->getTarget($source); $filename = $filesystem->basename($source_uri); $dirname = $filesystem->dirname($source_uri); $dest_dir = $package_uri . DIRECTORY_SEPARATOR . $dirname; $target = $dest_dir . DIRECTORY_SEPARATOR . $filename; if (self::copyImage($source, $dest_dir, $target) === FALSE) { $context['results']['failed']['copy'][] = $source; continue; } $context['results']['completed_count']++; } } /** * Implements callback_batch_operation(). * * Batch operation to duplicate files into the package folder. * * @param string $source * The path to the source image to package. * @param string $dest_dir * The directory path to copy an image to. * @param string $target * The destination path (including filename) to copy the image to. * * @return string|false * The path to the new file, or FALSE in the event of an error. * * @see file_unmanaged_copy() */ private static function copyImage($source, $dest_dir, $target) { $result = FALSE; /** @var FileSystem $fileSystem */ $fileSystem = \Drupal::service('file_system'); if (!$fileSystem->prepareDirectory($dest_dir, $fileSystem::CREATE_DIRECTORY | $fileSystem::MODIFY_PERMISSIONS)) { \Drupal::logger('content_packager')->error('Failed to create the directory to pack images: %directory', ['%directory' => $dest_dir]); } if (!$fileSystem->getDestinationFilename($target, $fileSystem::EXISTS_REPLACE)) { return FALSE; } $result = $fileSystem->copy($source, $target, $fileSystem::EXISTS_REPLACE); $dt = filemtime($source); if ($dt !== FALSE && $result !== FALSE) { touch($target, $dt); } return $result; } /** * Implements callback_batch_operation(). * * Adds a file to a zip file located at a given path. It is assumed that * the file being added is stored within the same directory that the zip * file is being produced in. * * @param string $zip_name * The file name of the zip file to generate/add to. * @param string $zip_dir * A path (no uri scheme) to the directory the zip file is destined for. * @param string $file_name * The file name being added to the zip file. * @param string $file_dir * The directory the file is located in (no uri scheme). Will have $zip_dir * trimmed from it, so the assumption is that the file and zip file are in * the same parent directory. * @param array $context * The batch context array. */ public static function zipFile($zip_name, $zip_dir, $file_name, $file_dir, array &$context) { // It initially seems like we should be using Drupal's Archiver plugins // but they really seem like they don't do what we need, which is Zip // creation. $zip = new \ZipArchive(); $zip_path = $zip_dir . DIRECTORY_SEPARATOR . $zip_name; $file_path = $file_dir . DIRECTORY_SEPARATOR . $file_name; $opened = $zip->open($zip_path, \ZipArchive::CREATE); if ($opened === FALSE) { $context['results']['failed']['zip'][] = $file_path; return; } // ZipArchive operations return true even if file doesn't exist or otherwise // has problems, so explicitly detect a failure at this point. if (!file_exists($file_path) || !is_readable($file_path)) { $context['results']['failed']['zip'][] = $file_path; return; } $internal_file_dir = $file_dir; if (substr($internal_file_dir, 0, strlen($zip_dir)) == $zip_dir) { $internal_file_dir = substr($internal_file_dir, strlen($zip_dir)); // Making sure no absolute-looking paths slip in, and no extra / ends up // on the end (we'll be adding it later). $internal_file_dir = trim($internal_file_dir, ' /'); // If we're adding a file that lives in '/' don't add the separator. if (strlen($internal_file_dir) > 0) { $internal_file_dir .= DIRECTORY_SEPARATOR; } } if ($zip->addFile($file_path, $internal_file_dir . $file_name) === FALSE) { $context['results']['failed']['zip'][] = $file_path; return; } if ($zip->close() === FALSE) { $context['results']['failed']['zip'][] = $file_path; return; } $context['results']['completed_count']++; } /** * Implements callback_batch_finished(). */ public static function packingFinished($success, $results, $operations) { if (!$success) { \Drupal::messenger()->addError('The content has not been properly packaged.'); return; } $file_count = isset($results['completed_count']) ? $results['completed_count'] : 0; \Drupal::messenger()->addStatus(t('Content packaging successfull!')); \Drupal::messenger()->addStatus(t('%file_count files copied to content packager directory. Content packaging successfull!', ['%file_count' => $file_count])); content_packager_clear_processed(); if (!empty($results['failed'])) { $msg = t(':count files did not copy or get zipped correctly; please refer to the <a href="@system-log">error log</a> for more information.', [':count' => count($results['failed']), '@system-log' => './admin/reports/dblog']); \Drupal::messenger()->addError($msg); return; } } }