convert to plugins+theme
@@ -1,12 +1,12 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="resources/it_logo_2024.svg" width=100/>
|
<img src="themes/ITStudioMainSite/resources/it_logo_2024.svg" width=100/>
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>爱特工作室总站</h1>
|
<h1>爱特工作室总站</h1>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="resources/Organization-ITstudio-shield.svg"/><br>
|
<img src="themes/ITStudioMainSite/resources/Organization-ITstudio-shield.svg"/><br>
|
||||||
<img src="https://img.shields.io/badge/Language-php-purple">
|
<img src="https://img.shields.io/badge/Language-php-purple">
|
||||||
<img src="https://img.shields.io/badge/Language-javascript-yellow">
|
<img src="https://img.shields.io/badge/Language-javascript-yellow">
|
||||||
<img src="https://img.shields.io/badge/Language-HTML-green">
|
<img src="https://img.shields.io/badge/Language-HTML-green">
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
1. 克隆本仓库到 WordPress 主题目录:
|
1. 克隆本仓库到 WordPress 主题目录:
|
||||||
```bash
|
```bash
|
||||||
cd wp-content/themes
|
cd wp-content/themes
|
||||||
git clone https://github.com/itstudio-2002/ITStudioMainSite.git
|
git clone https://git.itouc.cn/ITStudio_OUC/ITStudioMainSite.git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 在 WordPress 后台激活主题:
|
2. 在 WordPress 后台激活主题:
|
||||||
|
|||||||
@@ -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');
|
||||||
@@ -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);
|
||||||
@@ -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');
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# 已忽略包含查询文件的默认文件夹
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ITStudioMainSite.iml" filepath="$PROJECT_DIR$/.idea/ITStudioMainSite.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="resources/it_logo_2024.svg" width=100/>
|
||||||
|
</div>
|
||||||
|
<div align="center">
|
||||||
|
<h1>爱特工作室总站</h1>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div align="center">
|
||||||
|
<img src="resources/Organization-ITstudio-shield.svg"/><br>
|
||||||
|
<img src="https://img.shields.io/badge/Language-php-purple">
|
||||||
|
<img src="https://img.shields.io/badge/Language-javascript-yellow">
|
||||||
|
<img src="https://img.shields.io/badge/Language-HTML-green">
|
||||||
|
<img src="https://img.shields.io/badge/Language-CSS-blue">
|
||||||
|
<img src="https://img.shields.io/badge/WordPress-Theme-21759b">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 📖 项目简介
|
||||||
|
|
||||||
|
中国海洋大学爱特工作室官方网站 WordPress 主题。这是一个现代化、响应式的主题,支持暗色/亮色模式切换,融合海洋元素与 IT 风格。
|
||||||
|
|
||||||
|
## ✨ 主要特性
|
||||||
|
|
||||||
|
- 🎨 **现代简洁设计** - IT风格与海洋元素完美融合
|
||||||
|
- 🌓 **主题切换** - 支持暗色/亮色模式,可根据系统主题自动切换
|
||||||
|
- 📱 **响应式布局** - 完美适配 PC 端和移动端
|
||||||
|
- 🌐 **多语言支持** - 支持中文/英文切换
|
||||||
|
- ⚡ **性能优化** - 轻量级设计,加载快速
|
||||||
|
- 🎯 **自定义内容类型** - 支持公告通知、社团新闻、便民服务独立管理
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 安装方法
|
||||||
|
|
||||||
|
1. 克隆本仓库到 WordPress 主题目录:
|
||||||
|
```bash
|
||||||
|
cd wp-content/themes
|
||||||
|
git clone https://github.com/itstudio-2002/ITStudioMainSite.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在 WordPress 后台激活主题:
|
||||||
|
- 进入 **外观 > 主题**
|
||||||
|
- 找到 "IT Studio Theme"
|
||||||
|
- 点击 **激活**
|
||||||
|
|
||||||
|
### 主题配置
|
||||||
|
|
||||||
|
#### 1. 导航菜单设置
|
||||||
|
|
||||||
|
进入 **外观 > 菜单**,创建主导航菜单并分配到 "Primary Menu" 位置。
|
||||||
|
|
||||||
|
推荐菜单结构:
|
||||||
|
- 首页
|
||||||
|
- 公告通知
|
||||||
|
- 博客
|
||||||
|
- 便民服务
|
||||||
|
- 社团介绍
|
||||||
|
- 加入我们
|
||||||
|
|
||||||
|
#### 2. 发布公告
|
||||||
|
|
||||||
|
主题内置了"公告通知"自定义文章类型:
|
||||||
|
1. 进入 **Announcement > 新建 Announcement**
|
||||||
|
2. 填写标题和内容
|
||||||
|
3. 发布即可在首页显示
|
||||||
|
|
||||||
|
#### 3. 发布新闻
|
||||||
|
|
||||||
|
主题内置了"社团新闻"自定义文章类型:
|
||||||
|
1. 进入 **News > 新建 News**
|
||||||
|
2. 填写标题和内容
|
||||||
|
3. 发布即可在首页显示
|
||||||
|
|
||||||
|
#### 4. 新闻公告页权重字段(ACF)
|
||||||
|
|
||||||
|
新闻公告页右侧“高权重文章”读取 ACF 数值字段 `itstudio_weight`(数值越大,排序越靠前)。
|
||||||
|
|
||||||
|
适用范围:
|
||||||
|
- 默认文章(`post`)
|
||||||
|
- 公告(`announcement`)
|
||||||
|
- 新闻(`news`)
|
||||||
|
|
||||||
|
设置步骤:
|
||||||
|
1. 安装并启用插件 **Advanced Custom Fields (ACF)**。
|
||||||
|
2. 主题会自动注册字段组“内容权重”,字段名为 `itstudio_weight`(显示名“权重”)。
|
||||||
|
3. 编辑文章时在右侧填写“权重”数值(例如 `10`、`50`、`100`)。
|
||||||
|
4. 更新或发布文章。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 当前仅读取 ACF 字段值,不再使用 Gutenberg 的“自定义字段”面板作为权重来源。
|
||||||
|
- 未设置 `itstudio_weight` 的文章按 `0` 处理。
|
||||||
|
- 当高权重文章不足 4 篇时,页面会自动用最新文章补足。
|
||||||
|
|
||||||
|
#### 5. 维护便民服务目录
|
||||||
|
|
||||||
|
主题内置了“便民服务(service)”内容类型,`/services` 页面会自动读取并展示。
|
||||||
|
|
||||||
|
每条服务信息来源:
|
||||||
|
- 图标:文章特色图(Featured Image)
|
||||||
|
- 名称:服务双语字段(中文名称 / 英文名称)
|
||||||
|
- 简介:服务双语字段(中文简介 / 英文简介)
|
||||||
|
- 类别:服务分类双语字段(中文名称 / 英文名称)
|
||||||
|
- 跳转链接:编辑页中的“服务跳转链接”字段
|
||||||
|
- 访问属性:编辑页中的“是否为校内服务”勾选项
|
||||||
|
|
||||||
|
后台新增步骤:
|
||||||
|
1. 进入 **便民服务 > 新增便民服务**。
|
||||||
|
2. 在“服务双语与链接”填写:
|
||||||
|
- 中文名称 / 英文名称
|
||||||
|
- 中文简介 / 英文简介
|
||||||
|
- 服务跳转链接
|
||||||
|
- 是否为校内服务(可选)
|
||||||
|
3. 设置特色图作为服务图标。
|
||||||
|
4. 在右侧选择或新建“服务分类”。
|
||||||
|
5. 发布后,该服务会自动出现在 `/services` 页面中。
|
||||||
|
|
||||||
|
分类双语设置:
|
||||||
|
1. 进入 **便民服务 > 服务分类**。
|
||||||
|
2. 新增或编辑分类时,可填写“中文名称”和“英文名称”。
|
||||||
|
3. 前台会根据语言切换自动显示对应分类名。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 若未设置“服务跳转链接”,前端会回退到该服务文章自身链接。
|
||||||
|
- 若未设置特色图,前端会使用 `resources/it_logo_2024.svg` 作为默认图标。
|
||||||
|
- 勾选“是否为校内服务”后,服务卡片右上角会显示“仅校内访问”标签;不勾选则不显示该标签。
|
||||||
|
- 新增内容类型后若前台路由未生效,请进入 **设置 > 固定链接** 点击一次“保存更改”刷新重写规则。
|
||||||
|
|
||||||
|
## 🎨 设计规范
|
||||||
|
|
||||||
|
### 主题色彩
|
||||||
|
|
||||||
|
- **海洋浅蓝色** (#f0f8ff) - 亮色模式背景
|
||||||
|
- **海洋深蓝色** (#0a1929) - 暗色模式背景
|
||||||
|
- **强调色** (#8e88c7) - 链接、按钮等
|
||||||
|
- **中性色** - 灰度系列用于文本和边框
|
||||||
|
|
||||||
|
### 页面结构
|
||||||
|
|
||||||
|
- **Header** - 左侧 Logo,右侧导航栏、社交图标、主题切换按钮
|
||||||
|
- **首页** - 工作室介绍、公告列表、博客列表双栏布局
|
||||||
|
- **Footer** - 友情链接、联系方式、版权信息
|
||||||
|
|
||||||
|
## 📂 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ITStudioMainSite/
|
||||||
|
├── assets/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── content.css # 内容页样式
|
||||||
|
│ │ ├── front-page.css # 首页样式
|
||||||
|
│ │ └── services-page.css # 便民服务页样式
|
||||||
|
│ └── js/
|
||||||
|
│ ├── theme-toggle.js # 主题切换功能
|
||||||
|
│ └── main.js # 主要 JavaScript
|
||||||
|
├── resources/
|
||||||
|
│ ├── it_logo_2024.svg # 工作室 Logo
|
||||||
|
│ ├── ouc-logo.svg # 海大 Logo
|
||||||
|
│ └── ...
|
||||||
|
├── style.css # WordPress 主题样式表(必需)
|
||||||
|
├── functions.php # 主题功能文件
|
||||||
|
├── index.php # 首页模板
|
||||||
|
├── header.php # 头部模板
|
||||||
|
├── footer.php # 底部模板
|
||||||
|
├── single.php # 单篇文章模板
|
||||||
|
├── page.php # 页面模板
|
||||||
|
├── page-services.php # 便民服务页面模板
|
||||||
|
├── archive.php # 归档页面模板
|
||||||
|
└── 404.php # 404 错误页面
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 技术栈
|
||||||
|
|
||||||
|
- **WordPress** - 内容管理系统
|
||||||
|
- **PHP** - 后端开发语言
|
||||||
|
- **HTML5/CSS3** - 前端标记与样式
|
||||||
|
- **JavaScript** - 前端交互逻辑
|
||||||
|
- **SVG** - 矢量图标和 Logo
|
||||||
|
|
||||||
|
## 🔧 开发指南
|
||||||
|
|
||||||
|
### 本地开发环境
|
||||||
|
|
||||||
|
1. 安装 WordPress 本地开发环境(如 XAMPP、MAMP 等)
|
||||||
|
2. 克隆本仓库到 `wp-content/themes` 目录
|
||||||
|
3. 激活主题开始开发
|
||||||
|
|
||||||
|
### 自定义开发
|
||||||
|
|
||||||
|
- 修改 `assets/css/main.css` 自定义样式
|
||||||
|
- 编辑 `functions.php` 添加新功能
|
||||||
|
- 创建新的模板文件扩展功能
|
||||||
|
|
||||||
|
## 📝 使用许可
|
||||||
|
|
||||||
|
MIT License - 详见 LICENSE 文件
|
||||||
|
|
||||||
|
## 👥 关于我们
|
||||||
|
|
||||||
|
**爱特工作室**成立于 2002 年,是中国海洋大学信息科学与工程学部领导主持下的技术性团队。
|
||||||
|
|
||||||
|
🎯 **使命**: 发现人才,培养人才,输送人才
|
||||||
|
|
||||||
|
🔗 **链接**:
|
||||||
|
- GitHub: [https://github.com/ITStudioOUC](https://github.com/ITStudioOUC)
|
||||||
|
- Email: contact@itstudio.club
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
|
## 📄 更新日志
|
||||||
|
|
||||||
|
### v1.0.0 (2026-02-05)
|
||||||
|
- 🎉 初始版本发布
|
||||||
|
- ✅ 基础主题功能实现
|
||||||
|
- ✅ 响应式设计完成
|
||||||
|
- ✅ 主题切换功能
|
||||||
|
- ✅ 自定义文章类型(公告)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 加入我们页面(/join)使用说明
|
||||||
|
|
||||||
|
主题已内置「加入我们」页面模板(`page-join.php`),并支持在没有创建 WordPress 页面时通过 `/join` 自动 fallback 渲染。
|
||||||
|
|
||||||
|
### 1. 需要安装的插件
|
||||||
|
|
||||||
|
1. **Formidable Forms**
|
||||||
|
用于报名表单渲染(前台报名)。
|
||||||
|
2. **WP Mail SMTP**
|
||||||
|
用于 Formidable 提交邮件发送(站点邮件走 SMTP)。
|
||||||
|
|
||||||
|
### 2. 后台时间节点设置
|
||||||
|
|
||||||
|
进入:**设置 > 招新设置**
|
||||||
|
|
||||||
|
可配置字段:
|
||||||
|
- 报名开始时间(datetime)
|
||||||
|
- 报名结束时间(datetime)
|
||||||
|
- 第一次面试开始/结束时间(datetime)
|
||||||
|
- 第一次面试地点(中/英)
|
||||||
|
- 第二次面试开始/结束时间(datetime)
|
||||||
|
- 第二次面试地点(中/英)
|
||||||
|
- 国庆能力摸底开始/结束日期(调试)
|
||||||
|
- 录取结果公布开始日期(date,系统自动延续 7 天)
|
||||||
|
- 报名表单 Shortcode
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 「国庆能力摸底阶段」固定为每年 **10/01 - 10/07**。
|
||||||
|
- 若填写“国庆能力摸底开始/结束日期(调试)”,将优先使用调试时间。
|
||||||
|
|
||||||
|
### 3. 阶段结果文件配置(重点)
|
||||||
|
|
||||||
|
在同一页面中,为以下阶段上传结果文件(CSV 或 XLSX):
|
||||||
|
- 第一次面试结果文件
|
||||||
|
- 国庆能力摸底结果文件
|
||||||
|
- 第二次面试结果文件
|
||||||
|
- 录取结果文件
|
||||||
|
|
||||||
|
文件列顺序必须为:
|
||||||
|
|
||||||
|
`姓名,QQ,邮箱,学号,手机,是否通过`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `是否通过 = 1` 表示通过
|
||||||
|
- 其他任意值(含 `0`、空)都按未通过处理
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 不需要配置任何“字段映射”或“阶段识别字段”。
|
||||||
|
- 系统会自动按上述固定列进行识别与查询。
|
||||||
|
|
||||||
|
### 4. 前台显示逻辑
|
||||||
|
|
||||||
|
- 报名表单:仅在报名阶段显示。
|
||||||
|
- 录取进度查询:报名阶段不显示;报名结束后按当前阶段显示对应结果(使用姓名/QQ/邮箱/学号查询)。
|
||||||
|
- Canvas 进度条:根据当前时间自动高亮阶段(已完成/进行中/未开始)。
|
||||||
|
|
||||||
|
### 5. WP Mail SMTP 配置建议
|
||||||
|
|
||||||
|
1. 安装并启用 **WP Mail SMTP**。
|
||||||
|
2. 在插件中填写 SMTP 服务信息(Host、Port、加密方式、账号密码)。
|
||||||
|
3. 设置发件人邮箱与发件人名称。
|
||||||
|
4. 使用插件自带发送测试邮件,确认可达后再开放报名。
|
||||||
|
|
||||||
|
## 加入我们页面快速说明
|
||||||
|
|
||||||
|
- 后台路径:`设置 -> 招新设置`。
|
||||||
|
- 先配置时间节点:报名开始/结束、一面开始/结束、二面开始/结束、录取结果公布开始日期。
|
||||||
|
- “国庆能力摸底”默认是每年 `10/01 - 10/07`(可用调试日期覆盖)。
|
||||||
|
- 报名表单短代码使用 Formidable 表单,邮件发送建议配合 WP Mail SMTP。
|
||||||
|
- 阶段结果统一使用 CSV/XLSX 上传,不再使用 Formidable Entries 做结果查询。
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# ITStudioMainSite 主题业务逻辑拆分方案
|
||||||
|
|
||||||
|
> 目标:把 `functions.php`(3959 行)里混入的插件级业务逻辑抽成 3 个独立插件,主题只保留展示层。
|
||||||
|
|
||||||
|
## 一、数据安全铁律
|
||||||
|
|
||||||
|
迁移只搬代码,不动数据库。以下标识符字符串必须一字不改,否则数据库中老数据失联:
|
||||||
|
|
||||||
|
| 类型 | 必须保持不变的值 |
|
||||||
|
|---|---|
|
||||||
|
| 文章类型 slug | `news`、`announcement`、`service`、`post` |
|
||||||
|
| 分类法 slug | `service_category` |
|
||||||
|
| 选项名 | `itstudio_join_settings` |
|
||||||
|
| post meta / ACF 字段名 | `itstudio_weight`、`itstudio_is_recruitment_article`、各 service meta key |
|
||||||
|
| AJAX action / nonce | `itstudio_join_progress_lookup` |
|
||||||
|
|
||||||
|
CPT 注册从主题挪到插件后,重写规则改变,必须在插件激活钩子 `flush_rewrite_rules()`,或手动访问一次“设置→固定链接”。
|
||||||
|
|
||||||
|
## 二、三个插件与函数清单
|
||||||
|
|
||||||
|
### 插件 1 `itstudio-content`(核心内容,地基,无依赖)
|
||||||
|
- news / announcement CPT(从 `itstudio_custom_post_types` 拆出)+ post_tag 绑定
|
||||||
|
- ACF `group_itstudio_content_priority`(权重,从 `itstudio_register_acf_fields` 拆出)
|
||||||
|
- 浏览量统计:`get_post_views`(1206)、`is_probably_bot_request`(1394)、view cookie 组(1436-1494)、`should_count_post_view`(1494)、`track_post_views`(1534)
|
||||||
|
- 助手:`get_post_weight`(1232)、`get_post_char_count`(1250)、`get_post_excerpt_chars`(1269)
|
||||||
|
- 归档 URL:`get_archive_url_by_post_type`(1289)、`get_archive_tag_filter_url`(1311)
|
||||||
|
- 归档扩展搜索:`archive_extended_search_enabled/join/where`(1337-1392)
|
||||||
|
- 日期精度显示:`expand_news_notice_date_precision`(155)
|
||||||
|
|
||||||
|
### 插件 2 `itstudio-services`(便民服务,独立,无依赖)
|
||||||
|
- service CPT + service_category(从 `itstudio_custom_post_types` 拆出)
|
||||||
|
- service meta key 助手(638-670)
|
||||||
|
- `get_service_category_i18n_labels`(670)、`get_service_i18n_content`(695)
|
||||||
|
- meta box:`add_service_meta_boxes`(745)、`render_service_link_meta_box`(757)、`save_service_meta`(847)
|
||||||
|
- 分类法字段:`service_category_add_fields`(914)、`edit_fields`(929)、`save_service_category_meta`(947)
|
||||||
|
- 前端读取:`get_service_target_url`(975)、`is_service_campus_only`(990)
|
||||||
|
- 后台列:`service_admin_columns`(999)、`service_admin_custom_column`(1006)
|
||||||
|
|
||||||
|
### 插件 3 `itstudio-recruitment`(招新系统,依赖插件1 + Formidable + ACF)
|
||||||
|
- 招新文章标记 meta:`get_recruitment_article_meta_key`(406)、`normalize_recruitment_flag`(410)、`is_recruitment_article`(423)、meta box 462/477/501
|
||||||
|
- ACF `group_itstudio_recruitment_article`(从 `itstudio_register_acf_fields` 拆出)
|
||||||
|
- 招新 feed:`join_get_recruitment_feed_items`(538)
|
||||||
|
- `is_join_page_context`(1562)
|
||||||
|
- 设置读写/清洗(1573-1748)、时间解析(1750-1811)、结果字段映射(1814-1848)
|
||||||
|
- CSV/XLSX 解析器(1863-2050)
|
||||||
|
- Formidable 耦合(2121-2427)
|
||||||
|
- 查询匹配/身份识别(2453-2678)
|
||||||
|
- 录取进度查询核心 `join_resolve_progress_lookup`(2693)
|
||||||
|
- 阶段状态机(2893-3336)
|
||||||
|
- AJAX 接口(3366 + 钩子 3402/3403)
|
||||||
|
- 强制 Formidable AJAX(3405 + 钩子 3418)
|
||||||
|
- 前端资源(3420 + 钩子 3450)
|
||||||
|
- 后台设置页(3452-3572 + 钩子 3463/3472/3483)
|
||||||
|
|
||||||
|
## 三、需手术拆开的函数
|
||||||
|
1. `itstudio_custom_post_types()`(231-306):news/announcement/post_tag → 插件1;service/service_category → 插件2。
|
||||||
|
2. `itstudio_register_acf_fields()`(308-403):`group_itstudio_content_priority` → 插件1;`group_itstudio_recruitment_article` → 插件3。
|
||||||
|
|
||||||
|
## 四、留在主题的部分(纯展示)
|
||||||
|
`itstudio_theme_setup`、评论禁用(21-60)、favicon/bootstrap 脚本(76-101)、`enqueue_scripts`(103)、`register_sidebars`(192)、`intro_body_class`(215)、`archive_document_title`(1040)、各 `*_fallback` 渲染(1052-1204、3907)。
|
||||||
|
|
||||||
|
**建议删除**:`itstudio_apply_site_identity()`(62-74)——每次强制覆写站点标题/副标题,反模式。
|
||||||
|
|
||||||
|
## 五、主题模板解耦
|
||||||
|
- 插件用 `add_shortcode` 暴露内容(如 `[itstudio_join_app]`、`[itstudio_services_grid]`),模板用 `shortcode_exists()` 判断。
|
||||||
|
- 助手函数调用处一律加 `function_exists()` 守卫,插件未启用时走降级,保证不白屏。
|
||||||
|
|
||||||
|
## 六、迁移顺序(按依赖,每步原子提交)
|
||||||
|
1. 建 `itstudio-content`(地基)→ 搬入 + 从主题删除 → 加模板守卫 → 刷新重写规则 → 验证。
|
||||||
|
2. 建 `itstudio-services`(独立)→ 同上。
|
||||||
|
3. 建 `itstudio-recruitment`(依赖插件1)→ 声明 `Requires Plugins` → 验证。
|
||||||
|
4. 收尾:清理 `functions.php`,删除 `apply_site_identity`。
|
||||||
|
|
||||||
|
关键:每个插件“搬入 + 从主题删除”是一次原子提交。绝不能让同一函数名/CPT 注册在主题和插件同时存在(PHP `Cannot redeclare` 致命错误)。是剪切,不是复制双跑。
|
||||||
|
|
||||||
|
## 七、迁移期间不崩策略
|
||||||
|
- 本地/staging 先做,别直上生产。
|
||||||
|
- 每个插件搬完过验证清单再 commit,可单独 revert。
|
||||||
|
- 生产上线:插件上传 + 启用 + 主题更新尽量同一操作。
|
||||||
|
- 上线后立刻刷一次固定链接。
|
||||||
|
|
||||||
|
## 八、验证清单(每插件搬完都跑)
|
||||||
|
- 新闻/公告归档正常、按作者名搜索有效、浏览量在涨
|
||||||
|
- 便民服务列表/分类/双语/仅校内标签正常,meta box 能存能读
|
||||||
|
- 加入我们页:报名表单能提交、进度查询返回正确阶段、招新设置能存能读、CSV/XLSX 解析正常
|
||||||
|
- 后台开 `WP_DEBUG` 无 PHP 错误
|
||||||
|
- 停用任一插件对应页面走降级不白屏
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function itstudio_theme_setup() {
|
||||||
|
add_theme_support('title-tag');
|
||||||
|
add_theme_support('post-thumbnails');
|
||||||
|
add_theme_support('automatic-feed-links');
|
||||||
|
add_theme_support('html5', array(
|
||||||
|
'search-form',
|
||||||
|
'gallery',
|
||||||
|
'caption',
|
||||||
|
));
|
||||||
|
|
||||||
|
register_nav_menus(array(
|
||||||
|
'primary' => __('Primary Menu', 'itstudio'),
|
||||||
|
));
|
||||||
|
|
||||||
|
load_theme_textdomain('itstudio', get_template_directory() . '/languages');
|
||||||
|
}
|
||||||
|
add_action('after_setup_theme', 'itstudio_theme_setup');
|
||||||
|
|
||||||
|
add_filter('comments_open', '__return_false', 20, 2);
|
||||||
|
add_filter('pings_open', '__return_false', 20, 2);
|
||||||
|
add_filter('comments_array', '__return_empty_array', 10, 2);
|
||||||
|
|
||||||
|
function itstudio_disable_comments_post_types() {
|
||||||
|
$post_types = array('post', 'page', 'announcement', 'news', 'service');
|
||||||
|
foreach ($post_types as $post_type) {
|
||||||
|
if (post_type_supports($post_type, 'comments')) {
|
||||||
|
remove_post_type_support($post_type, 'comments');
|
||||||
|
}
|
||||||
|
if (post_type_supports($post_type, 'trackbacks')) {
|
||||||
|
remove_post_type_support($post_type, 'trackbacks');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('init', 'itstudio_disable_comments_post_types', 100);
|
||||||
|
|
||||||
|
function itstudio_hide_comments_menu() {
|
||||||
|
remove_menu_page('edit-comments.php');
|
||||||
|
}
|
||||||
|
add_action('admin_menu', 'itstudio_hide_comments_menu', 999);
|
||||||
|
|
||||||
|
function itstudio_hide_admin_bar_comments() {
|
||||||
|
remove_action('admin_bar_menu', 'wp_admin_bar_comments_menu', 60);
|
||||||
|
}
|
||||||
|
add_action('init', 'itstudio_hide_admin_bar_comments');
|
||||||
|
|
||||||
|
function itstudio_redirect_comments_admin_pages() {
|
||||||
|
global $pagenow;
|
||||||
|
if ($pagenow === 'edit-comments.php' || $pagenow === 'comment.php') {
|
||||||
|
wp_safe_redirect(admin_url());
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('admin_init', 'itstudio_redirect_comments_admin_pages');
|
||||||
|
|
||||||
|
function itstudio_remove_comments_dashboard_widget() {
|
||||||
|
remove_meta_box('dashboard_recent_comments', 'dashboard', 'normal');
|
||||||
|
}
|
||||||
|
add_action('wp_dashboard_setup', 'itstudio_remove_comments_dashboard_widget');
|
||||||
|
|
||||||
|
function itstudio_apply_site_identity() {
|
||||||
|
$site_name = '爱特工作室';
|
||||||
|
$site_tagline = '爱特工作室官方网站';
|
||||||
|
|
||||||
|
if (get_option('blogname') !== $site_name) {
|
||||||
|
update_option('blogname', $site_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_option('blogdescription') !== $site_tagline) {
|
||||||
|
update_option('blogdescription', $site_tagline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('init', 'itstudio_apply_site_identity', 20);
|
||||||
|
|
||||||
|
function itstudio_output_favicon() {
|
||||||
|
$favicon_url = get_template_directory_uri() . '/resources/it_logo_2024.svg';
|
||||||
|
echo '<link rel="icon" href="' . esc_url($favicon_url) . '" type="image/svg+xml">' . "\n";
|
||||||
|
echo '<link rel="shortcut icon" href="' . esc_url($favicon_url) . '" type="image/svg+xml">' . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function itstudio_output_theme_bootstrap_script() {
|
||||||
|
echo '<meta name="color-scheme" content="dark light">' . "\n";
|
||||||
|
echo '<script>(function(){var d=document.documentElement;var t="light";try{var s=localStorage.getItem("theme");if(s==="dark"||s==="light"){t=s;}else if(window.matchMedia){t=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";}}catch(e){if(window.matchMedia){t=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";}}d.setAttribute("data-theme",t);d.style.colorScheme=t;})();</script>' . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function itstudio_output_lang_bootstrap_script() {
|
||||||
|
echo '<style id="itstudio-lang-boot-style">html.itstudio-lang-pending body{visibility:hidden;}</style>' . "\n";
|
||||||
|
echo '<script>(function(){var d=document.documentElement;var lang="zh";d.classList.add("itstudio-lang-pending");try{var s=localStorage.getItem("language");if(s==="zh"||s==="en"){lang=s;}}catch(e){}d.setAttribute("lang",lang);window.__ITSTUDIO_LANG__=lang;var apply=function(root){var scope=root||document;var txt=scope.querySelectorAll("[data-cn][data-en]");for(var i=0;i<txt.length;i++){var el=txt[i];var next=lang==="en"?el.getAttribute("data-en"):el.getAttribute("data-cn");if(!next){continue;}if(el.tagName==="INPUT"||el.tagName==="TEXTAREA"){var tp=(el.getAttribute("type")||"").toLowerCase();if(tp==="submit"||tp==="button"||tp==="reset"){el.value=next;continue;}}el.textContent=next;}var ph=scope.querySelectorAll("[data-cn-placeholder][data-en-placeholder]");for(var j=0;j<ph.length;j++){var ep=ph[j];var pk=lang==="en"?"data-en-placeholder":"data-cn-placeholder";var pv=ep.getAttribute(pk);if(pv){ep.setAttribute("placeholder",pv);}}var ar=scope.querySelectorAll("[data-cn-aria-label][data-en-aria-label]");for(var k=0;k<ar.length;k++){var ea=ar[k];var ak=lang==="en"?"data-en-aria-label":"data-cn-aria-label";var av=ea.getAttribute(ak);if(av){ea.setAttribute("aria-label",av);}}};var finish=function(){if(document.body){document.body.setAttribute("lang",lang);}d.classList.remove("itstudio-lang-pending");var st=document.getElementById("itstudio-lang-boot-style");if(st&&st.parentNode){st.parentNode.removeChild(st);}};var run=function(){apply(document);finish();};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",run,{once:true});}else{run();}})();</script>' . "\n";
|
||||||
|
}
|
||||||
|
function itstudio_disable_default_site_icon() {
|
||||||
|
remove_action('wp_head', 'wp_site_icon', 99);
|
||||||
|
remove_action('admin_head', 'wp_site_icon', 99);
|
||||||
|
remove_action('login_head', 'wp_site_icon', 99);
|
||||||
|
}
|
||||||
|
add_action('init', 'itstudio_disable_default_site_icon');
|
||||||
|
add_action('wp_head', 'itstudio_output_theme_bootstrap_script', 0);
|
||||||
|
add_action('wp_head', 'itstudio_output_lang_bootstrap_script', 1);
|
||||||
|
add_action('wp_head', 'itstudio_output_favicon', 1);
|
||||||
|
add_action('admin_head', 'itstudio_output_favicon', 1);
|
||||||
|
add_action('login_head', 'itstudio_output_favicon', 1);
|
||||||
|
|
||||||
|
function itstudio_enqueue_scripts() {
|
||||||
|
// 基础样式 (Style.css)
|
||||||
|
wp_enqueue_style('itstudio-style', get_stylesheet_uri(), array(), '2.1.2');
|
||||||
|
|
||||||
|
wp_enqueue_style('itstudio-header', get_template_directory_uri() . '/assets/css/header.css', array('itstudio-style'), '2.1.2');
|
||||||
|
wp_enqueue_style('itstudio-footer', get_template_directory_uri() . '/assets/css/footer.css', array('itstudio-style'), '2.1.2');
|
||||||
|
wp_enqueue_style('itstudio-content', get_template_directory_uri() . '/assets/css/content.css', array('itstudio-style'), '2.1.2');
|
||||||
|
|
||||||
|
// 仅在首页加载首页样式
|
||||||
|
if (is_front_page() || is_home()) {
|
||||||
|
wp_enqueue_style('itstudio-front-page', get_template_directory_uri() . '/assets/css/front-page.css', array('itstudio-style'), '2.1.2');
|
||||||
|
wp_enqueue_script('itstudio-landing-hero-canvas', get_template_directory_uri() . '/assets/js/landing-hero-canvas.js', array(), '1.0.0', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅在工作室介绍页加载(包含 /about fallback)
|
||||||
|
$is_about = is_page('about') || is_page_template('page-about.php');
|
||||||
|
if (!$is_about && is_404()) {
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim($wp->request, '/') : '';
|
||||||
|
$is_about = ($request === 'about');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_about) {
|
||||||
|
wp_enqueue_style('itstudio-about-hero', get_template_directory_uri() . '/assets/css/about-hero.css', array('itstudio-content'), '2.1.2');
|
||||||
|
wp_enqueue_script('itstudio-animejs', get_template_directory_uri() . '/assets/js/vendor/anime.min.js', array(), '3.2.2', true);
|
||||||
|
wp_enqueue_script('itstudio-about-hero-waves', get_template_directory_uri() . '/assets/js/hero-waves.js', array('itstudio-animejs'), '1.0.0', true);
|
||||||
|
wp_enqueue_script('itstudio-about-hero', get_template_directory_uri() . '/assets/js/home-hero.js', array('itstudio-animejs'), '1.0.0', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅在便民服务页加载(包含 /services fallback)
|
||||||
|
$is_services = is_page('services') || is_page_template('page-services.php');
|
||||||
|
if (!$is_services && is_404()) {
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim($wp->request, '/') : '';
|
||||||
|
$is_services = ($request === 'services');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_services) {
|
||||||
|
wp_enqueue_style('itstudio-services-page', get_template_directory_uri() . '/assets/css/services-page.css', array('itstudio-content'), '1.0.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scripts
|
||||||
|
wp_enqueue_script('itstudio-theme-toggle', get_template_directory_uri() . '/assets/js/theme-toggle.js', array(), '1.0.0', true);
|
||||||
|
wp_enqueue_script('itstudio-lang-toggle', get_template_directory_uri() . '/assets/js/lang-toggle.js', array(), '1.0.0', true);
|
||||||
|
wp_enqueue_script('itstudio-main', get_template_directory_uri() . '/assets/js/main.js', array(), '1.0.0', true);
|
||||||
|
|
||||||
|
if (is_singular(array('post', 'announcement', 'news'))) {
|
||||||
|
wp_enqueue_script('itstudio-single-title-fit', get_template_directory_uri() . '/assets/js/single-title-fit.js', array(), '1.0.0', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('wp_enqueue_scripts', 'itstudio_enqueue_scripts');
|
||||||
|
|
||||||
|
function itstudio_register_sidebars() {
|
||||||
|
register_sidebar(array(
|
||||||
|
'name' => __('Footer - Column 1', 'itstudio'),
|
||||||
|
'id' => 'footer-1',
|
||||||
|
'description' => __('Footer widget area 1', 'itstudio'),
|
||||||
|
'before_widget' => '<div class="footer-widget">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<h3 class="widget-title">',
|
||||||
|
'after_title' => '</h3>',
|
||||||
|
));
|
||||||
|
|
||||||
|
register_sidebar(array(
|
||||||
|
'name' => __('Footer - Column 2', 'itstudio'),
|
||||||
|
'id' => 'footer-2',
|
||||||
|
'description' => __('Footer widget area 2', 'itstudio'),
|
||||||
|
'before_widget' => '<div class="footer-widget">',
|
||||||
|
'after_widget' => '</div>',
|
||||||
|
'before_title' => '<h3 class="widget-title">',
|
||||||
|
'after_title' => '</h3>',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
add_action('widgets_init', 'itstudio_register_sidebars');
|
||||||
|
|
||||||
|
function itstudio_intro_body_class($classes) {
|
||||||
|
$is_about = is_page('about') || is_page_template('page-about.php');
|
||||||
|
if (!$is_about && is_404()) {
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim($wp->request, '/') : '';
|
||||||
|
$is_about = ($request === 'about');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_about) {
|
||||||
|
$classes[] = 'intro-about';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
add_filter('body_class', 'itstudio_intro_body_class');
|
||||||
|
|
||||||
|
function itstudio_archive_document_title($parts) {
|
||||||
|
if (is_post_type_archive('announcement')) {
|
||||||
|
$parts['title'] = '公告通知';
|
||||||
|
} elseif (is_post_type_archive('news')) {
|
||||||
|
$parts['title'] = '社团新闻';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parts;
|
||||||
|
}
|
||||||
|
add_filter('document_title_parts', 'itstudio_archive_document_title', 20);
|
||||||
|
|
||||||
|
// Fallback: render /about even if the page isn't created in WP admin.
|
||||||
|
function itstudio_about_fallback() {
|
||||||
|
if (!is_404()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim($wp->request, '/') : '';
|
||||||
|
if ($request !== 'about') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = locate_template('page-about.php');
|
||||||
|
if ($template) {
|
||||||
|
global $wp_query;
|
||||||
|
if ($wp_query) {
|
||||||
|
$wp_query->is_404 = false;
|
||||||
|
$wp_query->is_page = true;
|
||||||
|
$wp_query->is_singular = true;
|
||||||
|
$virtual_post = new WP_Post((object) array(
|
||||||
|
'ID' => 0,
|
||||||
|
'post_type' => 'page',
|
||||||
|
'post_parent' => 0,
|
||||||
|
'post_title' => __('工作室介绍', 'itstudio'),
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'post_name' => 'about',
|
||||||
|
'post_content' => '',
|
||||||
|
));
|
||||||
|
$wp_query->post = $virtual_post;
|
||||||
|
$wp_query->posts = array($virtual_post);
|
||||||
|
$wp_query->queried_object = $virtual_post;
|
||||||
|
$wp_query->queried_object_id = 0;
|
||||||
|
$wp_query->post_count = 1;
|
||||||
|
$wp_query->found_posts = 1;
|
||||||
|
$wp_query->max_num_pages = 1;
|
||||||
|
global $post;
|
||||||
|
$post = $virtual_post;
|
||||||
|
setup_postdata($post);
|
||||||
|
}
|
||||||
|
add_filter('document_title_parts', function ($parts) {
|
||||||
|
$parts['title'] = __('工作室介绍', 'itstudio');
|
||||||
|
return $parts;
|
||||||
|
});
|
||||||
|
status_header(200);
|
||||||
|
nocache_headers();
|
||||||
|
include $template;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('template_redirect', 'itstudio_about_fallback');
|
||||||
|
|
||||||
|
// Fallback: render /news via archive.php even if the page isn't created in WP admin.
|
||||||
|
function itstudio_news_fallback() {
|
||||||
|
if (!is_404()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim($wp->request, '/') : '';
|
||||||
|
if ($request !== 'news') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = locate_template('archive.php');
|
||||||
|
if ($template) {
|
||||||
|
global $wp_query;
|
||||||
|
if ($wp_query) {
|
||||||
|
$wp_query->is_404 = false;
|
||||||
|
$wp_query->is_page = true;
|
||||||
|
$wp_query->is_singular = true;
|
||||||
|
$virtual_post = new WP_Post((object) array(
|
||||||
|
'ID' => 0,
|
||||||
|
'post_type' => 'page',
|
||||||
|
'post_parent' => 0,
|
||||||
|
'post_title' => __('社团新闻', 'itstudio'),
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'post_name' => 'news',
|
||||||
|
'post_content' => '',
|
||||||
|
));
|
||||||
|
$wp_query->post = $virtual_post;
|
||||||
|
$wp_query->posts = array($virtual_post);
|
||||||
|
$wp_query->queried_object = $virtual_post;
|
||||||
|
$wp_query->queried_object_id = 0;
|
||||||
|
$wp_query->post_count = 1;
|
||||||
|
$wp_query->found_posts = 1;
|
||||||
|
$wp_query->max_num_pages = 1;
|
||||||
|
global $post;
|
||||||
|
$post = $virtual_post;
|
||||||
|
setup_postdata($post);
|
||||||
|
}
|
||||||
|
add_filter('document_title_parts', function ($parts) {
|
||||||
|
$parts['title'] = __('社团新闻', 'itstudio');
|
||||||
|
return $parts;
|
||||||
|
});
|
||||||
|
$GLOBALS['itstudio_archive_mode'] = 'news';
|
||||||
|
status_header(200);
|
||||||
|
nocache_headers();
|
||||||
|
include $template;
|
||||||
|
unset($GLOBALS['itstudio_archive_mode']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('template_redirect', 'itstudio_news_fallback');
|
||||||
|
|
||||||
|
// Fallback: render /services even if the page isn't created in WP admin.
|
||||||
|
function itstudio_services_fallback() {
|
||||||
|
if (!is_404()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim($wp->request, '/') : '';
|
||||||
|
if ($request !== 'services') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = locate_template('page-services.php');
|
||||||
|
if ($template) {
|
||||||
|
global $wp_query;
|
||||||
|
if ($wp_query) {
|
||||||
|
$wp_query->is_404 = false;
|
||||||
|
$wp_query->is_page = true;
|
||||||
|
$wp_query->is_singular = true;
|
||||||
|
$virtual_post = new WP_Post((object) array(
|
||||||
|
'ID' => 0,
|
||||||
|
'post_type' => 'page',
|
||||||
|
'post_parent' => 0,
|
||||||
|
'post_title' => __('便民服务', 'itstudio'),
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'post_name' => 'services',
|
||||||
|
'post_content' => '',
|
||||||
|
));
|
||||||
|
$wp_query->post = $virtual_post;
|
||||||
|
$wp_query->posts = array($virtual_post);
|
||||||
|
$wp_query->queried_object = $virtual_post;
|
||||||
|
$wp_query->queried_object_id = 0;
|
||||||
|
$wp_query->post_count = 1;
|
||||||
|
$wp_query->found_posts = 1;
|
||||||
|
$wp_query->max_num_pages = 1;
|
||||||
|
global $post;
|
||||||
|
$post = $virtual_post;
|
||||||
|
setup_postdata($post);
|
||||||
|
}
|
||||||
|
add_filter('document_title_parts', function ($parts) {
|
||||||
|
$parts['title'] = __('便民服务', 'itstudio');
|
||||||
|
return $parts;
|
||||||
|
});
|
||||||
|
status_header(200);
|
||||||
|
nocache_headers();
|
||||||
|
include $template;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('template_redirect', 'itstudio_services_fallback');
|
||||||
|
|
||||||
|
function itstudio_join_fallback() {
|
||||||
|
if (!is_404()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wp;
|
||||||
|
$request = isset($wp->request) ? trim((string) $wp->request, '/') : '';
|
||||||
|
if ($request !== 'join') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = locate_template('page-join.php');
|
||||||
|
if (!$template) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wp_query;
|
||||||
|
if ($wp_query) {
|
||||||
|
$wp_query->is_404 = false;
|
||||||
|
$wp_query->is_page = true;
|
||||||
|
$wp_query->is_singular = true;
|
||||||
|
$virtual_post = new WP_Post((object) array(
|
||||||
|
'ID' => 0,
|
||||||
|
'post_type' => 'page',
|
||||||
|
'post_parent' => 0,
|
||||||
|
'post_title' => '加入我们',
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'post_name' => 'join',
|
||||||
|
'post_content' => '',
|
||||||
|
));
|
||||||
|
$wp_query->post = $virtual_post;
|
||||||
|
$wp_query->posts = array($virtual_post);
|
||||||
|
$wp_query->queried_object = $virtual_post;
|
||||||
|
$wp_query->queried_object_id = 0;
|
||||||
|
$wp_query->post_count = 1;
|
||||||
|
$wp_query->found_posts = 1;
|
||||||
|
$wp_query->max_num_pages = 1;
|
||||||
|
global $post;
|
||||||
|
$post = $virtual_post;
|
||||||
|
setup_postdata($post);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_filter('document_title_parts', static function ($parts) {
|
||||||
|
$parts['title'] = '加入我们';
|
||||||
|
return $parts;
|
||||||
|
});
|
||||||
|
|
||||||
|
status_header(200);
|
||||||
|
nocache_headers();
|
||||||
|
include $template;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
add_action('template_redirect', 'itstudio_join_fallback', 9);
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |