viewsCache = []; $this->cardsCache = []; $this->acf = $acf; $this->viewMarkup = $viewMarkup; $this->acfViewData = $acfViewData->getDeepClone(); $this->acfCardData = $acfCardData->getDeepClone(); $this->cardMarkup = $cardMarkup; $this->queryBuilder = $queryBuilder; } protected static function getErrorMarkup(string $error): string { return "

{$error}

"; } protected function getAcfViewData(int $viewId): AcfViewData { if (key_exists($viewId, $this->viewsCache)) { return $this->viewsCache[$viewId]; } $acfViewData = $this->acfViewData->getDeepClone(); $acfViewData->load($viewId); $this->viewsCache[$viewId] = $acfViewData; return $acfViewData; } protected function getAcfCardData(int $cardId): AcfCardData { if (key_exists($cardId, $this->cardsCache)) { return $this->cardsCache[$cardId]; } $acfCardData = $this->acfCardData->getDeepClone(); $acfCardData->load($cardId); $this->cardsCache[$cardId] = $acfCardData; return $acfCardData; } protected function getAcfView(Post $dataPost, int $viewId, int $pageId): AcfView { $viewGroup = $this->getAcfViewData($viewId); // don't use the 'AcfViewData->markup' field, as user can override it (and it shouldn't be supported) $viewMarkup = $this->viewMarkup->getMarkup($viewGroup, $pageId); return new AcfView($this->acf, $viewGroup, $dataPost, $pageId, $viewMarkup); } protected function getAcfCard(AcfCardData $acfCardData): AcfCard { return new AcfCard($acfCardData, $this->queryBuilder, $this->cardMarkup); } protected function getViewPreviewJsData(): array { $jsData = [ 'HTML' => '', 'CSS' => '', ]; global $post; if (! $this->isCPTScreen(AcfViews::NAME) || 'publish' !== $post->post_status) { return $jsData; } $acfViewData = $this->getAcfViewData($post->ID); $previewPostId = $acfViewData->previewPost ?: 0; if ($previewPostId) { $acfView = $this->getAcfView(new Post($previewPostId), $post->ID, 0); $acfView->insertFields(); // without minify, it's a preview $viewHTML = $acfView->getHTML(false); } else { $viewHTML = $this->viewMarkup->getMarkup($acfViewData, 0); } // amend to allow work the '#view' alias $viewHTML = str_replace('class="acf-view ', 'id="view" class="acf-view ', $viewHTML); $jsData['HTML'] = htmlentities($viewHTML, ENT_QUOTES); $jsData['CSS'] = htmlentities($acfViewData->getCssCode(false), ENT_QUOTES); $jsData['HOME'] = get_site_url(); return $jsData; } protected function getCardPreviewJsData(): array { $jsData = [ 'HTML' => '', 'CSS' => '', ]; global $post; if (! $this->isCPTScreen(AcfCards::NAME) || 'publish' !== $post->post_status) { return $jsData; } $acfCardData = $this->getAcfCardData($post->ID); $acfCard = $this->getAcfCard($acfCardData); $acfCard->queryPostsAndInsertData(1); $acfViewData = $this->getAcfViewData($acfCardData->acfViewId); // amend to allow work the '#card' alias $viewHTML = str_replace( 'class="acf-card ', 'id="card" class="acf-card ', $acfCard->getHTML(false) ); $jsData['HTML'] = htmlentities($viewHTML, ENT_QUOTES); // Card CSS without minification as it's for views' purposes $jsData['CSS'] = htmlentities($acfCardData->getCssCode(false), ENT_QUOTES); $jsData['VIEW_CSS'] = htmlentities($acfViewData->getCssCode(), ENT_QUOTES); $jsData['HOME'] = get_site_url(); return $jsData; } protected function enqueueAdminAssets(array $jsData = []): void { $jsData = array_merge_recursive($jsData, [ 'markupTextarea' => [ [ 'idSelector' => AcfViewData::getAcfFieldName(AcfViewData::FIELD_MARKUP), 'isReadOnly' => true, 'mode' => 'htmlmixed', ], [ 'idSelector' => AcfViewData::getAcfFieldName(AcfViewData::FIELD_CSS_CODE), 'isReadOnly' => false, 'mode' => 'css', ], [ 'idSelector' => AcfViewData::getAcfFieldName(AcfViewData::FIELD_JS_CODE), 'isReadOnly' => false, 'mode' => 'javascript', ], [ 'idSelector' => AcfViewData::getAcfFieldName(AcfViewData::FIELD_CUSTOM_MARKUP), 'isReadOnly' => false, 'mode' => 'htmlmixed', ], [ 'idSelector' => AcfViewData::getAcfFieldName(AcfViewData::FIELD_PHP_VARIABLES), 'isReadOnly' => false, 'mode' => 'php', ], [ 'idSelector' => AcfCardData::getAcfFieldName(AcfCardData::FIELD_MARKUP), 'isReadOnly' => true, 'mode' => 'htmlmixed', ], [ 'idSelector' => AcfCardData::getAcfFieldName(AcfCardData::FIELD_CSS_CODE), 'isReadOnly' => false, 'mode' => 'css', ], [ 'idSelector' => AcfCardData::getAcfFieldName(AcfCardData::FIELD_JS_CODE), 'isReadOnly' => false, 'mode' => 'javascript', ], [ 'idSelector' => AcfCardData::getAcfFieldName( AcfCardData::FIELD_CUSTOM_MARKUP ), 'isReadOnly' => false, 'mode' => 'htmlmixed', ], [ 'idSelector' => AcfCardData::getAcfFieldName(AcfCardData::FIELD_QUERY_PREVIEW), 'isReadOnly' => true, 'mode' => 'javascript', ], ], 'fieldSelect' => [ [ 'mainSelectId' => Item::getAcfFieldName(Item::FIELD_GROUP), 'subSelectId' => Field::getAcfFieldName(Field::FIELD_KEY), 'identifierInputId' => Field::getAcfFieldName(Field::FIELD_ID), ], [ 'mainSelectId' => AcfCardData::getAcfFieldName(AcfCardData::FIELD_ORDER_BY_META_FIELD_GROUP), 'subSelectId' => AcfCardData::getAcfFieldName(AcfCardData::FIELD_ORDER_BY_META_FIELD_KEY), 'identifierInputId' => '', ], [ 'mainSelectId' => Field::getAcfFieldName(Field::FIELD_KEY), 'subSelectId' => RepeaterField::getAcfFieldName(RepeaterField::FIELD_KEY), 'identifierInputId' => RepeaterField::getAcfFieldName(RepeaterField::FIELD_ID), ], [ 'mainSelectId' => MetaField::getAcfFieldName(MetaField::FIELD_GROUP), 'subSelectId' => MetaField::getAcfFieldName(MetaField::FIELD_FIELD_KEY), 'identifierInputId' => '', ], [ 'mainSelectId' => TaxField::getAcfFieldName(TaxField::FIELD_TAXONOMY), 'subSelectId' => TaxField::getAcfFieldName(TaxField::FIELD_TERM), 'identifierInputId' => '', ], ], 'viewPreview' => $this->getViewPreviewJsData(), 'cardPreview' => $this->getCardPreviewJsData(), ]); wp_enqueue_style(AcfViews::NAME, $this->getAssetsUrl('admin.css'), [], $this->getVersion()); // jquery is necessary for select2 events wp_enqueue_script(AcfViews::NAME, $this->getAssetsUrl('admin.js'), ['jquery',], $this->getVersion()); wp_localize_script(AcfViews::NAME, 'acf_views', $jsData); } public function getName(): string { return $this->name; } public function getSlug(): string { return $this->slug; } public function getVersion(): string { // return strval(time()); return $this->version; } public function getAssetsUrl(string $file): string { return plugin_dir_url(__FILE__) . 'assets/' . $file; } public function setHooks(): void { add_action('admin_notices', [$this, 'showWarningAboutInactiveAcfPlugin']); add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']); add_shortcode(Plugin::SHORTCODE, [$this, 'acfViewsShortcode']); add_shortcode(Plugin::SHORTCODE_CARDS, [$this, 'acfCardsShortcode']); add_action('wp_footer', [$this, 'printCustomAssets']); add_filter('acf/field_wrapper_attributes', [$this, 'addClassToAdminProFieldClasses'], 10, 2); } public function showWarningAboutInactiveAcfPlugin(): void { if (is_plugin_active('advanced-custom-fields-pro/acf.php')) { return; } echo '
' . '

"ACF Views" plugin requires "Advanced Custom Fields PRO" plugin to be enabled on your website (with or without a license).' . ' Please enable "Advanced Custom Fields PRO" plugin or "ACF Views" plugin won\'t work properly.

' . '
'; } public function acfCardsShortcode($attrs): string { $cardId = (int)($attrs['card-id'] ?? 0); $acfCardPost = $cardId ? get_post($cardId) : null; $shortCodeName = ':' . self::SHORTCODE_CARDS . ':'; if (! $acfCardPost || ! in_array($acfCardPost->post_type, [AcfCards::NAME,], true) || 'publish' !== $acfCardPost->post_status ) { return self::getErrorMarkup($shortCodeName . ' card-id attribute is missing or wrong'); } $acfCardData = $this->getAcfCardData($cardId); $acfCard = $this->getAcfCard($acfCardData); $acfCard->queryPostsAndInsertData(1); return $acfCard->getHTML(); } public function acfViewsShortcode($attrs): string { $viewId = (int)($attrs['view-id'] ?? 0); $acfViewPost = $viewId ? get_post($viewId) : null; $shortCodeName = ':' . self::SHORTCODE . ':'; if (! $acfViewPost || ! in_array($acfViewPost->post_type, [AcfViews::NAME,], true) || 'publish' !== $acfViewPost->post_status ) { return self::getErrorMarkup($shortCodeName . ' view-id attribute is missing or wrong'); } $dataPostId = (int)($attrs['object-id'] ?? 0); $dataPostId = $dataPostId ?: get_queried_object_id(); $dataPostId = get_post($dataPostId) ? $dataPostId : 0; if (! $dataPostId) { return self::getErrorMarkup($shortCodeName . ' object id is not a post'); } $acfView = $this->getAcfView(new Post($dataPostId), $viewId, get_queried_object_id()); $acfView->insertFields(); return $acfView->getHTML(); } public function enqueueAdminScripts(): void { $currentScreen = get_current_screen(); if ($currentScreen && (in_array($currentScreen->id, [AcfViews::NAME, AcfCards::NAME,], true) || in_array($currentScreen->post_type, [AcfViews::NAME, AcfCards::NAME], true))) { $this->enqueueAdminAssets(); } } public function printCustomAssets(): void { $code = ''; $views = $this->viewMarkup->getRenderedViews(); foreach ($views as $view) { $cssCode = $view->getCssCode(); // 'minify' JS $jsCode = str_replace(["\t", "\n", "\r"], '', $view->jsCode); $jsCode = trim($jsCode); // no escaping, it's a CSS code, so e.g '.a > .b' shouldn't be escaped $code .= $cssCode ? sprintf("", $view->getSource(), $cssCode) : ''; $code .= $jsCode ? sprintf("", $view->getSource(), $jsCode) : ''; } $cards = $this->cardMarkup->getRenderedCards(); foreach ($cards as $card) { $cssCode = $card->getCssCode(); // 'minify' JS $jsCode = str_replace(["\t", "\n", "\r"], '', $card->jsCode); $jsCode = trim($jsCode); // no escaping, it's a CSS code, so e.g '.a > .b' shouldn't be escaped $code .= $cssCode ? sprintf("", $card->getSource(), $cssCode) : ''; $code .= $jsCode ? sprintf("", $card->getSource(), $jsCode) : ''; } echo $code; } public function addClassToAdminProFieldClasses(array $wrapper, array $field): array { if (defined('ACF_VIEWS_PRO') || ! key_exists('a-pro', $field)) { return $wrapper; } if (! key_exists('class', $wrapper)) { $wrapper['class'] = ''; } $wrapper['class'] .= ' acf-views-pro'; return $wrapper; } public function isCPTScreen(string $cptName): bool { $currentScreen = get_current_screen(); $isTargetPost = in_array($currentScreen->id, [$cptName,], true) || in_array($currentScreen->post_type, [$cptName], true); // base = edit (list management), post (editing), add (adding) return $isTargetPost && in_array($currentScreen->base, ['post', 'add',], true); } }