viewMarkup = $viewMarkup; } protected function updateIdentifiers(AcfViewData $acfViewData): void { foreach ($acfViewData->items as $item) { $item->field->id = ($item->field->id && !preg_match('/^[a-zA-Z0-9_\-]+$/', $item->field->id)) ? '' : $item->field->id; if ($item->field->id && $item->field->id === $this->getUniqueFieldId($acfViewData, $item, $item->field->id)) { continue; } $fieldMeta = new FieldMeta($item->field->getAcfFieldId()); if (!$fieldMeta->isFieldExist()) { continue; } // $Post$ fields have '_' prefix, remove it, otherwise looks bad in the markup $name = ltrim($fieldMeta->getName(), '_'); // transform '_' to '-' to follow the BEM standard (underscore only as a delimiter) $name = str_replace('_', '-', $name); $item->field->id = $this->getUniqueFieldId($acfViewData, $item, $name); } } protected function updateMarkup(AcfViewData $acfViewData): void { // pageId 0, so without CSS, also skipCache and customMarkup $viewMarkup = $this->viewMarkup->getMarkup($acfViewData, 0, '', true, true); $acfViewData->markup = $viewMarkup; } /** * @param $cptData * @return array */ protected function getTranslatableLabels($cptData): array { $labels = []; foreach ($cptData->items as $item) { if ($item->field->label) { $labels[] = $item->field->label; } if ($item->field->linkLabel) { $labels[] = $item->field->linkLabel; } } return $labels ? [ Plugin::getThemeTextDomain() => array_unique($labels), ] : []; } public function getUniqueFieldId(AcfViewData $acfViewData, $excludeObject, string $name): string { $isUnique = true; foreach ($acfViewData->items as $item) { if ($item === $excludeObject || $item->field->id !== $name) { continue; } $isUnique = false; break; } return $isUnique ? $name : $this->getUniqueFieldId($acfViewData, $excludeObject, $name . '2'); } public function refreshAjax(): void { $viewId = (int)($_POST['_postId'] ?? 0); $isWithShortcode = isset($_POST['_withShortcode']); $postType = get_post($viewId)->post_type ?? ''; if (self::NAME !== $postType) { echo "Post id is wrong"; exit; } $acfViewData = $this->cache->getAcfViewData($viewId); $response = ''; // ignore customMarkup (we need the preview) $markup = $this->viewMarkup->getMarkup( $acfViewData, 0, '', false, true ); $response .= sprintf('
%s
', $markup); if ($isWithShortcode) { $shortcodes = $this->html->postboxShortcodes( $acfViewData->getUniqueId(true), false, Shortcodes::SHORTCODE_VIEWS, get_the_title($viewId), false ); $response .= sprintf('
%s
', $shortcodes); } echo $response; exit; } public function setHooks(): void { parent::setHooks(); add_action('init', [$this, 'addCPT']); add_action( 'manage_' . self::NAME . '_posts_custom_column', [ $this, 'printColumn', ], 10, 2 ); // priority is important here, should be 1) after the acf code (20) // 2) after the CPT save hook, which replaces fields with json (30) add_action('acf/save_post', [$this, 'performSaveActions'], 30); add_action('admin_menu', [$this, 'removeAddNewItemSubmenuLink']); add_action('pre_get_posts', [$this, 'addSortableColumnsToRequest',]); add_action('wp_ajax_acf_views__view_refresh', [$this, 'refreshAjax',]); add_filter('manage_' . self::NAME . '_posts_columns', [$this, 'getColumns',]); add_filter('manage_edit-' . self::NAME . '_sortable_columns', [$this, 'getSortableColumns',]); add_filter('enter_title_here', [$this, 'getTitlePlaceholder',]); } public function replacePostUpdatedMessage(array $messages): array { global $post; $restoredMessage = false; $scheduledMessage = __('ACF View scheduled for:', 'acf-views'); $scheduledMessage .= sprintf( ' %1$s', date_i18n('M j, Y @ G:i', strtotime($post->post_date)) ); if (isset($_GET['revision'])) { $restoredMessage = __('ACF View restored to revision from', 'acf-views'); $restoredMessage .= ' ' . wp_post_revision_title((int)$_GET['revision'], false); } $messages[self::NAME] = [ 0 => '', // Unused. Messages start at index 1. 1 => __('ACF View updated.', 'acf-views'), 2 => __('Custom field updated.', 'acf-views'), 3 => __('Custom field deleted.', 'acf-views'), 4 => __('ACF View updated.', 'acf-views'), 5 => $restoredMessage, 6 => __('ACF View published.', 'acf-views'), 7 => __('ACF View saved.', 'acf-views'), 8 => __('ACF View submitted.', 'acf-views'), 9 => $scheduledMessage, 10 => __('ACF View draft updated.', 'acf-views'), ]; if (self::NAME === $post->post_type && 'publish' === $post->post_status) { $acfViewData = $this->cache->getAcfViewData($post->ID); $customMarkup = trim($acfViewData->customMarkup); if ($customMarkup) { $extraMessage = "
Custom Markup is in use, if you've added or removed fields then remember to update your Markup too."; $messages[self::NAME][1] .= $extraMessage; $messages[self::NAME][4] .= $extraMessage; } } return $messages; } /** * @param int|string $postId * * @return void */ public function performSaveActions($postId): void { if (!$this->isMyPost($postId)) { return; } $acfViewData = $this->cache->getAcfViewData($postId); $this->updateIdentifiers($acfViewData); $this->updateMarkup($acfViewData); $this->updateTranslationsFile($acfViewData); $this->maybeSetUniqueId($acfViewData, 'view_'); // it'll also update post fields, like 'comment_count' $acfViewData->saveToPostContent(); } public function addCPT(): void { $labels = [ 'name' => __('ACF Views', 'acf-views'), 'singular_name' => __('ACF View', 'acf-views'), 'menu_name' => __('ACF Views', 'acf-views'), 'parent_item_colon' => __('Parent ACF View', 'acf-views'), 'all_ite__(ms' => __('ACF Views', 'acf-views'), 'view_item' => __('Browse ACF View', 'acf-views'), 'add_new_item' => __('Add New ACF View', 'acf-views'), 'add_new' => __('Add New', 'acf-views'), 'item_updated' => __('ACF View updated.', 'acf-views'), 'edit_item' => __('Edit ACF View', 'acf-views'), 'update_item' => __('Update ACF View', 'acf-views'), 'search_items' => __('Search ACF View', 'acf-views'), 'not_found' => __('Not Found', 'acf-views'), 'not_found_in_trash' => __('Not Found In Trash', 'acf-views'), ]; $description = __( 'Create an ACF View item by selecting post fields, paste the shortcode in place to display field values for a specific post/page/CPT item.', 'acf-views' ); $args = [ 'label' => __('ACF Views', 'acf-views'), 'description' => $description, 'labels' => $labels, // shouldn't be presented in the sitemap and other places 'public' => false, 'show_ui' => true, 'show_in_rest' => true, 'has_archive' => false, 'show_in_menu' => true, 'show_in_nav_menus' => false, 'delete_with_user' => false, 'exclude_from_search' => true, 'capability_type' => 'post', 'hierarchical' => false, 'can_export' => false, 'rewrite' => false, 'query_var' => false, 'menu_icon' => 'dashicons-format-gallery', 'supports' => ['title', 'editor',], 'show_in_graphql' => false, // right under ACF, which has 80 'menu_position' => 81, ]; register_post_type(self::NAME, $args); } public function getColumns(array $columns): array { unset($columns['date']); return array_merge($columns, [ self::COLUMN_DESCRIPTION => __('Description', 'acf-views'), self::COLUMN_SHORTCODE => __('Shortcode', 'acf-views'), self::COLUMN_AUTHOR => __('Author', 'acf-views'), self::COLUMN_LAST_MODIFIED => __('Last modified', 'acf-views'), self::COLUMN_CREATED => __('Created', 'acf-views'), ]); } public function getSortableColumns(array $columns): array { return array_merge($columns, [ self::COLUMN_AUTHOR => self::COLUMN_AUTHOR, self::COLUMN_LAST_MODIFIED => self::COLUMN_LAST_MODIFIED, self::COLUMN_CREATED => self::COLUMN_CREATED, ]); } public function addSortableColumnsToRequest(WP_Query $query): void { if (!is_admin()) { return; } $orderBy = $query->get('orderby'); switch ($orderBy) { case self::COLUMN_AUTHOR: $query->set('orderby', 'post_author'); break; case self::COLUMN_LAST_MODIFIED: $query->set('orderby', 'post_modified'); break; case self::COLUMN_CREATED: $query->set('orderby', 'post_date'); break; } } public function printColumn(string $column, int $postId): void { switch ($column) { case self::COLUMN_DESCRIPTION: $view = $this->cache->getAcfViewData($postId); echo esc_html($view->description); break; case self::COLUMN_SHORTCODE: $viewUniqueId = $this->cache->getAcfViewData($postId)->getUniqueId(true); echo $this->html->postboxShortcodes( $viewUniqueId, true, Shortcodes::SHORTCODE_VIEWS, get_the_title($postId), false ); break; case self::COLUMN_AUTHOR: echo esc_html(get_user_by('id', get_post($postId)->post_author)->display_name ?? ''); break; case self::COLUMN_LAST_MODIFIED: echo esc_html(explode(' ', get_post($postId)->post_modified)[0]); break; case self::COLUMN_CREATED: echo esc_html(explode(' ', get_post($postId)->post_date)[0]); break; } } public function addMetaboxes(): void { parent::addMetaboxes(); add_meta_box( 'acf-views_shortcode', __('Shortcode', 'acf-views'), function ($post, $meta) { if (!$post || 'publish' !== $post->post_status) { echo __('Your View shortcode is available after publishing.', 'acf-views'); return; } $viewUniqueId = $this->cache->getAcfViewData($post->ID)->getUniqueId(true); echo $this->html->postboxShortcodes( $viewUniqueId, false, Shortcodes::SHORTCODE_VIEWS, get_the_title($post), false ); }, [ self::NAME, ], 'side', // right after the publish button 'core' ); } public function getTitlePlaceholder(string $title): string { $screen = get_current_screen()->post_type ?? ''; if (self::NAME !== $screen) { return $title; } return __('Name your view', 'acf-views'); } public function removeAddNewItemSubmenuLink(): void { $url = sprintf('edit.php?post_type=%s', self::NAME); global $submenu; if (!$submenu[$url]) { $submenu[$url] = []; } foreach ($submenu[$url] as $itemKey => $item) { if (3 !== count($item) || $item[2] !== 'post-new.php?post_type=acf_views') { continue; } unset($submenu[$url][$itemKey]); break; } } }