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;
}
}
}