convert to plugins+theme
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* 归档扩展搜索:在新闻/公告归档搜索时,额外按作者显示名/登录名/昵称匹配。
|
||||
* 由主题模板通过查询变量 itstudio_archive_extend_search / itstudio_archive_keyword 触发。
|
||||
* 抽离自主题 functions.php。
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function itstudio_archive_extended_search_enabled($query) {
|
||||
if (!($query instanceof WP_Query)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$query->get('itstudio_archive_extend_search')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$keyword = trim((string) $query->get('itstudio_archive_keyword'));
|
||||
return $keyword !== '';
|
||||
}
|
||||
|
||||
function itstudio_archive_extended_search_join($join, $query) {
|
||||
if (!itstudio_archive_extended_search_enabled($query)) {
|
||||
return $join;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
if (strpos($join, $wpdb->users) !== false) {
|
||||
return $join;
|
||||
}
|
||||
|
||||
return $join . " LEFT JOIN {$wpdb->users} ON ({$wpdb->posts}.post_author = {$wpdb->users}.ID) ";
|
||||
}
|
||||
add_filter('posts_join', 'itstudio_archive_extended_search_join', 10, 2);
|
||||
|
||||
function itstudio_archive_extended_search_where($where, $query) {
|
||||
if (!itstudio_archive_extended_search_enabled($query)) {
|
||||
return $where;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$keyword = trim((string) $query->get('itstudio_archive_keyword'));
|
||||
$like = '%' . $wpdb->esc_like($keyword) . '%';
|
||||
|
||||
$search_sql = $wpdb->prepare(
|
||||
" AND (
|
||||
{$wpdb->posts}.post_title LIKE %s
|
||||
OR {$wpdb->posts}.post_excerpt LIKE %s
|
||||
OR {$wpdb->posts}.post_content LIKE %s
|
||||
OR {$wpdb->users}.display_name LIKE %s
|
||||
OR {$wpdb->users}.user_login LIKE %s
|
||||
OR {$wpdb->users}.user_nicename LIKE %s
|
||||
)",
|
||||
$like,
|
||||
$like,
|
||||
$like,
|
||||
$like,
|
||||
$like,
|
||||
$like
|
||||
);
|
||||
|
||||
return $where . $search_sql;
|
||||
}
|
||||
add_filter('posts_where', 'itstudio_archive_extended_search_where', 10, 2);
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* 新闻 / 公告文章类型与内容权重字段。
|
||||
* 抽离自主题 functions.php 的 itstudio_custom_post_types() 与 itstudio_register_acf_fields()。
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册新闻、公告文章类型,并把标签绑定到它们。
|
||||
* 便民服务(service / service_category)不在此插件,由 itstudio-services 负责。
|
||||
*/
|
||||
function itstudio_content_register_post_types() {
|
||||
register_post_type('announcement', array(
|
||||
'labels' => array(
|
||||
'name' => __('公告', 'itstudio'),
|
||||
'singular_name' => __('公告', 'itstudio'),
|
||||
),
|
||||
'public' => true,
|
||||
'has_archive' => true,
|
||||
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
|
||||
'taxonomies' => array('post_tag'),
|
||||
'menu_icon' => 'dashicons-megaphone',
|
||||
'show_in_rest' => true,
|
||||
));
|
||||
|
||||
register_post_type('news', array(
|
||||
'labels' => array(
|
||||
'name' => __('新闻', 'itstudio'),
|
||||
'singular_name' => __('新闻', 'itstudio'),
|
||||
),
|
||||
'public' => true,
|
||||
'has_archive' => 'news',
|
||||
'rewrite' => array(
|
||||
'slug' => 'news',
|
||||
'with_front' => false,
|
||||
),
|
||||
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
|
||||
'taxonomies' => array('post_tag'),
|
||||
'menu_icon' => 'dashicons-media-document',
|
||||
'show_in_rest' => true,
|
||||
));
|
||||
|
||||
register_taxonomy_for_object_type('post_tag', 'announcement');
|
||||
register_taxonomy_for_object_type('post_tag', 'news');
|
||||
}
|
||||
add_action('init', 'itstudio_content_register_post_types');
|
||||
|
||||
/**
|
||||
* 内容权重 ACF 字段组:数值越大文章在侧栏排序越靠前。
|
||||
* 招新文章标记(itstudio_is_recruitment_article)不在此插件,由 itstudio-recruitment 负责。
|
||||
*/
|
||||
function itstudio_content_register_acf_fields() {
|
||||
if (!function_exists('acf_add_local_field_group')) {
|
||||
return;
|
||||
}
|
||||
|
||||
acf_add_local_field_group(array(
|
||||
'key' => 'group_itstudio_content_priority',
|
||||
'title' => '内容权重',
|
||||
'fields' => array(
|
||||
array(
|
||||
'key' => 'field_itstudio_weight',
|
||||
'label' => '权重',
|
||||
'name' => 'itstudio_weight',
|
||||
'type' => 'number',
|
||||
'instructions' => '数值越大,文章在侧栏排序越靠前。',
|
||||
'required' => 0,
|
||||
'default_value' => 0,
|
||||
'min' => 0,
|
||||
'step' => 1,
|
||||
),
|
||||
),
|
||||
'location' => array(
|
||||
array(
|
||||
array(
|
||||
'param' => 'post_type',
|
||||
'operator' => '==',
|
||||
'value' => 'post',
|
||||
),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'param' => 'post_type',
|
||||
'operator' => '==',
|
||||
'value' => 'announcement',
|
||||
),
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'param' => 'post_type',
|
||||
'operator' => '==',
|
||||
'value' => 'news',
|
||||
),
|
||||
),
|
||||
),
|
||||
'position' => 'side',
|
||||
'style' => 'default',
|
||||
'active' => true,
|
||||
'show_in_rest' => 1,
|
||||
));
|
||||
}
|
||||
add_action('acf/init', 'itstudio_content_register_acf_fields');
|
||||
|
||||
/**
|
||||
* 新闻/公告在只有日期格式时补上时:分,提升列表时间精度。
|
||||
*/
|
||||
function itstudio_expand_news_notice_date_precision($the_date, $format, $post) {
|
||||
$post_obj = $post instanceof WP_Post ? $post : get_post($post);
|
||||
if (!($post_obj instanceof WP_Post)) {
|
||||
return $the_date;
|
||||
}
|
||||
|
||||
if (!in_array((string) $post_obj->post_type, array('announcement', 'news'), true)) {
|
||||
return $the_date;
|
||||
}
|
||||
|
||||
$format = (string) $format;
|
||||
if ($format === '') {
|
||||
$format = (string) get_option('date_format');
|
||||
}
|
||||
|
||||
$lower = strtolower($format);
|
||||
if (in_array($lower, array('c', 'r', 'u'), true)) {
|
||||
return $the_date;
|
||||
}
|
||||
|
||||
// Already has time precision.
|
||||
if (preg_match('/[HhGgis]/', $format)) {
|
||||
return $the_date;
|
||||
}
|
||||
|
||||
$timestamp = function_exists('get_post_timestamp')
|
||||
? get_post_timestamp($post_obj, 'date')
|
||||
: false;
|
||||
if (!$timestamp) {
|
||||
return $the_date;
|
||||
}
|
||||
|
||||
$format_with_time = rtrim($format) . ' H:i';
|
||||
return wp_date($format_with_time, (int) $timestamp, wp_timezone());
|
||||
}
|
||||
add_filter('get_the_date', 'itstudio_expand_news_notice_date_precision', 10, 3);
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* 文章浏览量统计(含防机器人与去重 cookie)。
|
||||
* 抽离自主题 functions.php。
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function itstudio_get_post_views($post_id) {
|
||||
$post_id = (int) $post_id;
|
||||
if ($post_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$meta_keys = array(
|
||||
'post_views_count',
|
||||
'itstudio_views',
|
||||
'views',
|
||||
'post_views',
|
||||
'view_count',
|
||||
'views_count',
|
||||
);
|
||||
|
||||
$max_views = 0;
|
||||
foreach ($meta_keys as $meta_key) {
|
||||
$raw = get_post_meta($post_id, $meta_key, true);
|
||||
if ($raw !== '' && is_numeric($raw)) {
|
||||
$max_views = max($max_views, (int) $raw);
|
||||
}
|
||||
}
|
||||
|
||||
return max(0, $max_views);
|
||||
}
|
||||
|
||||
function itstudio_is_probably_bot_request() {
|
||||
$user_agent = strtolower(trim((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')));
|
||||
if ($user_agent === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$bot_signatures = array(
|
||||
'bot',
|
||||
'spider',
|
||||
'crawl',
|
||||
'slurp',
|
||||
'headless',
|
||||
'preview',
|
||||
'facebookexternalhit',
|
||||
'discordbot',
|
||||
'telegrambot',
|
||||
'linkedinbot',
|
||||
'applebot',
|
||||
'googlebot',
|
||||
'bingbot',
|
||||
'wget',
|
||||
'curl',
|
||||
'python-requests',
|
||||
'postmanruntime',
|
||||
);
|
||||
|
||||
foreach ($bot_signatures as $signature) {
|
||||
if (strpos($user_agent, $signature) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$purpose = strtolower((string) ($_SERVER['HTTP_PURPOSE'] ?? ''));
|
||||
$sec_purpose = strtolower((string) ($_SERVER['HTTP_SEC_PURPOSE'] ?? ''));
|
||||
$x_moz = strtolower((string) ($_SERVER['HTTP_X_MOZ'] ?? ''));
|
||||
if (strpos($purpose, 'prefetch') !== false || strpos($sec_purpose, 'prefetch') !== false || $x_moz === 'prefetch') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function itstudio_get_view_cookie_name() {
|
||||
return 'itstudio_viewed_posts';
|
||||
}
|
||||
|
||||
function itstudio_read_view_cookie_map() {
|
||||
$cookie_name = itstudio_get_view_cookie_name();
|
||||
$raw_cookie = isset($_COOKIE[$cookie_name]) ? wp_unslash((string) $_COOKIE[$cookie_name]) : '';
|
||||
if ($raw_cookie === '') {
|
||||
return array();
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw_cookie, true);
|
||||
if (!is_array($decoded)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$clean = array();
|
||||
foreach ($decoded as $post_id => $timestamp) {
|
||||
$post_id = (int) $post_id;
|
||||
$timestamp = (int) $timestamp;
|
||||
if ($post_id > 0 && $timestamp > 0) {
|
||||
$clean[(string) $post_id] = $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
function itstudio_write_view_cookie_map($map, $window_seconds) {
|
||||
if (!headers_sent()) {
|
||||
$cookie_name = itstudio_get_view_cookie_name();
|
||||
$expire_at = time() + max(DAY_IN_SECONDS, (int) $window_seconds * 2);
|
||||
$path = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/';
|
||||
$domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '';
|
||||
$secure = is_ssl();
|
||||
$http_only = true;
|
||||
$encoded = wp_json_encode($map);
|
||||
if (!is_string($encoded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID >= 70300) {
|
||||
setcookie($cookie_name, $encoded, array(
|
||||
'expires' => $expire_at,
|
||||
'path' => $path,
|
||||
'domain' => $domain,
|
||||
'secure' => $secure,
|
||||
'httponly' => $http_only,
|
||||
'samesite' => 'Lax',
|
||||
));
|
||||
} else {
|
||||
setcookie($cookie_name, $encoded, $expire_at, $path, $domain, $secure, $http_only);
|
||||
}
|
||||
|
||||
$_COOKIE[$cookie_name] = $encoded;
|
||||
}
|
||||
}
|
||||
|
||||
function itstudio_should_count_post_view($post_id) {
|
||||
$post_id = (int) $post_id;
|
||||
if ($post_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itstudio_is_probably_bot_request()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$window_seconds = (int) apply_filters('itstudio_post_view_window_seconds', 12 * HOUR_IN_SECONDS);
|
||||
$window_seconds = max(60, $window_seconds);
|
||||
$max_entries = (int) apply_filters('itstudio_post_view_cookie_max_entries', 120);
|
||||
$max_entries = max(10, $max_entries);
|
||||
|
||||
$now = time();
|
||||
$key = (string) $post_id;
|
||||
$map = itstudio_read_view_cookie_map();
|
||||
|
||||
foreach ($map as $id => $timestamp) {
|
||||
if (($now - (int) $timestamp) > $window_seconds) {
|
||||
unset($map[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($map[$key]) && ($now - (int) $map[$key]) < $window_seconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$map[$key] = $now;
|
||||
if (count($map) > $max_entries) {
|
||||
asort($map, SORT_NUMERIC);
|
||||
$map = array_slice($map, -$max_entries, null, true);
|
||||
}
|
||||
|
||||
itstudio_write_view_cookie_map($map, $window_seconds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function itstudio_track_post_views() {
|
||||
if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_singular(array('post', 'announcement', 'news')) || is_preview() || is_feed() || is_trackback() || is_embed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($_SERVER['REQUEST_METHOD']) || strtoupper((string) $_SERVER['REQUEST_METHOD']) !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = (int) get_queried_object_id();
|
||||
if ($post_id <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!itstudio_should_count_post_view($post_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$views = itstudio_get_post_views($post_id) + 1;
|
||||
update_post_meta($post_id, 'post_views_count', $views);
|
||||
update_post_meta($post_id, 'itstudio_views', $views);
|
||||
}
|
||||
add_action('template_redirect', 'itstudio_track_post_views', 20);
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* 供主题模板调用的内容助手函数(权重、字数、摘要、归档 URL)。
|
||||
* 函数名保持与主题历史一致,模板正在按原名调用它们。
|
||||
* 抽离自主题 functions.php。
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function itstudio_get_post_weight($post_id, $field_name = 'itstudio_weight') {
|
||||
$post_id = (int) $post_id;
|
||||
if ($post_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!function_exists('get_field')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$raw = get_field($field_name, $post_id, false);
|
||||
if ($raw === '' || $raw === null || !is_numeric($raw)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) $raw;
|
||||
}
|
||||
|
||||
function itstudio_get_post_char_count($post_id) {
|
||||
$post_id = (int) $post_id;
|
||||
if ($post_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$content = (string) get_post_field('post_content', $post_id);
|
||||
$plain_text = trim(wp_strip_all_tags($content));
|
||||
if ($plain_text === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (function_exists('mb_strlen')) {
|
||||
return (int) mb_strlen($plain_text, 'UTF-8');
|
||||
}
|
||||
|
||||
return (int) strlen($plain_text);
|
||||
}
|
||||
|
||||
function itstudio_get_post_excerpt_chars($post_id, $limit = 200) {
|
||||
$post_id = (int) $post_id;
|
||||
$limit = max(1, (int) $limit);
|
||||
if ($post_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$excerpt = (string) get_post_field('post_excerpt', $post_id);
|
||||
if (trim($excerpt) === '') {
|
||||
$excerpt = (string) get_post_field('post_content', $post_id);
|
||||
}
|
||||
|
||||
$excerpt = trim(wp_strip_all_tags($excerpt));
|
||||
if ($excerpt === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return wp_html_excerpt($excerpt, $limit, '...');
|
||||
}
|
||||
|
||||
function itstudio_get_archive_url_by_post_type($post_type) {
|
||||
$post_type = sanitize_key((string) $post_type);
|
||||
if ($post_type === '') {
|
||||
return home_url('/');
|
||||
}
|
||||
|
||||
$archive_url = get_post_type_archive_link($post_type);
|
||||
if ($archive_url) {
|
||||
return $archive_url;
|
||||
}
|
||||
|
||||
if ($post_type === 'news') {
|
||||
return home_url('/news/');
|
||||
}
|
||||
|
||||
if ($post_type === 'announcement') {
|
||||
return home_url('/announcement/');
|
||||
}
|
||||
|
||||
return home_url('/');
|
||||
}
|
||||
|
||||
function itstudio_get_archive_tag_filter_url($term, $post_type) {
|
||||
$term_obj = null;
|
||||
if ($term instanceof WP_Term) {
|
||||
$term_obj = $term;
|
||||
} else {
|
||||
$term_obj = get_term($term, 'post_tag');
|
||||
}
|
||||
|
||||
if (!($term_obj instanceof WP_Term) || is_wp_error($term_obj)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$post_type = sanitize_key((string) $post_type);
|
||||
if (!in_array($post_type, array('news', 'announcement'), true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$archive_url = itstudio_get_archive_url_by_post_type($post_type);
|
||||
return add_query_arg(
|
||||
array(
|
||||
'tag' => $term_obj->slug,
|
||||
),
|
||||
$archive_url
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: ITStudio 核心内容
|
||||
* Plugin URI: https://git.itouc.cn/ITStudio_OUC/ITStudioMainSite
|
||||
* Description: ITStudio 主站的核心内容类型与内容行为:新闻/公告文章类型、内容权重字段、浏览量统计、归档扩展搜索与内容助手函数。是招新等其它插件的地基。
|
||||
* Version: 1.0.0
|
||||
* Author: ITStudio OUC
|
||||
* Text Domain: itstudio-content
|
||||
*
|
||||
* 说明:本插件从 ITStudioMainSite 主题的 functions.php 抽离而来。
|
||||
* 迁移铁律:文章类型 slug(news / announcement)、ACF 字段名(itstudio_weight)
|
||||
* 等标识符必须与主题历史保持一字不差,否则数据库中的老数据会失联。
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define('ITSTUDIO_CONTENT_VERSION', '1.0.0');
|
||||
define('ITSTUDIO_CONTENT_PATH', plugin_dir_path(__FILE__));
|
||||
|
||||
require_once ITSTUDIO_CONTENT_PATH . 'includes/post-types.php';
|
||||
require_once ITSTUDIO_CONTENT_PATH . 'includes/post-views.php';
|
||||
require_once ITSTUDIO_CONTENT_PATH . 'includes/archive-search.php';
|
||||
require_once ITSTUDIO_CONTENT_PATH . 'includes/template-helpers.php';
|
||||
|
||||
/**
|
||||
* 激活时注册文章类型后刷新重写规则,保证 /news/ 等固定链接立即生效。
|
||||
*/
|
||||
function itstudio_content_activate() {
|
||||
itstudio_content_register_post_types();
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
register_activation_hook(__FILE__, 'itstudio_content_activate');
|
||||
|
||||
/**
|
||||
* 停用时同样刷新重写规则,清掉本插件注册的重写。
|
||||
*/
|
||||
function itstudio_content_deactivate() {
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
register_deactivation_hook(__FILE__, 'itstudio_content_deactivate');
|
||||
Reference in New Issue
Block a user