三项功能:
1. 清明不再用固定 04-04
清明是节气,日期在 4/4~4/6 之间浮动。硬编码 04-04 实测 1900-2100
共 201 年里只有 76 年是对的(约 38%)。改为内嵌逐年表换算真实日期,
超出 1900-2100 返回 null 明确放弃,不退化成近似值。
兼容 3.2.17 及以前勾过的 '04-04' 键,升级后勾选不会被静默丢弃。
2. 主题内实现 [TOC]
文章里写 [TOC],该位置输出本文目录,点击跳到对应标题。
在 the_content 优先级 9 处理(早于 wpautop 10 / do_shortcode 11),
保证"补 id"与"扫目录"用的是同一份文本,href 与 id 必然对得上。
嵌套用深度归一化,h1→h3 跳级不会算错层级。
scroll-margin-top 声明式让位,原生锚点/外部 #hash/后退全都生效。
3. 重建评论区
旧实现的问题:列表渲染与 AJAX 渲染各写一份 HTML,已经分叉
(AJAX 返回的新评论没有回复按钮);<li> 被 <div> 包着,嵌套回复错位。
现在统一到 yang_comment_html() 单一渲染函数,列表与 AJAX 只差一个
is-new 类。同时修掉三处真实缺陷:
· 评论项用 flex 会把 <ol class="yang-children"> 当成第三个 flex 项,
子评论跑到父评论右侧、正文被挤到 156px、页面横向溢出 75px —— 改 grid
· 成功提示在 exitReplyMode() 之前设置,被其末尾的 setStatus('','')
擦掉,用户提交成功却看不到反馈 —— 调整顺序
· 窄屏媒体查询打不过深层选择器(媒体查询不增加特异性),
嵌套头像反向变大 —— 补深层覆盖
与 wpDiscuz 解耦:邮件模块不再无条件 return false(那等于永久关掉
notify_postauthor 且不报错);插件在场时不加载主题评论资源,
避免双份表单互抢。
验证:PHP 语法 73/73;_static_qingming 36、_test_toc 53、
_test_comments 62、_test_comments_falsify 17、
_toc_geometry 121、_toc_geometry_falsify 9、
_comments_geometry 98、_comments_geometry_falsify 23,全部 0 失败。
线上端到端 29/0(真实文章渲染目录 + 真实评论数据渲染)。
358 lines
14 KiB
PHP
358 lines
14 KiB
PHP
<?php
|
||
/**
|
||
* 文章相关函数
|
||
* @author Yongquan Yang <yangyq.net>
|
||
* @license MIT License
|
||
* @version 2020.06.13
|
||
*/
|
||
|
||
// 文章链接添加 target 和 rel
|
||
function imgnofollow($content)
|
||
{
|
||
$regexp = "<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>";
|
||
if (preg_match_all("/$regexp/siU", $content, $matches, PREG_SET_ORDER)) {
|
||
if (!empty($matches)) {
|
||
$srcUrl = get_option('siteurl');
|
||
for ($i = 0; $i < count($matches); $i++) {
|
||
$tag = $matches[$i][0];
|
||
$tag2 = $matches[$i][0];
|
||
$url = $matches[$i][0];
|
||
$noFollow = '';
|
||
$pattern = '/target\s*=\s*"\s*_blank\s*"/';
|
||
preg_match($pattern, $tag2, $match, PREG_OFFSET_CAPTURE);
|
||
if (count($match) < 1) {
|
||
$noFollow .= ' target="_blank" ';
|
||
}
|
||
|
||
$pattern = '/rel\s*=\s*"\s*[n|d]ofollow\s*"/';
|
||
preg_match($pattern, $tag2, $match, PREG_OFFSET_CAPTURE);
|
||
if (count($match) < 1) {
|
||
$noFollow .= ' rel="nofollow" ';
|
||
}
|
||
|
||
$pos = strpos($url, $srcUrl);
|
||
if ($pos === false) {
|
||
$tag = rtrim($tag, '>');
|
||
$tag .= $noFollow . '>';
|
||
$content = str_replace($tag2, $tag, $content);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
$content = str_replace(']]>', ']]>', $content);
|
||
return $content;
|
||
}
|
||
add_filter('the_content', 'imgnofollow');
|
||
|
||
// 文章点赞
|
||
function love()
|
||
{
|
||
global $wpdb, $post;
|
||
$id = $_POST["um_id"];
|
||
$action = $_POST["um_action"];
|
||
if ($action == 'love') {
|
||
$raters = get_post_meta($id, 'love', true);
|
||
$expire = time() + 99999999;
|
||
$domain = ($_SERVER['HTTP_HOST'] != 'localhost') ? $_SERVER['HTTP_HOST'] : false;
|
||
setcookie('love_' . $id, $id, $expire, '/', $domain, false);
|
||
if (!$raters || !is_numeric($raters)) {
|
||
update_post_meta($id, 'love', 1);
|
||
} else {
|
||
update_post_meta($id, 'love', ($raters + 1));
|
||
}
|
||
echo get_post_meta($id, 'love', true);
|
||
}
|
||
die;
|
||
}
|
||
add_action('wp_ajax_nopriv_love', 'love');
|
||
add_action('wp_ajax_love', 'love');
|
||
|
||
// 文章阅读次数统计
|
||
function set_post_views()
|
||
{
|
||
if (is_singular()) {
|
||
global $post;
|
||
$post_ID = $post->ID;
|
||
if ($post_ID) {
|
||
$post_views = (int) get_post_meta($post_ID, 'views', true);
|
||
if (!update_post_meta($post_ID, 'views', ($post_views + 1))) {
|
||
add_post_meta($post_ID, 'views', 1, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
add_action('wp_head', 'set_post_views');
|
||
|
||
function get_post_views($echo = 1)
|
||
{
|
||
global $post;
|
||
$post_ID = $post->ID;
|
||
$views = (int) get_post_meta($post_ID, 'views', true);
|
||
return $views;
|
||
}
|
||
|
||
// 文章列表简介内容
|
||
function excerpt_length($length)
|
||
{
|
||
return 260;
|
||
}
|
||
add_filter('excerpt_length', 'excerpt_length');
|
||
|
||
// 开启特色图
|
||
add_theme_support("post-thumbnails");
|
||
|
||
// 文章特色图片
|
||
function post_thumbnail()
|
||
{
|
||
global $post;
|
||
$img_id = get_post_thumbnail_id();
|
||
$img_url = wp_get_attachment_image_src($img_id, array(720, 435));
|
||
if (is_array($img_url)) {
|
||
$img_url = $img_url[0];
|
||
}
|
||
if (has_post_thumbnail()) {
|
||
echo '<img src="' . $img_url . '" />';
|
||
} else {
|
||
$content = $post->post_content;
|
||
$img_preg = "/<img (.*?)src=\"(.+?)\".*?>/";
|
||
preg_match($img_preg, $content, $img_src);
|
||
$img_count = count($img_src) - 1;
|
||
if (isset($img_src[$img_count])) {
|
||
$img_val = $img_src[$img_count];
|
||
}
|
||
if (!empty($img_val)) {
|
||
echo '<img src="' . esc_url($img_val) . '" />';
|
||
} else {
|
||
/* 正文里没图、也没设特色图时用默认特色图。
|
||
空值(用户清过那一格)同样落到包内默认图。 */
|
||
$img = yang_option_image('g_postthumbnail');
|
||
if ($img !== '') {
|
||
echo '<img src="' . esc_url($img) . '" />';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 文章列表分页
|
||
function pagelist($range = 5)
|
||
{
|
||
global $paged, $wp_query, $max_page;
|
||
if (!$max_page) {$max_page = $wp_query->max_num_pages;}
|
||
if ($max_page > 1) {if (!$paged) {$paged = 1;}
|
||
echo "<div class='paginations'>";
|
||
if ($paged > 1) {
|
||
echo '<a href="' . get_pagenum_link($paged - 1) . '" class="prev" title="上一页"><i class="kicon i-larrows"></i></a>';
|
||
}
|
||
if ($max_page > $range) {
|
||
if ($paged < $range) {
|
||
for ($i = 1; $i <= $range; $i++) {
|
||
if ($i == $paged) {
|
||
echo '<span class="page-numbers current">' . $i . '</span>';
|
||
} else {
|
||
echo "<a href='" . get_pagenum_link($i) . "'>$i</a>";
|
||
}
|
||
}
|
||
echo '<span class="page-numbers dots">…</span>';
|
||
echo "<a href='" . get_pagenum_link($max_page) . "'>$max_page</a>";
|
||
} elseif ($paged >= ($max_page - ceil(($range / 2)))) {
|
||
if ($paged != 1) {
|
||
echo "<a href='" . get_pagenum_link(1) . "' class='extend' title='首页'>1</a>";
|
||
echo '<span class="page-numbers dots">…</span>';
|
||
}
|
||
for ($i = $max_page - $range + 1; $i <= $max_page; $i++) {
|
||
if ($i == $paged) {
|
||
echo '<span class="page-numbers current">' . $i . '</span>';
|
||
} else {
|
||
echo "<a href='" . get_pagenum_link($i) . "'>$i</a>";
|
||
}
|
||
}
|
||
} elseif ($paged >= $range && $paged < ($max_page - ceil(($range / 2)))) {
|
||
if ($paged != 1) {
|
||
echo "<a href='" . get_pagenum_link(1) . "' class='extend' title='首页'>1</a>";
|
||
echo '<span class="page-numbers dots">…</span>';
|
||
}
|
||
for ($i = ($paged - ceil($range / 3)); $i <= ($paged + ceil(($range / 3))); $i++) {
|
||
if ($i == $paged) {
|
||
echo '<span class="page-numbers current">' . $i . '</span>';
|
||
} else {
|
||
echo "<a href='" . get_pagenum_link($i) . "'>$i</a>";
|
||
}
|
||
}
|
||
echo '<span class="page-numbers dots">…</span>';
|
||
echo "<a href='" . get_pagenum_link($max_page) . "'>$max_page</a>";
|
||
}
|
||
} else {
|
||
for ($i = 1; $i <= $max_page; $i++) {
|
||
if ($i == $paged) {
|
||
echo '<span class="page-numbers current">' . $i . '</span>';
|
||
} else {
|
||
echo "<a href='" . get_pagenum_link($i) . "'>$i</a>";
|
||
}
|
||
}
|
||
}
|
||
if ($paged < $max_page) {
|
||
echo '<a href="' . get_pagenum_link($paged + 1) . '" class="next" title="下一页"><i class="kicon i-rarrows"></i></a>';
|
||
}
|
||
echo "</div>";
|
||
}
|
||
}
|
||
|
||
/* ── 评论相关已迁到 inc/theme-comments.php(3.2.18)──────────────
|
||
*
|
||
* 原先这里有一段 201~294 行的评论实现(comment_scripts /
|
||
* comment_callback / comment_callbacks)和 380~385 行的
|
||
* recover_comment_fields。迁走的原因是:
|
||
*
|
||
* · 列表渲染与 AJAX 渲染**各写了一份 HTML**,已经分叉 ——
|
||
* AJAX 返回的新评论没有「回复」按钮,必须刷新页面才出现;
|
||
* · 提交逻辑用的是旧的 layer 弹窗 + 直接透传后端原始响应;
|
||
* · 缺少 nonce 校验、缺少防重复提交。
|
||
*
|
||
* 新实现统一到 yang_comment_html() 一个渲染函数,两边都调它,
|
||
* 从结构上消除分叉可能。函数名 comment_callbacks / comment_scripts
|
||
* 也都在新文件里以兼容形式保留,所以 comments.php 不必改调用点。
|
||
* ------------------------------------------------------------ */
|
||
|
||
// 文章评论表情
|
||
function custom_smilies_src($img_src, $img, $siteurl)
|
||
{
|
||
return ASSET_PATH . '/assets/img/smilies/' . $img;
|
||
}
|
||
add_filter('smilies_src', 'custom_smilies_src', 1, 10);
|
||
|
||
function disable_emojis_tinymce($plugins)
|
||
{
|
||
return array_diff($plugins, array('wpemoji'));
|
||
}
|
||
function smilies_reset()
|
||
{
|
||
global $wpsmiliestrans, $wp_smiliessearch, $wp_version;
|
||
if (!get_option('use_smilies') || $wp_version < 4.2) {
|
||
return;
|
||
}
|
||
|
||
$wpsmiliestrans = array(
|
||
':mrgreen:' => 'mrgreen.png',
|
||
':exclaim:' => 'exclaim.png',
|
||
':neutral:' => 'neutral.png',
|
||
':twisted:' => 'twisted.png',
|
||
':arrow:' => 'arrow.png',
|
||
':eek:' => 'eek.png',
|
||
':smile:' => 'smile.png',
|
||
':confused:' => 'confused.png',
|
||
':cool:' => 'cool.png',
|
||
':evil:' => 'evil.png',
|
||
':biggrin:' => 'biggrin.png',
|
||
':idea:' => 'idea.png',
|
||
':redface:' => 'redface.png',
|
||
':razz:' => 'razz.png',
|
||
':rolleyes:' => 'rolleyes.png',
|
||
':wink:' => 'wink.png',
|
||
':cry:' => 'cry.png',
|
||
':lol:' => 'lol.png',
|
||
':mad:' => 'mad.png',
|
||
':drooling:' => 'drooling.png',
|
||
':persevering:' => 'persevering.png',
|
||
);
|
||
}
|
||
smilies_reset();
|
||
|
||
function smilies_custom_button()
|
||
{
|
||
printf('<style>.smilies-wrap{background:#fff;border: 1px solid #ccc;box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.24);padding: 10px;position: absolute;top: 60px;width: 400px;display:none}.smilies-wrap img{height:24px;width:24px;cursor:pointer;margin-bottom:5px} .is-active.smilies-wrap{display:block}@media screen and (max-width: 782px){ #wp-content-media-buttons a { font-size: 14px; padding: 0 14px; }}</style><a id="insert-media-button" style="position:relative" class="button insert-smilies add_smilies" data-editor="content" href="javascript:;"><span class="dashicons dashicons-smiley" style="line-height: 26px;"></span>' . __('添加表情', 'kratos') . '</a><div class="smilies-wrap">' . get_wpsmiliestrans() . '</div><script>jQuery(document).ready(function(){jQuery(document).on("click", ".insert-smilies",function() { if(jQuery(".smilies-wrap").hasClass("is-active")){jQuery(".smilies-wrap").removeClass("is-active");}else{jQuery(".smilies-wrap").addClass("is-active");}});jQuery(document).on("click", ".add-smily",function() { send_to_editor(" " + jQuery(this).data("smilies") + " ");jQuery(".smilies-wrap").removeClass("is-active");return false;});});</script>');
|
||
}
|
||
add_action('media_buttons', 'smilies_custom_button');
|
||
|
||
function get_wpsmiliestrans()
|
||
{
|
||
global $wpsmiliestrans;
|
||
global $output;
|
||
$wpsmilies = array_unique($wpsmiliestrans);
|
||
foreach ($wpsmilies as $alt => $src_path) {
|
||
$output .= '<a class="add-smily" data-smilies="' . $alt . '"><img class="wp-smiley" src="' . ASSET_PATH . '/assets/img/smilies/' . rtrim($src_path, "png") . 'png" /></a>';
|
||
}
|
||
return $output;
|
||
}
|
||
|
||
if (!kratos_option('g_gutenberg',false)) {
|
||
// 禁用 Gutenberg 编辑器
|
||
add_filter('use_block_editor_for_post', '__return_false');
|
||
remove_action('wp_enqueue_scripts', 'wp_common_block_scripts_and_styles');
|
||
|
||
// 删除前端的block library的css资源,
|
||
add_action('wp_enqueue_scripts', 'remove_block_library_css', 100);
|
||
function remove_block_library_css()
|
||
{
|
||
wp_dequeue_style('wp-block-library');
|
||
}
|
||
}
|
||
|
||
// 文章评论增强
|
||
function comment_add_at($comment_text, $comment = '') {
|
||
if( $comment->comment_parent > 0) {
|
||
$comment_text = '<span>@' . get_comment_author( $comment->comment_parent ) . '</span> ' . $comment_text;
|
||
}
|
||
|
||
return $comment_text;
|
||
}
|
||
add_filter('comment_text' , 'comment_add_at', 20, 2);
|
||
|
||
/* recover_comment_fields() 已由 inc/theme-comments.php 里的
|
||
yang_comment_form_fields() 接替(同样的字段顺序,但不再用
|
||
array_shift 硬拿第一个 —— 那样在插件往字段数组前面插东西时会拿错)。 */
|
||
|
||
$new_meta_boxes =
|
||
array(
|
||
"description" => array(
|
||
"name" => "seo_description",
|
||
"std" => "",
|
||
"title" => __( '描述', 'kratos' )
|
||
),
|
||
"keywords" => array(
|
||
"name" => "seo_keywords",
|
||
"std" => "",
|
||
"title" => __( '关键词', 'kratos' )
|
||
)
|
||
);
|
||
|
||
function seo_meta_boxes() {
|
||
$post_types = get_post_types();
|
||
add_meta_box( 'meta-box-id', __( 'SEO 设置', 'kratos' ), 'post_seo_callback', $post_types );
|
||
}
|
||
add_action( 'add_meta_boxes', 'seo_meta_boxes' );
|
||
|
||
function post_seo_callback( $post ) {
|
||
global $new_meta_boxes;
|
||
|
||
foreach($new_meta_boxes as $meta_box) {
|
||
$meta_box_value = get_post_meta($post->ID, $meta_box['name'].'_value', true);
|
||
|
||
if($meta_box_value == "")
|
||
$meta_box_value = $meta_box['std'];
|
||
|
||
echo '<h3 style="font-size: 14px; padding: 9px 0; margin: 0; line-height: 1.4;">'.$meta_box['title'].'</h3>';
|
||
echo '<textarea cols="60" rows="3" style="width:100%" name="'.$meta_box['name'].'_value">'.$meta_box_value.'</textarea><br/>';
|
||
}
|
||
|
||
echo '<input type="hidden" name="metaboxes_nonce" id="metaboxes_nonce" value="'.wp_create_nonce( plugin_basename(__FILE__) ).'" />';
|
||
}
|
||
|
||
function wpdocs_save_meta_box( $post_id ) {
|
||
global $new_meta_boxes;
|
||
|
||
if ( empty( $_POST['metaboxes_nonce'] )||!wp_verify_nonce( $_POST['metaboxes_nonce'], plugin_basename(__FILE__) ))
|
||
return;
|
||
|
||
if ( !current_user_can( 'edit_posts', $post_id ))
|
||
return;
|
||
|
||
foreach($new_meta_boxes as $meta_box) {
|
||
$data = $_POST[$meta_box['name'].'_value'];
|
||
|
||
if($data == "")
|
||
delete_post_meta($post_id, $meta_box['name'].'_value', get_post_meta($post_id, $meta_box['name'].'_value', true));
|
||
else
|
||
update_post_meta($post_id, $meta_box['name'].'_value', $data);
|
||
}
|
||
}
|
||
add_action( 'save_post', 'wpdocs_save_meta_box' ); |