标签: wordpress

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

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

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

    禁用代码如下:

    // language: php
    // 禁用用户端点,同时确保其他 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

    //language:bash
    find . -type d -print0 | xargs -0 chmod 0755

    for files

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

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

    //language:bash
    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!'));
            }
        });
    });

    然后使用这种链接即可实现下载https://lwbj.cn/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;
        }
    }
    

    比较重要的一点:

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

  • 使用PHP内置服务器运行wordpress造成的404问题

    使用内置服务器时,无法把请求转到发 index.php 。可以通过新加一个 routing.php 文件来实现该功能。

    运行 wordpress 时使用如下命令:

    E:\php\php-7.4.29-Win32-vc15-x64\php.exe -S localhost:80 routing.php

    routing.php文件的内容如下:

    <?php
    $root = $_SERVER['DOCUMENT_ROOT'];
    $path = '/' . ltrim(parse_url(urldecode($_SERVER['REQUEST_URI']))['path'], '/');
    if (file_exists($root . $path)) {
        // Enforces trailing slash, keeping links tidy in the admin
        if (is_dir($root . $path) && substr($path, -1) !== '/') {
            header("Location: $path/");
            exit;
        }
    
        // Runs PHP file if it exists
        if (strpos($path, '.php') !== false) {
            chdir(dirname($root . $path));
            require_once $root . $path;
        } else {
            return false;
        }
    } else {
        // Otherwise, run `index.php`
        chdir($root);
        require_once 'index.php';
    }
    

    参考:

    https://old.romaricpascal.is/writing-about/running-worpdress-with-php-built-in-server