convert to plugins+theme

This commit is contained in:
OldTaoge
2026-07-21 09:55:39 +08:00
parent 803841cd12
commit aea6095c0a
57 changed files with 2742 additions and 1619 deletions
@@ -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');
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,295 @@
<?php
/**
* 招新文章标记:ACF “招新文章标记” 字段组、is_recruitment_article 判定、
* 编辑页 meta box、以及“加入我们”页顶部招新新闻 feed。
* 抽离自主题 functions.php(lift-and-shift,逻辑未改)。
*/
if (!defined('ABSPATH')) {
exit;
}
function itstudio_register_acf_fields() {
if (!function_exists('acf_add_local_field_group')) {
return;
}
acf_add_local_field_group(array(
'key' => 'group_itstudio_recruitment_article',
'title' => '招新文章标记',
'fields' => array(
array(
'key' => 'field_itstudio_is_recruitment_article',
'label' => '是否为招新文章',
'name' => 'itstudio_is_recruitment_article',
'type' => 'true_false',
'instructions' => '勾选后,该文章会在“加入我们”页面顶部新闻条中显示(按发布时间排序)。',
'required' => 0,
'default_value' => 0,
'ui' => 1,
'ui_on_text' => '是',
'ui_off_text' => '否',
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'announcement',
),
),
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'news',
),
),
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'post',
),
),
),
'position' => 'side',
'style' => 'default',
'active' => true,
'show_in_rest' => 1,
));
}
add_action('acf/init', 'itstudio_register_acf_fields');
function itstudio_get_recruitment_article_meta_key() {
return 'itstudio_is_recruitment_article';
}
function itstudio_normalize_recruitment_flag($value) {
if (is_bool($value)) {
return $value;
}
if (is_numeric($value)) {
return ((int) $value) > 0;
}
$value = strtolower(trim((string) $value));
return in_array($value, array('1', 'true', 'yes', 'on', 'y'), true);
}
function itstudio_is_recruitment_article($post_id) {
$post_id = (int) $post_id;
if ($post_id <= 0) {
return false;
}
$meta_key = itstudio_get_recruitment_article_meta_key();
$meta_keys = array(
$meta_key,
'_itstudio_is_recruitment_article',
'itstudio_join_article',
'join_recruitment_article',
'is_recruitment_article',
);
foreach ($meta_keys as $key) {
$raw = get_post_meta($post_id, $key, true);
if ($raw !== '' && $raw !== null && itstudio_normalize_recruitment_flag($raw)) {
return true;
}
}
if (function_exists('get_field')) {
$acf_keys = array(
'itstudio_is_recruitment_article',
'is_recruitment_article',
'itstudio_join_article',
);
foreach ($acf_keys as $acf_key) {
$raw = get_field($acf_key, $post_id, false);
if ($raw !== '' && $raw !== null && itstudio_normalize_recruitment_flag($raw)) {
return true;
}
}
}
return false;
}
function itstudio_add_recruitment_meta_boxes() {
$screens = array('announcement', 'news', 'post');
foreach ($screens as $screen) {
add_meta_box(
'itstudio_recruitment_flag',
__('招新文章', 'itstudio'),
'itstudio_render_recruitment_meta_box',
$screen,
'side',
'high'
);
}
}
add_action('add_meta_boxes', 'itstudio_add_recruitment_meta_boxes');
function itstudio_render_recruitment_meta_box($post) {
$meta_key = itstudio_get_recruitment_article_meta_key();
$checked = itstudio_is_recruitment_article((int) $post->ID);
wp_nonce_field('itstudio_save_recruitment_meta', 'itstudio_recruitment_meta_nonce');
?>
<p>
<label for="itstudio_is_recruitment_article">
<input
type="checkbox"
id="itstudio_is_recruitment_article"
name="itstudio_is_recruitment_article"
value="1"
<?php checked($checked); ?>
>
<?php esc_html_e('在加入我们页面显示为招新新闻', 'itstudio'); ?>
</label>
</p>
<p style="color:#666;line-height:1.5;margin:0;">
<?php esc_html_e('仅显示当年且已发布的招新文章,最多展示 5 篇。', 'itstudio'); ?>
</p>
<input type="hidden" name="itstudio_recruitment_meta_key" value="<?php echo esc_attr($meta_key); ?>">
<?php
}
function itstudio_save_recruitment_meta_box($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
if (!isset($_POST['itstudio_recruitment_meta_nonce'])) {
return;
}
$nonce = sanitize_text_field(wp_unslash($_POST['itstudio_recruitment_meta_nonce']));
if (!wp_verify_nonce($nonce, 'itstudio_save_recruitment_meta')) {
return;
}
$post_type = get_post_type($post_id);
if (!in_array($post_type, array('announcement', 'news', 'post'), true)) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
$meta_key = itstudio_get_recruitment_article_meta_key();
$checked = isset($_POST['itstudio_is_recruitment_article'])
? sanitize_text_field(wp_unslash($_POST['itstudio_is_recruitment_article']))
: '';
if (itstudio_normalize_recruitment_flag($checked)) {
update_post_meta($post_id, $meta_key, '1');
} else {
delete_post_meta($post_id, $meta_key);
}
}
add_action('save_post', 'itstudio_save_recruitment_meta_box');
function itstudio_join_get_recruitment_feed_items($join_runtime = array(), $limit = 5) {
$limit = max(1, min(10, (int) $limit));
$join_runtime = is_array($join_runtime) ? $join_runtime : array();
$now = new DateTimeImmutable('now', wp_timezone());
$now_year = (int) $now->format('Y');
$display_year = isset($join_runtime['recruitment_year']) && is_numeric($join_runtime['recruitment_year'])
? (int) $join_runtime['recruitment_year']
: $now_year;
$season_start_ts = null;
$season_end_ts = null;
$stages = isset($join_runtime['stages']) && is_array($join_runtime['stages']) ? $join_runtime['stages'] : array();
foreach ($stages as $stage) {
if (!is_array($stage)) {
continue;
}
$start_ts = isset($stage['start_ts']) && is_numeric($stage['start_ts']) ? (int) $stage['start_ts'] : null;
$end_ts = isset($stage['end_ts']) && is_numeric($stage['end_ts']) ? (int) $stage['end_ts'] : null;
if ($start_ts !== null && ($season_start_ts === null || $start_ts < $season_start_ts)) {
$season_start_ts = $start_ts;
}
if ($end_ts !== null && ($season_end_ts === null || $end_ts > $season_end_ts)) {
$season_end_ts = $end_ts;
}
}
$now_ts = (int) (isset($join_runtime['now_ts']) && is_numeric($join_runtime['now_ts']) ? $join_runtime['now_ts'] : ((int) $now->format('U') * 1000));
if ($season_start_ts !== null && $now_ts < $season_start_ts && $display_year > $now_year) {
// 下年招新未开始:继续展示本年招新资讯
$display_year = $now_year;
}
$query = new WP_Query(array(
'post_type' => array('announcement', 'news', 'post'),
'post_status' => 'publish',
'posts_per_page' => 80,
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true,
'ignore_sticky_posts' => true,
'date_query' => array(
array(
'year' => $display_year,
),
),
));
$items = array();
if ($query->have_posts()) {
foreach ($query->posts as $post) {
if (count($items) >= $limit) {
break;
}
$post_id = (int) $post->ID;
if (!itstudio_is_recruitment_article($post_id)) {
continue;
}
$title = trim((string) get_the_title($post_id));
$url = (string) get_permalink($post_id);
if ($title === '' || $url === '') {
continue;
}
$excerpt = function_exists('itstudio_get_post_excerpt_chars')
? itstudio_get_post_excerpt_chars($post_id, 72)
: wp_html_excerpt(trim(wp_strip_all_tags((string) get_post_field('post_excerpt', $post_id))), 72, '...');
if ($excerpt === '') {
$excerpt = '...';
}
$author_id = (int) get_post_field('post_author', $post_id);
$author = trim((string) get_the_author_meta('display_name', $author_id));
if ($author === '') {
$author = 'Unknown';
}
$items[] = array(
'id' => $post_id,
'title' => $title,
'excerpt' => $excerpt,
'url' => $url,
'date' => get_the_date('Y-m-d H:i', $post_id),
'date_iso' => get_the_date('c', $post_id),
'author' => $author,
'type' => get_post_type($post_id),
);
}
}
wp_reset_postdata();
return array(
'display_year' => $display_year,
'items' => $items,
);
}
@@ -0,0 +1,261 @@
<?php
/**
* 招新“学期”数据层:把原本单一的 itstudio_join_settings 配置扩展为可存档的多学期集合。
*
* 存储:
* option itstudio_join_terms —— array,键为学期 id,值为 { id, academic_year, season, label, created, settings }
* option itstudio_join_active_term_id —— string,当前生效学期的 id
* option itstudio_join_terms_migrated —— 迁移完成标记
*
* 兼容:旧的 itstudio_join_settings 选项保留,作为迁移来源与兜底。
* 迁移铁律沿用:各字段/选项标识不变,老数据不丢。
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* 可选的季节及其中文名。
*/
function itstudio_join_get_seasons() {
return array(
'fall' => '秋季',
'spring' => '春季',
);
}
/**
* 读取全部学期(id => term 的关联数组)。
*/
function itstudio_join_get_terms() {
$terms = get_option('itstudio_join_terms', array());
return is_array($terms) ? $terms : array();
}
/**
* 当前生效学期 id。
*/
function itstudio_join_get_active_term_id() {
$id = (string) get_option('itstudio_join_active_term_id', '');
if ($id !== '') {
$terms = itstudio_join_get_terms();
if (isset($terms[$id])) {
return $id;
}
}
// 生效指针失效时,回退到第一个存在的学期。
$terms = itstudio_join_get_terms();
if (!empty($terms)) {
return (string) array_key_first($terms);
}
return '';
}
/**
* 取单个学期结构(不存在返回 null)。
*/
function itstudio_join_get_term($term_id) {
$term_id = (string) $term_id;
$terms = itstudio_join_get_terms();
return isset($terms[$term_id]) ? $terms[$term_id] : null;
}
/**
* 学期的展示名:优先自定义 label,否则“学年 + 季节招新”。
*/
function itstudio_join_term_label($term) {
if (!is_array($term)) {
return '';
}
$custom = trim((string) ($term['label'] ?? ''));
if ($custom !== '') {
return $custom;
}
$seasons = itstudio_join_get_seasons();
$season_cn = $seasons[$term['season'] ?? ''] ?? '';
$year = trim((string) ($term['academic_year'] ?? ''));
$label = trim($year . ' ' . $season_cn . '招新');
return $label !== '招新' ? $label : '未命名招新期';
}
/**
* 由学年 + 季节生成稳定且唯一的学期 id(冲突时追加 -2、-3…)。
*/
function itstudio_join_make_term_id($academic_year, $season, array $existing = array()) {
$base = sanitize_title($academic_year . '-' . $season);
if ($base === '') {
$base = 'term';
}
$id = $base;
$suffix = 2;
while (isset($existing[$id])) {
$id = $base . '-' . $suffix;
$suffix++;
}
return $id;
}
/**
* 新建学期。$settings 省略时用默认空配置。返回新学期 id。
*/
function itstudio_join_create_term($academic_year, $season, $label = '', $settings = null) {
$terms = itstudio_join_get_terms();
$season = array_key_exists($season, itstudio_join_get_seasons()) ? $season : 'fall';
$academic_year = itstudio_join_sanitize_shortcode_value($academic_year);
$label = itstudio_join_sanitize_shortcode_value($label);
$id = itstudio_join_make_term_id($academic_year, $season, $terms);
$terms[$id] = array(
'id' => $id,
'academic_year' => $academic_year,
'season' => $season,
'label' => $label,
'created' => current_time('mysql'),
'settings' => is_array($settings)
? itstudio_join_sanitize_settings($settings)
: itstudio_join_get_default_settings(),
);
update_option('itstudio_join_terms', $terms);
// 若尚无“显式”生效学期(读原始选项,不走会兜底的 getter),新建的即为生效。
$active_raw = (string) get_option('itstudio_join_active_term_id', '');
if ($active_raw === '' || !isset($terms[$active_raw])) {
itstudio_join_set_active_term($id);
}
return $id;
}
/**
* 更新某学期的元信息(学年/季节/自定义名)。
*/
function itstudio_join_update_term_meta($term_id, $academic_year, $season, $label = '') {
$terms = itstudio_join_get_terms();
$term_id = (string) $term_id;
if (!isset($terms[$term_id])) {
return false;
}
$terms[$term_id]['academic_year'] = itstudio_join_sanitize_shortcode_value($academic_year);
$terms[$term_id]['season'] = array_key_exists($season, itstudio_join_get_seasons()) ? $season : 'fall';
$terms[$term_id]['label'] = itstudio_join_sanitize_shortcode_value($label);
update_option('itstudio_join_terms', $terms);
return true;
}
/**
* 把一份配置写入指定学期。
*/
function itstudio_join_save_term_settings($term_id, $raw_settings) {
$terms = itstudio_join_get_terms();
$term_id = (string) $term_id;
if (!isset($terms[$term_id])) {
return false;
}
$terms[$term_id]['settings'] = itstudio_join_sanitize_settings($raw_settings);
update_option('itstudio_join_terms', $terms);
return true;
}
/**
* 设为当前生效学期。
*/
function itstudio_join_set_active_term($term_id) {
$term_id = (string) $term_id;
$terms = itstudio_join_get_terms();
if (!isset($terms[$term_id])) {
return false;
}
update_option('itstudio_join_active_term_id', $term_id);
return true;
}
/**
* 删除学期。不允许删“当前生效”或最后一个。
*/
function itstudio_join_delete_term($term_id) {
$term_id = (string) $term_id;
$terms = itstudio_join_get_terms();
if (!isset($terms[$term_id])) {
return false;
}
if (count($terms) <= 1) {
return false;
}
if (itstudio_join_get_active_term_id() === $term_id) {
return false;
}
unset($terms[$term_id]);
update_option('itstudio_join_terms', $terms);
return true;
}
/**
* 由旧的单一配置推断学年与季节(报名开始月份 >= 7 视为秋季)。
*/
function itstudio_join_guess_term_from_settings($settings) {
$settings = is_array($settings) ? $settings : array();
$anchor = '';
foreach (array('registration_start', 'notice_start_date', 'first_interview_date') as $key) {
if (!empty($settings[$key])) {
$anchor = (string) $settings[$key];
break;
}
}
if ($anchor === '' || !preg_match('/(\d{4})-(\d{2})/', $anchor, $m)) {
return array('academic_year' => '', 'season' => 'fall');
}
$year = (int) $m[1];
$month = (int) $m[2];
if ($month >= 7) {
return array('academic_year' => $year . '-' . ($year + 1), 'season' => 'fall');
}
return array('academic_year' => ($year - 1) . '-' . $year, 'season' => 'spring');
}
/**
* 一次性迁移:把旧的单一 itstudio_join_settings 归入首个学期并设为生效。
* 前后台每个请求都会触发,但有标记,已迁移则立即返回。
*/
function itstudio_join_maybe_migrate_terms() {
if (get_option('itstudio_join_terms_migrated')) {
return;
}
$terms = itstudio_join_get_terms();
if (empty($terms)) {
$legacy = get_option('itstudio_join_settings', array());
$legacy = is_array($legacy) ? $legacy : array();
if (!empty($legacy)) {
$guess = itstudio_join_guess_term_from_settings($legacy);
$label = $guess['academic_year'] === '' ? '初始招新期' : '';
itstudio_join_create_term($guess['academic_year'], $guess['season'], $label, $legacy);
} else {
// 全新安装:建一个空的当前学期占位。
$now_year = (int) current_time('Y');
itstudio_join_create_term($now_year . '-' . ($now_year + 1), 'fall', '', null);
}
}
update_option('itstudio_join_terms_migrated', 1);
}
add_action('init', 'itstudio_join_maybe_migrate_terms', 1);
@@ -0,0 +1,36 @@
<?php
/**
* Plugin Name: ITStudio 招新系统
* Plugin URI: https://git.itouc.cn/ITStudio_OUC/ITStudioMainSite
* Description: ITStudio “加入我们”招新系统:招新文章标记、招新阶段时间状态机、录取进度查询(AJAX)、CSV/XLSX 结果解析、Formidable Forms 对接、以及后台“招新设置”页面。
* Version: 1.0.0
* Author: ITStudio OUC
* Text Domain: itstudio-recruitment
* Requires Plugins: itstudio-content
*
* 说明:本插件从 ITStudioMainSite 主题的 functions.php 抽离而来。
* 依赖 itstudio-content(新闻/公告文章类型),并在运行时软依赖 ACF 与 Formidable Forms。
* 迁移铁律:选项名 itstudio_join_settings、meta/ACF 字段 itstudio_is_recruitment_article、
* AJAX action itstudio_join_progress_lookup 必须与主题历史保持一字不差,否则老数据/前端脚本失联。
*/
if (!defined('ABSPATH')) {
exit;
}
define('ITSTUDIO_RECRUITMENT_VERSION', '1.0.0');
define('ITSTUDIO_RECRUITMENT_PATH', plugin_dir_path(__FILE__));
require_once ITSTUDIO_RECRUITMENT_PATH . 'includes/recruitment-articles.php';
require_once ITSTUDIO_RECRUITMENT_PATH . 'includes/join-system.php';
require_once ITSTUDIO_RECRUITMENT_PATH . 'includes/terms.php';
function itstudio_recruitment_activate() {
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'itstudio_recruitment_activate');
function itstudio_recruitment_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'itstudio_recruitment_deactivate');
@@ -0,0 +1,280 @@
<?php
/**
* 便民服务后台:双语与链接 meta box、服务分类的双语字段、以及列表页自定义列。
* 抽离自主题 functions.php。
*/
if (!defined('ABSPATH')) {
exit;
}
function itstudio_add_service_meta_boxes() {
add_meta_box(
'itstudio_service_link',
__('服务双语与链接', 'itstudio'),
'itstudio_render_service_link_meta_box',
'service',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'itstudio_add_service_meta_boxes');
function itstudio_render_service_link_meta_box($post) {
$service_url = (string) get_post_meta($post->ID, itstudio_get_service_url_meta_key(), true);
$title_cn = (string) get_post_meta($post->ID, itstudio_get_service_title_cn_meta_key(), true);
$title_en = (string) get_post_meta($post->ID, itstudio_get_service_title_en_meta_key(), true);
$excerpt_cn = (string) get_post_meta($post->ID, itstudio_get_service_excerpt_cn_meta_key(), true);
$excerpt_en = (string) get_post_meta($post->ID, itstudio_get_service_excerpt_en_meta_key(), true);
$is_campus_only = itstudio_is_service_campus_only($post->ID);
wp_nonce_field('itstudio_save_service_meta', 'itstudio_service_meta_nonce');
?>
<p>
<label for="itstudio_service_title_cn"><strong><?php esc_html_e('中文名称', 'itstudio'); ?></strong></label>
</p>
<p>
<input
type="text"
id="itstudio_service_title_cn"
name="itstudio_service_title_cn"
class="widefat"
placeholder="<?php echo esc_attr((string) get_the_title($post)); ?>"
value="<?php echo esc_attr($title_cn); ?>"
>
</p>
<p>
<label for="itstudio_service_title_en"><strong><?php esc_html_e('英文名称', 'itstudio'); ?></strong></label>
</p>
<p>
<input
type="text"
id="itstudio_service_title_en"
name="itstudio_service_title_en"
class="widefat"
placeholder="Service Name"
value="<?php echo esc_attr($title_en); ?>"
>
</p>
<p>
<label for="itstudio_service_excerpt_cn"><strong><?php esc_html_e('中文简介', 'itstudio'); ?></strong></label>
</p>
<p>
<textarea
id="itstudio_service_excerpt_cn"
name="itstudio_service_excerpt_cn"
class="widefat"
rows="3"
placeholder="<?php echo esc_attr((string) get_the_excerpt($post)); ?>"
><?php echo esc_textarea($excerpt_cn); ?></textarea>
</p>
<p>
<label for="itstudio_service_excerpt_en"><strong><?php esc_html_e('英文简介', 'itstudio'); ?></strong></label>
</p>
<p>
<textarea
id="itstudio_service_excerpt_en"
name="itstudio_service_excerpt_en"
class="widefat"
rows="3"
placeholder="Short English description"
><?php echo esc_textarea($excerpt_en); ?></textarea>
</p>
<p>
<label for="itstudio_service_url"><strong><?php esc_html_e('目标链接', 'itstudio'); ?></strong></label>
</p>
<p>
<input
type="url"
id="itstudio_service_url"
name="itstudio_service_url"
class="widefat"
placeholder="https://example.com"
value="<?php echo esc_attr($service_url); ?>"
>
</p>
<p class="description">
<?php esc_html_e('支持分别填写中英文名称与简介;未填写时会回退到标题/摘要。', 'itstudio'); ?>
</p>
<p>
<label for="itstudio_service_campus_only">
<input
type="checkbox"
id="itstudio_service_campus_only"
name="itstudio_service_campus_only"
value="1"
<?php checked($is_campus_only); ?>
>
<?php esc_html_e('是否为校内服务(勾选后前台显示“仅校内访问”)', 'itstudio'); ?>
</label>
</p>
<?php
}
function itstudio_save_service_meta($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!isset($_POST['itstudio_service_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['itstudio_service_meta_nonce'])), 'itstudio_save_service_meta')) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
if (get_post_type($post_id) !== 'service') {
return;
}
$title_cn = isset($_POST['itstudio_service_title_cn']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_title_cn'])) : '';
$title_en = isset($_POST['itstudio_service_title_en']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_title_en'])) : '';
$excerpt_cn = isset($_POST['itstudio_service_excerpt_cn']) ? sanitize_textarea_field(wp_unslash($_POST['itstudio_service_excerpt_cn'])) : '';
$excerpt_en = isset($_POST['itstudio_service_excerpt_en']) ? sanitize_textarea_field(wp_unslash($_POST['itstudio_service_excerpt_en'])) : '';
$meta_updates = array(
itstudio_get_service_title_cn_meta_key() => trim($title_cn),
itstudio_get_service_title_en_meta_key() => trim($title_en),
itstudio_get_service_excerpt_cn_meta_key() => trim($excerpt_cn),
itstudio_get_service_excerpt_en_meta_key() => trim($excerpt_en),
);
foreach ($meta_updates as $meta_key => $value) {
if ($value === '') {
delete_post_meta($post_id, $meta_key);
} else {
update_post_meta($post_id, $meta_key, $value);
}
}
$campus_only_meta_key = itstudio_get_service_campus_only_meta_key();
$is_campus_only = isset($_POST['itstudio_service_campus_only']) && (string) wp_unslash($_POST['itstudio_service_campus_only']) === '1';
if ($is_campus_only) {
update_post_meta($post_id, $campus_only_meta_key, '1');
} else {
delete_post_meta($post_id, $campus_only_meta_key);
}
$url_meta_key = itstudio_get_service_url_meta_key();
$raw_url = isset($_POST['itstudio_service_url']) ? trim((string) wp_unslash($_POST['itstudio_service_url'])) : '';
if ($raw_url === '') {
delete_post_meta($post_id, $url_meta_key);
return;
}
if (strpos($raw_url, '/') === 0) {
$raw_url = home_url($raw_url);
}
$service_url = esc_url_raw($raw_url);
if ($service_url === '') {
delete_post_meta($post_id, $url_meta_key);
return;
}
update_post_meta($post_id, $url_meta_key, $service_url);
}
add_action('save_post_service', 'itstudio_save_service_meta');
function itstudio_service_category_add_fields($taxonomy) {
wp_nonce_field('itstudio_save_service_category_meta', 'itstudio_service_category_meta_nonce');
?>
<div class="form-field term-itstudio-service-name-cn-wrap">
<label for="itstudio_service_cat_name_cn"><?php esc_html_e('中文名称', 'itstudio'); ?></label>
<input type="text" name="itstudio_service_cat_name_cn" id="itstudio_service_cat_name_cn" value="">
</div>
<div class="form-field term-itstudio-service-name-en-wrap">
<label for="itstudio_service_cat_name_en"><?php esc_html_e('英文名称', 'itstudio'); ?></label>
<input type="text" name="itstudio_service_cat_name_en" id="itstudio_service_cat_name_en" value="" placeholder="Category Name">
</div>
<?php
}
add_action('service_category_add_form_fields', 'itstudio_service_category_add_fields');
function itstudio_service_category_edit_fields($term) {
$term_id = (int) $term->term_id;
$name_cn = (string) get_term_meta($term_id, itstudio_get_service_category_name_cn_meta_key(), true);
$name_en = (string) get_term_meta($term_id, itstudio_get_service_category_name_en_meta_key(), true);
wp_nonce_field('itstudio_save_service_category_meta', 'itstudio_service_category_meta_nonce');
?>
<tr class="form-field term-itstudio-service-name-cn-wrap">
<th scope="row"><label for="itstudio_service_cat_name_cn"><?php esc_html_e('中文名称', 'itstudio'); ?></label></th>
<td><input type="text" name="itstudio_service_cat_name_cn" id="itstudio_service_cat_name_cn" value="<?php echo esc_attr($name_cn); ?>"></td>
</tr>
<tr class="form-field term-itstudio-service-name-en-wrap">
<th scope="row"><label for="itstudio_service_cat_name_en"><?php esc_html_e('英文名称', 'itstudio'); ?></label></th>
<td><input type="text" name="itstudio_service_cat_name_en" id="itstudio_service_cat_name_en" value="<?php echo esc_attr($name_en); ?>" placeholder="Category Name"></td>
</tr>
<?php
}
add_action('service_category_edit_form_fields', 'itstudio_service_category_edit_fields');
function itstudio_save_service_category_meta($term_id) {
if (!current_user_can('manage_categories')) {
return;
}
if (!isset($_POST['itstudio_service_category_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['itstudio_service_category_meta_nonce'])), 'itstudio_save_service_category_meta')) {
return;
}
$name_cn = isset($_POST['itstudio_service_cat_name_cn']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_cat_name_cn'])) : '';
$name_en = isset($_POST['itstudio_service_cat_name_en']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_cat_name_en'])) : '';
$term_meta = array(
itstudio_get_service_category_name_cn_meta_key() => trim($name_cn),
itstudio_get_service_category_name_en_meta_key() => trim($name_en),
);
foreach ($term_meta as $meta_key => $value) {
if ($value === '') {
delete_term_meta($term_id, $meta_key);
} else {
update_term_meta($term_id, $meta_key, $value);
}
}
}
add_action('created_service_category', 'itstudio_save_service_category_meta');
add_action('edited_service_category', 'itstudio_save_service_category_meta');
function itstudio_service_admin_columns($columns) {
$columns['service_category'] = __('分类', 'itstudio');
$columns['service_url'] = __('跳转链接', 'itstudio');
return $columns;
}
add_filter('manage_service_posts_columns', 'itstudio_service_admin_columns');
function itstudio_service_admin_custom_column($column, $post_id) {
if ($column === 'service_category') {
$terms = get_the_terms($post_id, 'service_category');
if (empty($terms) || is_wp_error($terms)) {
echo '<span style="color:#888;">' . esc_html__('未分类', 'itstudio') . '</span>';
return;
}
$labels = array();
foreach ($terms as $term) {
$i18n = itstudio_get_service_category_i18n_labels($term);
$label = $i18n['cn'];
if ($i18n['en'] !== $i18n['cn']) {
$label .= ' / ' . $i18n['en'];
}
$labels[] = $label;
}
echo esc_html(implode(' | ', $labels));
return;
}
if ($column === 'service_url') {
$url = itstudio_get_service_target_url($post_id);
if ($url === '') {
echo '<span style="color:#888;">-</span>';
return;
}
echo '<a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer">' . esc_html($url) . '</a>';
}
}
add_action('manage_service_posts_custom_column', 'itstudio_service_admin_custom_column', 10, 2);
+141
View File
@@ -0,0 +1,141 @@
<?php
/**
* 便民服务的 meta key 定义与前台读取助手(双语内容、跳转链接、仅校内判定)。
* 函数名保持与主题历史一致,主题模板与后台按原名调用它们。
* 抽离自主题 functions.php。
*/
if (!defined('ABSPATH')) {
exit;
}
function itstudio_get_service_url_meta_key() {
return '_itstudio_service_url';
}
function itstudio_get_service_title_cn_meta_key() {
return '_itstudio_service_title_cn';
}
function itstudio_get_service_title_en_meta_key() {
return '_itstudio_service_title_en';
}
function itstudio_get_service_excerpt_cn_meta_key() {
return '_itstudio_service_excerpt_cn';
}
function itstudio_get_service_excerpt_en_meta_key() {
return '_itstudio_service_excerpt_en';
}
function itstudio_get_service_campus_only_meta_key() {
return '_itstudio_service_campus_only';
}
function itstudio_get_service_category_name_cn_meta_key() {
return 'itstudio_service_category_name_cn';
}
function itstudio_get_service_category_name_en_meta_key() {
return 'itstudio_service_category_name_en';
}
function itstudio_get_service_category_i18n_labels($term) {
if (!$term || is_wp_error($term)) {
return array(
'cn' => '未分类',
'en' => 'Uncategorized',
);
}
$term_id = (int) $term->term_id;
$name_cn = trim((string) get_term_meta($term_id, itstudio_get_service_category_name_cn_meta_key(), true));
$name_en = trim((string) get_term_meta($term_id, itstudio_get_service_category_name_en_meta_key(), true));
if ($name_cn === '') {
$name_cn = (string) $term->name;
}
if ($name_en === '') {
$name_en = $name_cn;
}
return array(
'cn' => $name_cn,
'en' => $name_en,
);
}
function itstudio_get_service_i18n_content($service_id, $excerpt_limit = 90) {
$service_id = (int) $service_id;
if ($service_id <= 0) {
return array(
'title_cn' => '',
'title_en' => '',
'excerpt_cn' => '',
'excerpt_en' => '',
);
}
$title_cn = trim((string) get_post_meta($service_id, itstudio_get_service_title_cn_meta_key(), true));
$title_en = trim((string) get_post_meta($service_id, itstudio_get_service_title_en_meta_key(), true));
$excerpt_cn = trim((string) get_post_meta($service_id, itstudio_get_service_excerpt_cn_meta_key(), true));
$excerpt_en = trim((string) get_post_meta($service_id, itstudio_get_service_excerpt_en_meta_key(), true));
$fallback_title = trim((string) get_the_title($service_id));
if ($fallback_title === '') {
$fallback_title = '未命名服务';
}
$fallback_excerpt = function_exists('itstudio_get_post_excerpt_chars')
? itstudio_get_post_excerpt_chars($service_id, $excerpt_limit)
: wp_html_excerpt(wp_strip_all_tags((string) get_post_field('post_excerpt', $service_id)), $excerpt_limit, '...');
$fallback_excerpt = trim((string) $fallback_excerpt);
if ($fallback_excerpt === '') {
$fallback_excerpt = '暂无简介';
}
if ($title_cn === '') {
$title_cn = $fallback_title;
}
if ($title_en === '') {
$title_en = $title_cn;
}
if ($excerpt_cn === '') {
$excerpt_cn = $fallback_excerpt;
}
if ($excerpt_en === '') {
$excerpt_en = $excerpt_cn;
}
return array(
'title_cn' => $title_cn,
'title_en' => $title_en,
'excerpt_cn' => $excerpt_cn,
'excerpt_en' => $excerpt_en,
);
}
function itstudio_get_service_target_url($service_id) {
$service_id = (int) $service_id;
if ($service_id <= 0) {
return '';
}
$meta_key = itstudio_get_service_url_meta_key();
$url = (string) get_post_meta($service_id, $meta_key, true);
if ($url !== '') {
return $url;
}
return (string) get_permalink($service_id);
}
function itstudio_is_service_campus_only($service_id) {
$service_id = (int) $service_id;
if ($service_id <= 0) {
return false;
}
return (string) get_post_meta($service_id, itstudio_get_service_campus_only_meta_key(), true) === '1';
}
@@ -0,0 +1,53 @@
<?php
/**
* 便民服务文章类型与服务分类。
* 抽离自主题 functions.php 的 itstudio_custom_post_types() 中 service 部分。
*/
if (!defined('ABSPATH')) {
exit;
}
function itstudio_services_register_post_types() {
register_taxonomy('service_category', array('service'), array(
'labels' => array(
'name' => __('服务分类', 'itstudio'),
'singular_name' => __('服务分类', 'itstudio'),
),
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_admin_column' => false,
'show_in_rest' => true,
'rewrite' => array(
'slug' => 'service-category',
'with_front' => false,
),
));
register_post_type('service', array(
'labels' => array(
'name' => __('便民服务', 'itstudio'),
'singular_name' => __('便民服务', 'itstudio'),
'menu_name' => __('便民服务', 'itstudio'),
'add_new_item' => __('新增便民服务', 'itstudio'),
'edit_item' => __('编辑便民服务', 'itstudio'),
),
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => false,
'exclude_from_search' => true,
'publicly_queryable' => true,
'has_archive' => false,
'rewrite' => array(
'slug' => 'service',
'with_front' => false,
),
'supports' => array('title', 'excerpt', 'thumbnail', 'page-attributes'),
'taxonomies' => array('service_category'),
'menu_icon' => 'dashicons-admin-tools',
'show_in_rest' => true,
));
}
add_action('init', 'itstudio_services_register_post_types');
@@ -0,0 +1,38 @@
<?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-services
*
* 说明:本插件从 ITStudioMainSite 主题的 functions.php 抽离而来。
* 迁移铁律:文章类型 slug(service)、分类法 slug(service_category)、各 meta key
* (_itstudio_service_url 等)必须与主题历史保持一字不差,否则数据库中的老数据会失联。
*/
if (!defined('ABSPATH')) {
exit;
}
define('ITSTUDIO_SERVICES_VERSION', '1.0.0');
define('ITSTUDIO_SERVICES_PATH', plugin_dir_path(__FILE__));
require_once ITSTUDIO_SERVICES_PATH . 'includes/post-type.php';
require_once ITSTUDIO_SERVICES_PATH . 'includes/meta.php';
require_once ITSTUDIO_SERVICES_PATH . 'includes/admin.php';
/**
* 激活时注册文章类型/分类法后刷新重写规则,保证 /service/ 等固定链接立即生效。
*/
function itstudio_services_activate() {
itstudio_services_register_post_types();
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'itstudio_services_activate');
function itstudio_services_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'itstudio_services_deactivate');