316 lines
16 KiB
PHP
316 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Article extension fields shared by the block and classic editors.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Core;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class PostMetaBox {
|
|
private static $instance = null;
|
|
|
|
public const META_KEY = DomainModel::POST_META_KEY;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
public function init(): void {
|
|
add_action('add_meta_boxes', [$this, 'register_meta_boxes']);
|
|
add_action('save_post_post', [$this, 'save_meta_box'], 10, 2);
|
|
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public static function get_meta(int $post_id): array {
|
|
$saved = get_post_meta($post_id, self::META_KEY, true);
|
|
$defaults = DomainModel::default_post_meta();
|
|
$meta = array_merge($defaults, is_array($saved) ? array_intersect_key($saved, $defaults) : []);
|
|
$meta['views'] = max(0, (int) get_post_meta($post_id, DomainModel::POST_VIEW_COUNT_KEY, true));
|
|
$meta['like'] = max(0, (int) get_post_meta($post_id, '_aksrt_like_count', true));
|
|
$meta['featured_video_episode'] = self::sanitize_video_episodes($meta['featured_video_episode'] ?? []);
|
|
return $meta;
|
|
}
|
|
|
|
public static function get_meta_value(int $post_id, string $key, $default = '') {
|
|
$meta = self::get_meta($post_id);
|
|
return array_key_exists($key, $meta) ? $meta[$key] : $default;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $raw Raw episode rows.
|
|
* @return array<int, array{title:string,url:string}>
|
|
*/
|
|
public static function sanitize_video_episodes($raw): array {
|
|
if (!is_array($raw)) {
|
|
return [];
|
|
}
|
|
$episodes = [];
|
|
foreach ($raw as $row) {
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
$url = esc_url_raw((string) ($row['url'] ?? ''));
|
|
if ($url === '') {
|
|
continue;
|
|
}
|
|
$episodes[] = [
|
|
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
|
|
'url' => $url,
|
|
];
|
|
}
|
|
return array_slice($episodes, 0, 50);
|
|
}
|
|
|
|
public function register_meta_boxes(): void {
|
|
add_meta_box('post_main', __('文章扩展', 'aksrt'), [$this, 'render_meta_box'], 'post', 'side', 'high');
|
|
|
|
$settings = class_exists('\AKSRT\Settings\Settings')
|
|
? \AKSRT\Settings\Settings::get_post_filter_settings()
|
|
: [];
|
|
if (!empty($settings['custom_filter'])) {
|
|
add_meta_box(
|
|
'aksrt_custom_filter_meta',
|
|
__('高级自定义筛选', 'aksrt'),
|
|
[$this, 'render_custom_filter_meta_box'],
|
|
'post',
|
|
'side',
|
|
'default'
|
|
);
|
|
}
|
|
}
|
|
|
|
public function enqueue_assets(string $hook): void {
|
|
if (!in_array($hook, ['post.php', 'post-new.php'], true)) {
|
|
return;
|
|
}
|
|
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
|
if (!$screen || $screen->post_type !== 'post') {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_media();
|
|
$css = AKSRT_DIR . '/assets/css/admin/post-extension.css';
|
|
$js = AKSRT_DIR . '/assets/js/admin-post-extension.js';
|
|
if (is_file($css)) {
|
|
wp_enqueue_style('aksrt-post-extension', AKSRT_URI . '/assets/css/admin/post-extension.css', [], (string) filemtime($css));
|
|
}
|
|
if (is_file($js)) {
|
|
wp_enqueue_script('aksrt-post-extension', AKSRT_URI . '/assets/js/admin-post-extension.js', ['jquery'], (string) filemtime($js), true);
|
|
}
|
|
}
|
|
|
|
public function render_meta_box(\WP_Post $post): void {
|
|
$meta = self::get_meta($post->ID);
|
|
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
|
$show_image = !array_key_exists('post_article_image_cover', $settings) || !empty($settings['post_article_image_cover']);
|
|
$show_slide = !array_key_exists('list_thumb_slides_s', $settings)
|
|
|| !empty($settings['list_thumb_slides_s'])
|
|
|| !empty($settings['post_article_slide_cover']);
|
|
$show_video = !array_key_exists('list_thumb_video_s', $settings)
|
|
|| !empty($settings['list_thumb_video_s'])
|
|
|| !empty($settings['post_article_video_cover']);
|
|
|
|
wp_nonce_field('aksrt_post_extension_save', 'aksrt_post_extension_nonce');
|
|
?>
|
|
<div class="aksrt-post-extension csf-fields" data-aksrt-post-extension>
|
|
<?php
|
|
$this->render_upload_field(
|
|
'thumbnail_url',
|
|
__('外链特色图像', 'aksrt'),
|
|
(string) $meta['thumbnail_url'],
|
|
'image',
|
|
__('支持直接输入链接用作特色图像', 'aksrt') . '<br><span class="aksrt-post-extension__danger">' . __('此处设置仅在未设置 WordPress 特色图片时有效', 'aksrt') . '</span>'
|
|
);
|
|
if ($show_image) {
|
|
$this->render_upload_field('cover_image', __('封面图', 'aksrt'), (string) $meta['cover_image'], 'image', __('在文章页顶部显示封面图', 'aksrt'));
|
|
}
|
|
?>
|
|
|
|
<?php if ($show_slide) : ?>
|
|
<div class="aksrt-post-extension__field">
|
|
<div class="aksrt-post-extension__title"><label for="aksrt-featured-slide"><?php esc_html_e('特色幻灯片', 'aksrt'); ?></label></div>
|
|
<div class="aksrt-post-extension__control">
|
|
<input id="aksrt-featured-slide" type="hidden" name="aksrt_post_extension[featured_slide]" value="<?php echo esc_attr((string) $meta['featured_slide']); ?>" data-gallery-input>
|
|
<div class="aksrt-post-extension__gallery" data-gallery-preview></div>
|
|
<div class="aksrt-post-extension__buttons">
|
|
<button type="button" class="button" data-gallery-select><?php esc_html_e('添加图像', 'aksrt'); ?></button>
|
|
<button type="button" class="button-link-delete" data-gallery-clear><?php esc_html_e('清空图像', 'aksrt'); ?></button>
|
|
</div>
|
|
<p class="description"><?php esc_html_e('为该文章显示幻灯片封面或幻灯片缩略图(优先级 > 特色图像及封面图)', 'aksrt'); ?></p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($show_video) : ?>
|
|
<?php $this->render_upload_field('featured_video', __('特色视频', 'aksrt'), (string) $meta['featured_video'], 'video', __('为该文章显示视频封面(优先级 > 幻灯片封面)', 'aksrt'), __('选择视频或填写视频地址', 'aksrt')); ?>
|
|
<div class="aksrt-post-extension__field compact" data-video-dependent>
|
|
<div class="aksrt-post-extension__title"><span><?php esc_html_e('本集标题', 'aksrt'); ?></span></div>
|
|
<div class="aksrt-post-extension__control">
|
|
<input type="text" name="aksrt_post_extension[featured_video_title]" value="<?php echo esc_attr((string) $meta['featured_video_title']); ?>" placeholder="<?php esc_attr_e('第1集', 'aksrt'); ?>">
|
|
<p class="description"><?php esc_html_e('如需添加剧集则需填写此处', 'aksrt'); ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="aksrt-post-extension__field compact" data-video-dependent>
|
|
<div class="aksrt-post-extension__title"><span><?php esc_html_e('视频剧集', 'aksrt'); ?></span></div>
|
|
<div class="aksrt-post-extension__control">
|
|
<p class="description"><?php esc_html_e('为视频封面添加更多剧集', 'aksrt'); ?></p>
|
|
<div data-episode-list>
|
|
<?php foreach ((array) $meta['featured_video_episode'] as $index => $episode) : ?>
|
|
<?php $this->render_episode_row((int) $index, (array) $episode); ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<button type="button" class="button" data-episode-add><?php esc_html_e('添加剧集', 'aksrt'); ?></button>
|
|
<template data-episode-template><?php $this->render_episode_row('__INDEX__', []); ?></template>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php $this->render_text_field('subtitle', __('副标题', 'aksrt'), (string) $meta['subtitle']); ?>
|
|
<?php $this->render_number_field('views', __('阅读量', 'aksrt'), (int) $meta['views']); ?>
|
|
<?php $this->render_number_field('like', __('点赞数', 'aksrt'), (int) $meta['like']); ?>
|
|
|
|
<fieldset class="aksrt-post-extension__field">
|
|
<legend class="aksrt-post-extension__title"><?php esc_html_e('显示布局', 'aksrt'); ?></legend>
|
|
<div class="aksrt-post-extension__control aksrt-post-extension__radios">
|
|
<?php
|
|
$layouts = [
|
|
'false' => __('跟随主题', 'aksrt'),
|
|
'no_sidebar' => __('无侧边栏', 'aksrt'),
|
|
'sidebar_left' => __('侧边栏靠左', 'aksrt'),
|
|
'sidebar_right' => __('侧边栏靠右', 'aksrt'),
|
|
];
|
|
foreach ($layouts as $value => $label) :
|
|
?>
|
|
<label><input type="radio" name="aksrt_post_extension[show_layout]" value="<?php echo esc_attr($value); ?>" <?php checked((string) $meta['show_layout'], $value); ?>> <?php echo esc_html($label); ?></label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<div class="aksrt-post-extension__field aksrt-post-extension__field--check"><div class="aksrt-post-extension__control"><label><input type="checkbox" name="aksrt_post_extension[no_article-navs]" value="1" <?php checked(!empty($meta['no_article-navs'])); ?>> <?php esc_html_e('不显示目录树', 'aksrt'); ?></label></div></div>
|
|
<div class="aksrt-post-extension__field aksrt-post-extension__field--check"><div class="aksrt-post-extension__control"><label><input type="checkbox" name="aksrt_post_extension[article_maxheight_xz]" value="1" <?php checked(!empty($meta['article_maxheight_xz'])); ?>> <?php esc_html_e('限制内容最大高度', 'aksrt'); ?></label></div></div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
private function render_upload_field(string $id, string $title, string $value, string $library, string $description, string $placeholder = ''): void {
|
|
?>
|
|
<div class="aksrt-post-extension__field">
|
|
<div class="aksrt-post-extension__title"><label for="aksrt-post-<?php echo esc_attr($id); ?>"><?php echo esc_html($title); ?></label></div>
|
|
<div class="aksrt-post-extension__control">
|
|
<div class="aksrt-post-extension__upload">
|
|
<input id="aksrt-post-<?php echo esc_attr($id); ?>" type="url" name="aksrt_post_extension[<?php echo esc_attr($id); ?>]" value="<?php echo esc_attr($value); ?>" placeholder="<?php echo esc_attr($placeholder); ?>" data-media-input>
|
|
<button type="button" class="button" data-media-select data-library="<?php echo esc_attr($library); ?>"><?php esc_html_e('选择', 'aksrt'); ?></button>
|
|
<button type="button" class="button-link-delete" data-media-clear><?php esc_html_e('移除', 'aksrt'); ?></button>
|
|
</div>
|
|
<p class="description"><?php echo wp_kses_post($description); ?></p>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
private function render_text_field(string $id, string $title, string $value): void {
|
|
?>
|
|
<div class="aksrt-post-extension__field"><div class="aksrt-post-extension__title"><label for="aksrt-post-<?php echo esc_attr($id); ?>"><?php echo esc_html($title); ?></label></div><div class="aksrt-post-extension__control"><input id="aksrt-post-<?php echo esc_attr($id); ?>" type="text" name="aksrt_post_extension[<?php echo esc_attr($id); ?>]" value="<?php echo esc_attr($value); ?>"></div></div>
|
|
<?php
|
|
}
|
|
|
|
private function render_number_field(string $id, string $title, int $value): void {
|
|
?>
|
|
<div class="aksrt-post-extension__field"><div class="aksrt-post-extension__title"><label for="aksrt-post-<?php echo esc_attr($id); ?>"><?php echo esc_html($title); ?></label></div><div class="aksrt-post-extension__control"><input id="aksrt-post-<?php echo esc_attr($id); ?>" type="number" min="0" step="1" name="aksrt_post_extension[<?php echo esc_attr($id); ?>]" value="<?php echo esc_attr((string) $value); ?>"></div></div>
|
|
<?php
|
|
}
|
|
|
|
/** @param int|string $index */
|
|
private function render_episode_row($index, array $episode): void {
|
|
?>
|
|
<div class="aksrt-post-extension__episode" data-episode-row>
|
|
<input type="text" name="aksrt_post_extension[featured_video_episode][<?php echo esc_attr((string) $index); ?>][title]" value="<?php echo esc_attr((string) ($episode['title'] ?? '')); ?>" placeholder="<?php esc_attr_e('剧集标题', 'aksrt'); ?>">
|
|
<div class="aksrt-post-extension__upload"><input type="url" name="aksrt_post_extension[featured_video_episode][<?php echo esc_attr((string) $index); ?>][url]" value="<?php echo esc_attr((string) ($episode['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('选择视频或填写视频地址', 'aksrt'); ?>" data-media-input><button type="button" class="button" data-media-select data-library="video"><?php esc_html_e('选择', 'aksrt'); ?></button></div>
|
|
<button type="button" class="button-link-delete" data-episode-remove><?php esc_html_e('移除', 'aksrt'); ?></button>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
public function render_custom_filter_meta_box(\WP_Post $post): void {
|
|
wp_nonce_field('aksrt_custom_filter_save', 'aksrt_custom_filter_nonce');
|
|
$settings = \AKSRT\Settings\Settings::get_post_filter_settings();
|
|
$dimensions = is_array($settings['custom_filter'] ?? null) ? $settings['custom_filter'] : [];
|
|
|
|
foreach ($dimensions as $dimension) {
|
|
$key = sanitize_key((string) ($dimension['key'] ?? ''));
|
|
$name = sanitize_text_field((string) ($dimension['name'] ?? $key));
|
|
$vals = is_array($dimension['vals'] ?? null) ? $dimension['vals'] : [];
|
|
if ($key === '') {
|
|
continue;
|
|
}
|
|
$current = (array) get_post_meta($post->ID, '_aksrt_cf_' . $key, true);
|
|
echo '<div class="aksrt-custom-filter-field"><strong>' . esc_html($name) . '</strong>';
|
|
foreach ($vals as $value) {
|
|
$value_key = sanitize_key((string) ($value['key'] ?? ''));
|
|
$value_name = sanitize_text_field((string) ($value['name'] ?? $value_key));
|
|
if ($value_key === '') {
|
|
continue;
|
|
}
|
|
printf('<label><input type="checkbox" name="aksrt_custom_filter[%s][]" value="%s"%s> %s</label>', esc_attr($key), esc_attr($value_key), checked(in_array($value_key, array_map('strval', $current), true), true, false), esc_html($value_name));
|
|
}
|
|
echo '</div>';
|
|
}
|
|
}
|
|
|
|
public function save_meta_box(int $post_id, \WP_Post $post): void {
|
|
if ($post->post_type !== 'post' || (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) || wp_is_post_revision($post_id) || !current_user_can('edit_post', $post_id)) {
|
|
return;
|
|
}
|
|
|
|
if (isset($_POST['aksrt_post_extension_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['aksrt_post_extension_nonce'])), 'aksrt_post_extension_save')) {
|
|
$raw = isset($_POST['aksrt_post_extension']) && is_array($_POST['aksrt_post_extension']) ? wp_unslash($_POST['aksrt_post_extension']) : [];
|
|
$layout = sanitize_key((string) ($raw['show_layout'] ?? 'false'));
|
|
if (!in_array($layout, ['false', 'no_sidebar', 'sidebar_left', 'sidebar_right'], true)) {
|
|
$layout = 'false';
|
|
}
|
|
$slide_ids = array_values(array_filter(array_map('absint', preg_split('/[\s,]+/', (string) ($raw['featured_slide'] ?? '')) ?: [])));
|
|
$meta = [
|
|
'thumbnail_url' => esc_url_raw((string) ($raw['thumbnail_url'] ?? '')),
|
|
'cover_image' => esc_url_raw((string) ($raw['cover_image'] ?? '')),
|
|
'featured_slide' => implode(',', array_slice($slide_ids, 0, 50)),
|
|
'featured_video' => esc_url_raw((string) ($raw['featured_video'] ?? '')),
|
|
'featured_video_title' => sanitize_text_field((string) ($raw['featured_video_title'] ?? '')),
|
|
'featured_video_episode' => self::sanitize_video_episodes($raw['featured_video_episode'] ?? []),
|
|
'subtitle' => sanitize_text_field((string) ($raw['subtitle'] ?? '')),
|
|
'show_layout' => $layout,
|
|
'no_article-navs' => !empty($raw['no_article-navs']),
|
|
'article_maxheight_xz' => !empty($raw['article_maxheight_xz']),
|
|
];
|
|
update_post_meta($post_id, self::META_KEY, $meta);
|
|
update_post_meta($post_id, DomainModel::POST_VIEW_COUNT_KEY, max(0, (int) ($raw['views'] ?? 0)));
|
|
update_post_meta($post_id, '_aksrt_like_count', max(0, (int) ($raw['like'] ?? 0)));
|
|
}
|
|
|
|
if (isset($_POST['aksrt_custom_filter_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['aksrt_custom_filter_nonce'])), 'aksrt_custom_filter_save')) {
|
|
$settings = \AKSRT\Settings\Settings::get_post_filter_settings();
|
|
$dimensions = is_array($settings['custom_filter'] ?? null) ? $settings['custom_filter'] : [];
|
|
$submitted = isset($_POST['aksrt_custom_filter']) && is_array($_POST['aksrt_custom_filter']) ? wp_unslash($_POST['aksrt_custom_filter']) : [];
|
|
foreach ($dimensions as $dimension) {
|
|
$key = sanitize_key((string) ($dimension['key'] ?? ''));
|
|
if ($key === '') {
|
|
continue;
|
|
}
|
|
$allowed = array_values(array_filter(array_map(static fn($value): string => sanitize_key((string) ($value['key'] ?? '')), (array) ($dimension['vals'] ?? []))));
|
|
$values = array_values(array_intersect($allowed, array_map('sanitize_key', (array) ($submitted[$key] ?? []))));
|
|
if ($values) {
|
|
update_post_meta($post_id, '_aksrt_cf_' . $key, $values);
|
|
} else {
|
|
delete_post_meta($post_id, '_aksrt_cf_' . $key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|