标签归档:WordPress

WordPress 禁止更新大版本,只允许小版本更新

两种方法,第一种是写在wp-config.php里面:

define( 'AUTOMATIC_UPDATER_DISABLED', false ); // 允许自动更新
define( 'WP_AUTO_UPDATE_CORE', 'minor' ); // 只更新小版本

第二种是写在主题的functions.php里面:

// 允许自动更新小版本,但禁止大版本更新
add_filter( 'allow_minor_auto_update', '__return_true' );
add_filter( 'allow_major_auto_update', '__return_false' );

推荐第一种,第二种的话如果更换主题就会失效。当然两个地方都写上也不是不行🤨。

科普一下WordPress的版本规则,例如版本6.6.2,6.6是一个大版本,.2是一个小版本,禁止大版本更新就是禁止从6.6升级到6.7或7.x;允许小版本更新,就是允许从6.6.2升级到6.6.3这样的。

参考链接:https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/

给 WordPress 添加文章浏览量统计功能

前几天给网站添加了文章的浏览量统计功能,但统计了几天后发现,统计了个寂寞,来访的除了蜘蛛就是自己,意义不大,索性删除了罢。想要统计,后面可以接入专门的网站统计系统,比如Google Analytics。下面把wordpress文章统计代码分享出来。

下面的代码我是加到functions.php里面的,当然,也可以做成插件。

/**
 * 获取文章阅读量
 *
 * @since 2024.10.25
 *
 */
function getPostViews($postID){
    $count_key = 'post_views_count';
    $count = get_post_meta($postID, $count_key, true);
    if($count==''){
        delete_post_meta($postID, $count_key);
        add_post_meta($postID, $count_key, '0');
        return "0";
    }
    return $count;
}

/**
 * 更新文章阅读量
 *
 * @since 2024.10.25
 *
 */
function setPostViews($postID) {
    // 检查用户是否已登录
    if (is_user_logged_in()) {
        return; // 已登录用户,不执行统计
    }
    $count_key = 'post_views_count';
    $count = get_post_meta($postID, $count_key, true);
    if($count==''){
        $count = 0;
        delete_post_meta($postID, $count_key);
        add_post_meta($postID, $count_key, '0');
    }else{
        $count++;
        update_post_meta($postID, $count_key, $count);
    }
}
// Remove issues with prefetching adding extra views
remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);

使用方法:

setPostViews函数加到single.php里面,如果有访问就会调用该函数实现文章阅读统计。然后在适当的地方调用getPostViews函数用于获取文章的阅读量。

当然,也可以完善setPostViews函数,使之不统计蜘蛛的流量,要实现也不难,通过useragent来判断即可。但既然觉得这事没有意义,也就懒得去做了。

补充:

既然去掉了该功能,那么数据库里产生的统计数据就要删除掉:

DELETE FROM wp_postmeta WHERE meta_key = 'post_views_count';

PHP生成WordPress的Nginx跳转规则

建站初期总有一些事情考虑不周,前几天调整了固定链接,从/category/id.html调整为/archive/id,然后总觉得哪里不踏实。趁着周末去考试的时间想了一下,如果固定链接依赖于文章ID或分类,那么一旦ID或分类发生变化,文章的固定链接就会失效。再者,从长远来说,将来如果要改版或重构网站,基于ID或分类的链接将变得不稳定。

由此,可以得出结论,固定链接最好不要依赖文章ID和分类,而是要依赖于文章的内容。这样的固定链接显得更稳定,而且具有更好的可读性,有利于SEO优化,分享链接时,链接本身也变得更有意义。

所以,决定把本站的固定链接改成基于postname的形式。修改固定链接后,要把之前的文章链接做一个跳转,但是由于文章有点多,手动修改很费时间,于是动手写了一段PHP代码,用于生成Nginx的跳转规则,代码如下:

<?php
// 数据库连接设置
$servername = "localhost"; // 数据库主机
$username = "your_username"; // 数据库用户名
$password = "your_password"; // 数据库密码
$dbname = "your_database"; // 数据库名

// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);

// 检查连接
if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
}

// 查询已发布的文章
$sql = "SELECT ID, post_title, post_name FROM wp_posts WHERE post_status='publish' AND post_type='post'";
$result = $conn->query($sql);

// 检查查询结果
if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        $id = $row['ID'];
        $post_name = $row['post_name'];
        // 生成 Nginx 重定向规则
        echo "rewrite ^/archives/$id$ /$post_name permanent;" . "<br>";
    }
} else {
    echo "没有找到已发布的文章。";
}

// 关闭连接
$conn->close();
?>

运行这段代码,就会在浏览器页面输出重定向规则,我们把它复制到nginx配置里就可以了。

location /archives/ {
    rewrite ^/archives/3681$ /letting-go-of-tech-obsession permanent;
    # 为了便于展示,这里只粘贴了一条规则...
}

后记:这种基于postname的permalink就是写文章的时候麻烦一点,写完后,还要再把标题翻译成英文,看个人取舍了。后面如果我忍受不了这多余的一步,也可能换成基于post_id的形式。使用postname还有一个好处理就是,如果要换成其它形式,WP可以自动实现跳转,而不用再另写跳转规则。

WordPress禁用指定endpoints,防止用户信息泄露

WordPress 在4.7.0版本之后将REST API插件集成到默认功能之中。REST API为WordPress的使用者提供了一个方便快捷的管理接口。在WordPress 4.7.0版本中,存在着一个越权漏洞,成功的利用这个漏洞,可以绕过管理员权限查看WordPress上所有发布过文章的用户信息列表。

虽然现在用的不是4.7.0这个版本,但是为了确保安全,最好还是把这两个rest api接口禁用掉吧。

禁用代码如下:

// 禁用用户端点,同时确保其他 REST API 功能正常
add_filter('rest_endpoints', function ($endpoints) {
    if (isset($endpoints['/wp/v2/users'])) {
        unset($endpoints['/wp/v2/users']);
    }
    if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
        unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
    }
    return $endpoints;
});

WordPress 网站根目录权限设置

在网站根目录下执行(针对debian/ubuntu,这俩系统的php-fpm用户名是www-data)

for directories

find . -type d -print0 | xargs -0 chmod 0755

for files

find . -type f -print0 | xargs -0 chmod 0644
chmod 600 wp-config.php
chown -R www-data:www-data *

如果wordpress站点健康检查提示无法自动更新,则执行下面这句,把当前文件夹也加入php-fpm用户组

chown -R www-data:www-data .

WordPress Custom Post Type 的 category 页面的模板文件命名规则

在使用自定义文章类型时,对应的 category 页面的文件名应该是taxonomy-{taxonomy}.php 。

例如有一个自定义 Post Type 的分类法是 product_category,那么该分类法对应的 category 页面的模板文件名就是taxonomy-product_category.php 。

如果该分类法下有一个分类,名为adva,那么该分类对应的模板文件名为taxonomy-product_category-adva.php 。


如果是默认的文章类型,即 post type 是 post ,它的 category 页面的名字就是category.php,默认的分类法其实就是category,但却不能写成taxonomy-category,只能自定义类型才能这么写。

如果默认分类法下有一个news的分类,那么对应的模板文件名就是category-news.php 。

wordpress category 和 archive 的区别

一个post type可以没有分类(category),但一定可以进行文章归档(archive)。

分类(category),是针对分类法(taxonomy)的,而归档而是针对文章的。

所以,模板命名上,category 总是和分类关联,比如news分类模板:category-news.php。

而 archive 总是和 post type 关联,比如有一个 custom post type 名字是 product,那么对应的归档页面就是 archive-product.php。

wordpress 实现文件下载功能

本想这个功能应该不难,但却费了好些工夫。

在wordpress中想要实现点击下载,有两种方法

1、在下载链接上增加 `download=””` 属性

<a href="<?php echo $download_url; ?>" download="">下载</a>

但是这种方法查资料发现,只支持chrome和firefox,兼容性不好。

2、第二种方法也是费了好大劲才查找到的资料,直接上代码

<?php

// add the download ID query var which we'll set in the rewrite rule
function wpd_query_var($query_vars) {
    $query_vars[] = 'download_id';
    return $query_vars;
}
add_filter('query_vars', 'wpd_query_var');

// add rewrite rule
function wpd_rewrite() {
    add_rewrite_rule(
        'download/([0-9]+)/?$',
        'index.php?download_id=$matches[1]',
        'top'
    );
}
add_action('init', 'wpd_rewrite');

// check for download_id and output the file
function wpd_request($wp) {
    if (array_key_exists('download_id', $wp->query_vars)) {
        // 在此实现下载代码的书写
        // output your headers and file here
        // ID is in $wp->query_vars['download_id']
        // then exit to halt any further execution
        $download_id = $wp->query_vars['download_id'];
        $filename =  get_attached_file($download_id);
        if (file_exists($filename)) {
            include get_template_directory() . '/inc/download.php';
            downloadFile($filename);
        } else {
            wp_die(__('要下载的文件不存在!'));
        }
    }
}
add_action('parse_query', 'wpd_request');

下载时,引用了一个inc目录下的download.php,代码如下:

<?php

/**
 * @param $filePath //下载文件的路径
 * @param int $readBuffer //分段下载 每次下载的字节数 默认1024bytes
 * @param array $allowExt //允许下载的文件类型
 * @return void
 */
function downloadFile($filePath, $readBuffer = 1024, $allowExt = ['jpeg', 'jpg', 'peg', 'gif', 'zip', 'rar', 'txt', 'pdf']) {
    //检测下载文件是否存在 并且可读
    if (!is_file($filePath) && !is_readable($filePath)) {
        return false;
    }

    //检测文件类型是否允许下载
    $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowExt)) {
        return false;
    }

    //设置头信息
    //声明浏览器输出的是字节流
    header('Content-Type: application/octet-stream');

    //声明浏览器返回大小是按字节进行计算
    header('Accept-Ranges:bytes');

    //告诉浏览器文件的总大小
    $fileSize = filesize($filePath); //坑 filesize 如果超过2G 低版本php会返回负数
    header('Content-Length:' . $fileSize); //注意是'Content-Length:' 非Accept-Length

    //声明下载文件的名称
    //   $useragent = $_SERVER['HTTP_USER_AGENT'];
    //     if (strpos($useragent, 'baiduboxapp')) {
    //           $encoded_filename = $filePath;
    //     } else {
    //           $encoded_filename = urlencode($filePath);
    //           $encoded_filename = str_replace("+", "%20", $encoded_filename);
    //     }

    header('Content-Disposition:attachment;filename=' . basename($filePath)); //声明作为附件处理和下载后文件的名称

    //获取文件内容
    $handle = fopen($filePath, 'rb'); //二进制文件用‘rb’模式读取
    while (!feof($handle)) { //循环到文件末尾 规定每次读取(向浏览器输出为$readBuffer设置的字节数)
        echo fread($handle, $readBuffer);
    }
    fclose($handle); //关闭文件句柄
    exit;
}

这样,下载链接只要按如下写法即可实现下载:

<a href="/download/<?php echo $ids[$i]; ?>">点击下载</a>

$ids[$i]表示wordpress附件的id

3、注意到还有一种方式似乎代码更少一些,代码如下:

<?php
add_action('init', function () {
    add_rewrite_endpoint('download', EP_ROOT);
    add_action('template_redirect', function () {
        if ($pid = get_query_var('download')) {
            $found = false;
            if ($file = wp_get_attachment_image_src(absint($pid), 'full')) {
                $found = true;
                header("Content-Disposition: attachment; filename=" . basename($file[0]));
                readfile($file[0]);
                exit();
            }
            if (!$found)
                wp_die(__('Image file not found!'));
        }
    });
});

但是这种方式只适合下载图片文件,可以把它改造一下,用来下载所有文件,改造后的代码如下:

<?php
add_action('init', function () {
    add_rewrite_endpoint('download', EP_ROOT);
    add_action('template_redirect', function () {
        if ($pid = get_query_var('download')) {
            $found = false;

            // 根据附件ID查询附件的真实路径
            $filename = get_attached_file($pid);
            if (file_exists($filename)) {
                $found = true;
                header("Content-Disposition: attachment; filename=" . basename($filename));
                readfile($filename);
                exit();
            }

            if (!$found)
                wp_die(__('Image file not found!'));
        }
    });
});

然后使用这种链接即可实现下载http://localhost/download/123/123表示附件的ID

和第二种方法,思路是一样的,都是添加通过URL重写规则,然后拦截该URL以实现下载功能。但是第三种方法代码更简练一些。

wordpress 自定义文章类型文章列表显示分类列

只需要在register_taxonomy函数的第三个参数中,添加show_admin_column=>true即可。代码如下:

<?php

/**
 * 为产品 post type 添加分类功能
 */
add_action('init', 'my_taxonomies_product', 0);
function my_taxonomies_product() {
    $labels = array(
        'name'              => _x('产品分类', 'taxonomy 名称'),
        'singular_name'     => _x('产品分类', 'taxonomy 单数名称'),
        'search_items'      => __('搜索产品分类'),
        'all_items'         => __('所有产品分类'),
        'parent_item'       => __('该产品分类的上级分类'),
        'parent_item_colon' => __('该产品分类的上级分类:'),
        'edit_item'         => __('编辑产品分类'),
        'update_item'       => __('更新产品分类'),
        'add_new_item'      => __('添加新的产品分类'),
        'new_item_name'     => __('新产品分类'),
        'menu_name'         => __('产品分类'),
    );
    $args = array(
        'labels' => $labels,
        'hierarchical' => true,
        'show_admin_column' => true,
    );
    register_taxonomy('product_category', 'product', $args);
}

如果要在列表中添加自定义字段,即wp_postmeta表中的字段(通过meta_box添加的),可以使用下面的代码:

<?php
// 在列表中把加的字段显示出来
add_action("manage_posts_custom_column",  "product_custom_columns");
function product_custom_columns($column) {
    global $post;
    switch ($column) {
        case "product_director":
            echo get_post_meta($post->ID, '_product_director', true);
            break;
    }
}

add_filter("manage_edit-product_columns", "movie_edit_columns");
function movie_edit_columns($columns) {
    $columns['product_director'] = '产品分类';
    return $columns;
}

wordpress 自定义文章类型文章列表显示分类列

只需要在register_taxonomy函数的第三个参数中,添加show_admin_column=>true即可。代码如下:

<?php

/**
 * 为产品 post type 添加分类功能
 */
add_action('init', 'my_taxonomies_product', 0);
function my_taxonomies_product() {
    $labels = array(
        'name'              => _x('产品分类', 'taxonomy 名称'),
        'singular_name'     => _x('产品分类', 'taxonomy 单数名称'),
        'search_items'      => __('搜索产品分类'),
        'all_items'         => __('所有产品分类'),
        'parent_item'       => __('该产品分类的上级分类'),
        'parent_item_colon' => __('该产品分类的上级分类:'),
        'edit_item'         => __('编辑产品分类'),
        'update_item'       => __('更新产品分类'),
        'add_new_item'      => __('添加新的产品分类'),
        'new_item_name'     => __('新产品分类'),
        'menu_name'         => __('产品分类'),
    );
    $args = array(
        'labels' => $labels,
        'hierarchical' => true,
        'show_admin_column' => true,
    );
    register_taxonomy('product_category', 'product', $args);
}

如果要在列表中添加自定义字段,即wp_postmeta表中的字段(通过meta_box添加的),可以使用下面的代码:

<?php
// 在列表中把加的字段显示出来
add_action("manage_posts_custom_column",  "product_custom_columns");
function product_custom_columns($column) {
    global $post;
    switch ($column) {
        case "product_director":
            echo get_post_meta($post->ID, '_product_director', true);
            break;
    }
}

add_filter("manage_edit-product_columns", "movie_edit_columns");
function movie_edit_columns($columns) {
    $columns['product_director'] = '产品分类';
    return $columns;
}

wordpress 自定义文章类型添加分类筛选功能

想实现的效果如下图:

自定义文章类型添加分类筛选功能

代码如下:

<?php
add_action('restrict_manage_posts', 'product_type_filter');
function product_type_filter() {
    global $typenow;
    $post_type = 'product'; // 发布类型
    $taxonomy = 'product_category'; // 该类型对应的分类法
    if ($typenow == $post_type) {
        $selected = isset($_GET[$taxonomy]) ? $_GET[$taxonomy] : '';
        $info_taxonomy = get_taxonomy($taxonomy);
        wp_dropdown_categories(array(
            'show_option_all' => __("所有{$info_taxonomy->label}"),
            'taxonomy' => $taxonomy,
            'name' => $taxonomy,
            'orderby' => 'name',
            'selected' => $selected,
            'value_field' => 'slug',
            'show_count' => true,
            'hide_empty' => true,
        ));
    };
}

wordpress 自定义文章类型的固定链接

自定义文章类型无法在后台设置固定链接格式,可以在 functions.php 中通过代码来实现。代码如下:

<?php
/**
 * 实现 solution 文章类型的 URL 重写
 */
add_filter('post_type_link', 'custom_solution_link', 1, 3);
function custom_solution_link($link, $post) {
    if ($post->post_type == 'solution') {
        return home_url('solution/' . $post->ID . '.html');
    } else {
        return $link;
    }
}

比较重要的一点:

代码添加完成后,一定要在后台 “ 固定链接设置” 页面,点击一下 “保存更改” 按钮才能生效!!