*/ protected array $values; protected ToolsData $toolsData; protected CardsDataStorage $cardsDataStorage; protected ViewsDataStorage $viewsDataStorage; protected Plugin $plugin; protected ViewData $acfViewData; protected CardData $acfCardData; /** * @var array> */ protected array $exportData; protected bool $isImportSuccessfull; protected string $importResultMessage; public function __construct( ToolsData $toolsData, CardsDataStorage $cardsDataStorage, ViewsDataStorage $viewsDataStorage, Plugin $plugin, ViewData $acfViewData, CardData $acfCardData ) { $this->toolsData = $toolsData; $this->cardsDataStorage = $cardsDataStorage; $this->viewsDataStorage = $viewsDataStorage; $this->plugin = $plugin; $this->acfViewData = $acfViewData; $this->acfCardData = $acfCardData; $this->values = []; $this->exportData = []; $this->isImportSuccessfull = false; $this->importResultMessage = ''; } /** * @param mixed $postId */ protected function isMySource($postId): bool { $screen = get_current_screen(); return null !== $screen && 'acf_views_page_acf-views-tools' === $screen->id && 'options' === $postId; } /** * @param string[] $slugs * @return WP_Post[] */ protected function getPosts(string $postType, array $slugs): array { $queryArgs = [ 'post_type' => $postType, 'post_status' => 'publish', 'posts_per_page' => -1, ]; if ([] !== $slugs) { $queryArgs['post_name__in'] = $slugs; } $query = new WP_Query($queryArgs); /** * @var WP_Post[] */ return $query->get_posts(); } protected function export(): void { $isViewsInExport = $this->toolsData->isExportAllViews || [] !== $this->toolsData->exportViews; $isCardsInExport = $this->toolsData->isExportAllCards || [] !== $this->toolsData->exportCards; $viewPosts = $isViewsInExport ? $this->getPosts(ViewsCpt::NAME, $this->toolsData->exportViews) : []; $cardPosts = $isCardsInExport ? $this->getPosts(CardsCpt::NAME, $this->toolsData->exportCards) : []; foreach ($viewPosts as $viewPost) { $acfViewData = $this->viewsDataStorage->get($viewPost->post_name); // we don't need to save defaults $this->exportData[$viewPost->post_name] = $acfViewData->getFieldValues('', true); } foreach ($cardPosts as $cardPost) { $acfCardData = $this->cardsDataStorage->get($cardPost->post_name); $cardUniqueId = $acfCardData->getUniqueId(); // we don't need to save defaults $this->exportData[$cardUniqueId] = $acfCardData->getFieldValues('', true); } } /** * @param string[] $successViewIds * @param string[] $successCardIds * @param string[] $failViewUniqueIds * @param string[] $failCardUniqueIds * @return string */ protected function getImportResultMessage( array $successViewIds, array $successCardIds, array $failViewUniqueIds, array $failCardUniqueIds ): string { $viewsInfo = []; $cardsInfo = []; $importResultMessage = ''; foreach ($successViewIds as $successViewId) { $successViewId = (int)$successViewId; $viewsInfo[] = sprintf( '%s', get_edit_post_link($successViewId), get_the_title($successViewId) ); } foreach ($successCardIds as $successCardId) { $successCardId = (int)$successCardId; $cardsInfo[] = sprintf( '%s', get_edit_post_link($successCardId), get_the_title($successCardId) ); } if ([] === $failViewUniqueIds && [] === $failCardUniqueIds) { $this->isImportSuccessfull = true; $importResultMessage .= sprintf( __('Successfully imported %d Views and %d Cards.', 'acf-views'), count($successViewIds), count($successCardIds) ); $importResultMessage .= '
'; } else { $importResultMessage .= sprintf( __('Something went wrong. Imported %d from %d Views and %d from %d Cards.', 'acf-views'), count($successViewIds), count($successViewIds) + count($failViewUniqueIds), count($successCardIds), count($successCardIds) + count($failCardUniqueIds) ); $importResultMessage .= '
'; } if ([] !== $viewsInfo) { $viewsLabel = __('Imported Views', 'acf-views'); $importResultMessage .= sprintf( '%s: %s.', $viewsLabel, implode(', ', $viewsInfo) ); $importResultMessage .= '
'; } if ([] !== $cardsInfo) { $cardsLabel = __('Imported Cards', 'acf-views'); $importResultMessage .= sprintf( '%s: %s.', $cardsLabel, implode(', ', $cardsInfo) ); $importResultMessage .= '
'; } if ([] !== $failViewUniqueIds) { $viewsLabel = __('Wrong Views', 'acf-views'); $importResultMessage .= sprintf( '%s: %s.', $viewsLabel, implode(', ', $failViewUniqueIds) ); $importResultMessage .= '
'; } if ([] !== $failCardUniqueIds) { $cardsLabel = __('Wrong Cards', 'acf-views'); $importResultMessage .= sprintf( '%s: %s.', $cardsLabel, implode(', ', $failCardUniqueIds) ); $importResultMessage .= '
'; } return $importResultMessage; } /** * @param array $jsonData * @throws Exception */ protected function importOrUpdateItems(array $jsonData): void { $successViewIds = []; $successCardIds = []; $failViewUniqueIds = []; $failCardUniqueIds = []; foreach ($jsonData as $uniqueId => $details) { if (!is_array($details)) { continue; } $postType = false !== strpos($uniqueId, ViewData::UNIQUE_ID_PREFIX) ? ViewsCpt::NAME : CardsCpt::NAME; $dataStorage = ViewsCpt::NAME === $postType ? $this->viewsDataStorage : $this->cardsDataStorage; $titleField = ViewsCpt::NAME === $postType ? ViewData::getAcfFieldName(ViewData::FIELD_TITLE) : CardData::getAcfFieldName(CardData::FIELD_TITLE); $title = $details[$titleField] ?? ''; $title = is_string($title) ? $title : ''; // get item, maybe it's already exists (then we'll override it) $cptData = $dataStorage->get($uniqueId); // insert if missing $cptData = false === $cptData->isLoaded() ? $dataStorage->createNew('publish', $title, null, $uniqueId) : $cptData; if (null === $cptData) { if (ViewsCpt::NAME === $postType) { $failViewUniqueIds[] = $uniqueId; } else { $failCardUniqueIds[] = $uniqueId; } continue; } // load all the old data. It'll also override the unique id if the instance is just made, that's right as id kept the same $cptData->load($cptData->getPostId(), '', $details); $dataStorage->save($cptData); // there is no sense to call the 'performSaveActions' method if (ViewsCpt::NAME === $postType) { $successViewIds[] = $cptData->getPostId(); } else { $successCardIds[] = $cptData->getPostId(); } } $resultMessage = []; $resultMessage[] = implode(',', $successViewIds); $resultMessage[] = implode(',', $successCardIds); $resultMessage[] = implode(',', $failViewUniqueIds); $resultMessage[] = implode(',', $failCardUniqueIds); $this->importResultMessage = implode(';', $resultMessage); } protected function import(): void { $pathToFile = (string)get_attached_file($this->toolsData->importFile); if ('' === $pathToFile || !file_exists($pathToFile)) { $this->importResultMessage = __('Import file not found.', 'acf-views'); return; } $fileContent = (string)file_get_contents($pathToFile); // remove the prefix, that was added to avoid WP Media library JSON detection $fileContent = str_replace('Advanced Views:', '', $fileContent); $jsonData = json_decode($fileContent, true); if (JSON_ERROR_NONE !== json_last_error()) { $this->importResultMessage = __('Import file is not a valid JSON.', 'acf-views'); return; } $jsonData = is_array($jsonData) ? $jsonData : []; $this->importOrUpdateItems($jsonData); wp_delete_attachment($this->toolsData->importFile, true); $url = $this->plugin->getAdminUrl(self::SLUG) . sprintf( '&message=1&type=import&isSuccess=%s&resultMessage=%s', $this->isImportSuccessfull, $this->importResultMessage ); wp_redirect($url); exit; } /** * @param mixed $postId */ public function maybeEchoExportFile($postId): void { if (!$this->isMySource($postId) || [] === $this->exportData) { return; } $ids = array_keys($this->exportData); $viewIds = array_filter($ids, function ($id) { return false !== strpos($id, ViewData::UNIQUE_ID_PREFIX); }); $countOfViewIds = count($viewIds); $cardIds = array_filter($ids, function ($id) { return false !== strpos($id, CardData::UNIQUE_ID_PREFIX); }); $countOfCardIds = count($cardIds); $redirectUrl = $this->plugin->getAdminUrl(self::SLUG) . sprintf('&message=1&type=export&_views=%s&_cards=%s', $countOfViewIds, $countOfCardIds); ?> getImportResultMessage( $successViewIds, $successCardIds, $failViewUniqueIds, $failCardUniqueId ); } acf_options_page()->add_page([ 'slug' => self::SLUG, 'page_title' => __('Tools', 'acf-views'), 'menu_title' => __('Tools', 'acf-views'), 'parent_slug' => sprintf('edit.php?post_type=%s', ViewsCpt::NAME), 'position' => 3, 'update_button' => __('Process', 'acf-views'), 'updated_message' => $updatedMessage, ]); } /** * @param mixed $postId */ public function maybeCatchValues($postId): void { if (!$this->isMySource($postId)) { return; } add_filter('acf/pre_update_value', function ($isUpdated, $value, $postId, array $field): bool { // extra check, as probably it's about another post if (!$this->isMySource($postId)) { return $isUpdated; } $fieldName = (string)($field['name'] ?? ''); $this->values[$fieldName] = $value; // avoid saving to the postmeta return true; }, 10, 4); } /** * @param mixed $postId * @throws Exception */ public function maybeProcess($postId): void { if (!$this->isMySource($postId) || [] === $this->values) { return; } $this->toolsData->load(false, '', $this->values); $isExport = $this->toolsData->isExportAllViews || $this->toolsData->isExportAllCards || [] !== $this->toolsData->exportViews || [] !== $this->toolsData->exportCards; $isImport = 0 !== $this->toolsData->importFile; if ($isExport) { $this->export(); } if ($isImport) { $this->import(); } } public function setHooks(CurrentScreen $currentScreen): void { if (false === $currentScreen->isAdmin()) { return; } // init, not acf/init, as the method uses 'get_edit_post_link' which will be available only since this hook // (because we sign up the CPTs in this hook) add_action('init', [$this, 'addPage',]); add_action('acf/save_post', [$this, 'maybeCatchValues',]); // priority 20, as it's after the ACF's save_post hook add_action('acf/save_post', [$this, 'maybeProcess',], 20); // priority 30, after the process action add_action('acf/save_post', [$this, 'maybeEchoExportFile',], 30); } }