WordPress重要机制

1. Parse机制

严格说,这个应不算一个“机制”。因为“机制”应是若干部分协同作用的结果,而在此阐述的wp_parse_request()与wp_parse_args()只是一个函数,但由于其重要性、通用性,使之承载了WordPress高超的设计理念,并成为其他“机制”所不可或缺的基石。

(1) wp_parse_args()

定义在/wp-includes/functions.php,可用于将给定的$arg与$default合并。详见这里

(2) wp_parse_request()

WordPress网址路由解析查询条件函数

2. Hook机制(Action与Filter)

Action hook与Filter hook是Hook的两种具体形式。实际上,不让Filter hook返回值时,也可将其当Action hook来用。

(1) 机制流程

埋钩子

在需要埋下钩子的地方插入do_action('hook');apply_filter('hook');

定义需要在埋钩处运行的函数

function custom_func() {
    code here;
}

挂钩子

将自定义函数挂载至埋钩处:add_action('hook',custom_function);add_filter('hook',custom_function);

(2) 具体实例(’widget_tag_cloud_args’ filter hook)

在/wp-includes/category-template.php中定义了wp_tag_cloud():

function wp_tag_cloud( $args = '' ) {
    $defaults = array(
        'smallest' => 8, 'largest' => 22, 'unit' => 'pt', 'number' => 45,
        'format' => 'flat', 'separator' => "n", 'orderby' => 'name', 'order' => 'ASC',
        'exclude' => '', 'include' => '', 'link' => 'view', 'taxonomy' => 'post_tag', 'echo' => true
    );
    $args = wp_parse_args( $args, $defaults );
            ...
}

在/wp-includes/default-widget.php中,定义了Tag cloud widget class:class WP_Widget_Tag_Cloud extends WP_Widget。其中关键的一句是:

wp_tag_cloud( apply_filters('widget_tag_cloud_args', array('taxonomy' => $current_taxonomy) ) );

复在theme的functions.php中定义:

function hlstar_tag_cloud_widget($args) {
    $args['number'] = 0; //adding a 0 will display all tags
    $args['largest'] = 16; //largest tag
    $args['format'] = 'list'; //ul with a class of wp-tag-cloud
    return $args;
}
add_filter( 'widget_tag_cloud_args', 'hlstar_tag_cloud_widget' );

因此最后的流程就是:函数hlstar_tag_cloud_widget()将array(‘taxonomy’ => $current_taxonomy)修改添加后,这个数组作为参数传递给wp_tag_cloud()函数;在这个函数内部,该数组参数首先由wp_parse_args()函数处理,将其与wp_tag_cloud()函数内部所定义的$default数组值合并,然后再进行处理。

这里说明一下,函数apply_filters()参数列表中的array('taxonomy' => $current_taxonomy)是一个实际参数,具体地说,是一个数组常量。作为apply_filters()的(实际)参数,它由apply_filters()函数传递给hlstar_tag_cloud_widget()函数进行修改处理,再由hlstar_tag_cloud_widget()返回(return)给apply_filters(),再作为参数返回给wp_tag_cloud()函数。

3. WordPress Theme Features机制

研究WordPress Twentythirteen主题的header时,你会发现在style.css文件中并未对header的背景图片有任何指定。这里的奥妙在于,这个header的不少样式属性是在twentythirteen/inc/custom-header.php中通过函数add_theme_support()指定的。

Theme Features是在WordPress Theme作者在设计并编码好总体的主题布局表现框架后,对其中一些高度模块化、可重用的网页组件,如header、sidebar等等,对于它们的具体实现或样式展现,由WordPress核心提供一个统一的注册实现机制,而不用主题作者重新编码实现。现就WordPress Theme Features机制的具体实现阐述如下。

(1) add_theme_support()

add_theme_support()作为WordPress core定义的函数,定义于/wp-includes/theme.php文件中,通常在主题的function.php文件或主题的/inc目录下的文件中调用,或在plugin中挂载到action hook之上(通常被挂至after_setup_theme动作钩子,init钩子可能会太迟)。

该函数的基本用法是<?php add_theme_support( $feature, $arguments ); ?>,其中$feature参数即用于指定将要自定义的theme feature,包括:

  • post-formats
  • post-thumbnails
  • custom-background
  • custom-header
  • automatic-feed-links
  • menus
  • html5

而$arguments参数则具体指定各个feature的具体参数。

以custom header为例,WordPress Twentythirteen Theme在/twentythirteen/inc/custom-header.php文件中给出了如下代码:

/**
 * Set up the WordPress core custom header arguments and settings.
 *
 * @uses add_theme_support() to register support for 3.4 and up.
 * @uses twentythirteen_header_style() to style front-end.
 * @uses twentythirteen_admin_header_style() to style wp-admin form.
 * @uses twentythirteen_admin_header_image() to add custom markup to wp-admin form.
 * @uses register_default_headers() to set up the bundled header images.
 *
 * @since Twenty Thirteen 1.0
 *
 * @return void
 */
function twentythirteen_custom_header_setup() {
    $args = array(
        // Text color and image (empty to use none).
        'default-text-color'     => '220e10',
        'default-image'          => '%s/images/headers/circle.png',

        // Set height and width, with a maximum value for the width.
        'height'                 => 230,
        'width'                  => 1600,

        // Callbacks for styling the header and the admin preview.
        'wp-head-callback'       => 'twentythirteen_header_style',
        'admin-head-callback'    => 'twentythirteen_admin_header_style',
        'admin-preview-callback' => 'twentythirteen_admin_header_image',
    );

    add_theme_support( 'custom-header', $args );

    /*
     * Default custom headers packaged with the theme.
     * %s is a placeholder for the theme template directory URI.
     */
    register_default_headers( array(
        'circle' => array(
            'url'           => '%s/images/headers/circle.png',
            'thumbnail_url' => '%s/images/headers/circle-thumbnail.png',
            'description'   => _x( 'Circle', 'header image description', 'twentythirteen' )
        ),
        'diamond' => array(
            'url'           => '%s/images/headers/diamond.png',
            'thumbnail_url' => '%s/images/headers/diamond-thumbnail.png',
            'description'   => _x( 'Diamond', 'header image description', 'twentythirteen' )
        ),
        'star' => array(
            'url'           => '%s/images/headers/star.png',
            'thumbnail_url' => '%s/images/headers/star-thumbnail.png',
            'description'   => _x( 'Star', 'header image description', 'twentythirteen' )
        ),
    ) );
}
add_action( 'after_setup_theme', 'twentythirteen_custom_header_setup', 11 );

(2) Sidebar与Widget机制

在具体的页面模板中,通过get_sidebar()函数引入sidebar。Widget一般放置于sidebar中,但sidebar中可以没有widget。

Sidebar

Sidebar是页面中的一块div框区域,可以放置Widget与其他任意内容。Sidebar可以位于页面中的任何位置,不一定要在side上。Sidebar一般由单个具体主题文件夹下的sidebar-{slug}.php文件实现。

为了在页面中引入作为widget container的sidebar,首先需要在theme文件(一般就是单个具体theme文件夹下的functions.php文件)中使用register_sidebar()函数向WordPress core注册sidebar。下面是WordPress官方twentythirteen主题中的代码。其中值得注意的是’id’的取值,它将在后面作为参数传递给dynamic_sidebar()函数。

/**
 * Register two widget areas.
 *
 * @since Twenty Thirteen 1.0
 *
 * @return void
 */
function twentythirteen_widgets_init() {
    register_sidebar( array(
        'name'          => __( 'Main Widget Area', 'twentythirteen' ),
        'id'            => 'sidebar-1',
        'description'   => __( 'Appears in the footer section of the site.', 'twentythirteen' ),
        'before_widget' => '<aside id="%1$s" class="widget %2$s">',
        'after_widget'  => '</aside>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>',
    ) );

    register_sidebar( array(
        'name'          => __( 'Secondary Widget Area', 'twentythirteen' ),
        'id'            => 'sidebar-2',
        'description'   => __( 'Appears on posts and pages in the sidebar.', 'twentythirteen' ),
        'before_widget' => '<aside id="%1$s" class="widget %2$s">',
        'after_widget'  => '</aside>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>',
    ) );
}
add_action( 'widgets_init', 'twentythirteen_widgets_init' );

然后在单个具体的theme文件夹中,创建一个或多个sidebar-{slug}.php文件,供get_sidebar()函数调用。其中get_sidebar()默认调用sidebar.php,而get_sidebar(‘{slug}’)调用sidebar-{slug}.php。sidebar.php文件的主要内容是为sidebar构建HTML页面div容器,然后在容器内使用dynamic_sidebar(id)函数激活对应id的sidebar。当然,也可在sidebar的容器中直接添加其他内容,并且如果不需要在sidebar中添加widget,那么连之前的register_sidebar()与dynamic_sidebar()都可省略。

对于作为widget container的sidebar而言,当其被dynamic_sidebar()函数激活后,后台管理面板中即出现其对应的可视化管理界面,可对其进行属性设置,并对widget进行拖放操作。

Widget

WordPress提供了WP_Widget类(定义于/wp-includes/widgets.php),具体开发时需要实现这个类:

class My_Widget extends WP_Widget {

    /**
     * Sets up the widgets name etc
     */
    public function __construct() {
        // widget actual processes
    }

    /**
     * Outputs the content of the widget
     *
     * @param array $args
     * @param array $instance
     */
    public function widget( $args, $instance ) {
        // outputs the content of the widget
    }

    /**
     * Ouputs the options form on admin
     *
     * @param array $instance The widget options
     */
    public function form( $instance ) {
        // outputs the options form on admin
    }

    /**
     * Processing widget options on save
     *
     * @param array $new_instance The new options
     * @param array $old_instance The previous options
     */
    public function update( $new_instance, $old_instance ) {
        // processes widget options to be saved
    }
}

然后将其注册至WordPress core:

add_action( 'widgets_init', function(){
     register_widget( 'My_Widget' );
});

此时,每当程序运行至’widget_init’钩子之后,My_Widget就会出现在WordPress admin面板界面,将其拖曳至Sidebar即可。

(3) js/css文件的载入机制

用于载入style和script。

这个机制包含三个方面的因素:实现机制相关功能的动作、动作作用的对象、动作发生的地点。

  • 动作:wp_register/deregister、wp_enqueue/dequeue
  • 对象:script、style
  • 地点:front-end、admin、login

动作自然是由函数来实现,而这四大动作与对象的排列组合,可由以下8个常用函数来稍稍穷尽:

wp_register_script(), wp_deregister_script(), wp_enqueue_script(), wp_dequeue_script()
wp_register_style(), wp_deregister_style(), wp_enqueue_style(), wp_dequeue_style()

另一方面,动作发生的地点由Hook决定,因此,通过wp_enqueue_scriptsadmin_enqueue_scriptslogin_enqueue_scripts这三个钩子可以指定动作发生的地点。

因script、style仅是要被操作的内容,而此处主要讲解操作机制本身,因此下文就动作、钩子来分别加以讲解。具体使用时,根据对象的不同(script还是style)选用不同的函数即可。

注册/反注册

wp_register_script()、wp_register_style()作用主要有二:一是将实实在在的script/style资源文件(如jquery.js、normalize.css等)向WordPress Core注册并命名($handle),同时指明它们所在的存储路径,以供wp_enqueue_script()、wp_enqueue_style()直呼其名($handle)加以调用;二是指定不同script之间的依赖关系,这样在调用某script时,其所依赖的script也会由WordPress自动预先载入,而不必在代码中显式调用。

wp_register_script()定义于wp-includes/functions.wp-scripts.php,而wp-includes/script-loader.php对wp-includes/js/中预存的script资源文件统一进行了注册。

下面以wp_register_script()为例,讲解函数细节。

该函数的基本用法是:

<?php wp_register_script( $handle, $src, $deps, $ver, $in_footer ); ?>

其中,参数$src就是指定该script文件位于什么地方,$handle相当于给它取的一个名字,今后直呼其名即可实现调用。

最值得一说的是$deps:当在连续的scripts register中,指定了不同scripts之间的依赖关系,那么,今后当WordPress include某个script时,其所依赖的script也会被自动include进来。

$in_footer参数是指定script被置于body的最底部。使用时要注意两点:一是主题中要在合适的地方放置wp_footer()这个template tag;二是该script要在wp_head被触发前就置入(就算它是被置入到body最后)。

注意:对于实现载入js/css文件这一需求而言,wp_register并不是一个必需的步骤。如果被载入的js/css资源不是需要反复调用的话,其实可以直接仅仅使用wp_enqueue。

详情可参阅这里

载入/取消载入

wp_enqueue_script()定义在wp-includes/functions.wp-scripts.php

  • wp_enqueue_script()中,参数$handle为必填的string变量,其中当string中含有?字符时,可以在其后接上查询字符串。
  • wp_enqueue_script()一定要在wp_enqueue_scripts动作钩子处被调用,不然会引致不好的结果。参阅这里。因此,最佳实践一般是:(在theme的functions.php文件中)

    function theme_scripts_styles() {
    wp_enqueue_scripts(handle, );

    wp_enqueue_styles(handle, );

    }
    add_action( ‘wp_enqueue_scripts’, ‘theme_scripts_styles’ );

钩子

有三个钩子:

  • wp_enqueue_scripts
  • admin_enqueue_scripts
  • login_enqueue_scripts

不要被它们名字所迷惑,无论是挂载script还是挂载style,都是用这些钩子。

这些钩子在何处被埋下?

有一种感觉:这三个钩子只是指定在哪些页面(front-end/admin/login)上发生enqueue动作,而具体是enqueue到head还是footer,则具体由enqueue函数中的$in_footer参数联合wp_footer() template tag(如果$in_footer指定为true的话)来确定。

现在有一个问题:wp_enqueue_script钩子到底埋在了哪里?

以TwentyTwelve Theme为例,当theme页面调用wp_head()时,从wp-includes/.php中可知

/**
 * Fire the wp_head action
 *
 * @since 1.2.0
 * @uses do_action() Calls 'wp_head' hook.
 */
function wp_head() {
    do_action('wp_head');
}

即在wp_head()处埋下wp_head钩子,预备fire被挂载在wp_head上的函数。而在wp-includes/default-filters.php中可以看到

add_action( 'wp_head', 'wp_enqueue_scripts',1);

即在wp_head钩子处执行wp-includes/.php中的wp_enqueue_scripts()函数:

/**
 * Wrapper for do_action('wp_enqueue_scripts')
 *
 * Allows plugins to queue scripts for the front end using wp_enqueue_script().
 * Runs first in wp_head() where all is_home(), is_page(), etc. functions are available.
 *
 * @since 2.8
 */
function wp_enqueue_scripts() {
    do_action('wp_enqueue_scripts');
}

即在此处埋下wp_enqueue_scripts钩子。最后的调用链条即为:在theme file中调用wp_head()埋下wp_head钩子启动wp_enqueue_scripts()函数埋下wp_enqueue_scripts钩子。

应用实例

禁用fonts.googleapi.com,解决因受屏蔽而阻塞整体页面加载的问题

在wp-includes/script-loader.php中,函数wp_default_styles( &$styles )中有下述语句:

// Hotlink Open Sans, for now
$open_sans_font_url = "//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,300,400,600&subset=$subsets";
...
$styles->add( 'open-sans', $open_sans_font_url );

此时,从原理上说,就可以通过调用wp_enqueue_style('open-sans')来使用open-sans字体了。事实上,Twenty Thirteen theme似乎并未使用open-sans字体,而是在theme文件夹下的functions.php中引入了Source Sans Pro和Bitter这两种font(注意在twentythirteen_fonts_url()函数中引入了上述两种字体):

// Add Source Sans Pro and Bitter fonts, used in the main stylesheet.
wp_enqueue_style( 'twentythirteen-fonts', twentythirteen_fonts_url(), array(), null );

此处使用Google Web Fonts的实质就是,用wp_enqueue_style()引入Google服务器中的存储有font信息(@font-face)的css文件(网络存储路径由twentythirteen_fonts_url()指定),然后,在theme下的style.css中就可以直接使用该Google Web Font (Source Sans Pro and Bitter fonts)指定的font name来设定相关字体。

查看实际生成的客户端网页源代码,可以发现在<head>部分有这样一行:

<link rel='stylesheet' id='twentythirteen-fonts-css'  href='//fonts.googleapis.com/css?family=Source+Sans+Pro%3A300%2C400%2C700%2C300italic%2C400italic%2C700italic%7CBitter%3A400%2C700&#038;subset=latin%2Clatin-ext' type='text/css' media='all' />

同时,在theme下的style.css中,可见

h1,
h2,
h3,
h4,
h5,
h6 {
    clear: both;
    font-family: Bitter, Georgia, serif;
    line-height: 1.3;
}

以及

.site-description {
    font: 300 italic 24px "Source Sans Pro", Helvetica, sans-serif;
    margin: 0;
}

如果要禁用Google提供的字体(Source Sans Pro and Bitter fonts),可有以下几种方法:

  1. 修改style.css文件,全盘弃用Google提供的font name;
  2. 在theme functions.php中,删除或注释掉wp_enqueue_style( 'twentythirteen-fonts', twentythirteen_fonts_url(), array(), null );

考虑到在WordPress Core(wp-includes/script-loader.php)中尚有链接到Googleapi的Open-Sans字体

// Hotlink Open Sans, for now
$open_sans_font_url = "//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,300,400,600&subset=$subsets";

故可采用以下两种方法禁用该字体(直接修改WordPress Core文件可是个big no-no):

  1. 在theme style.css文件中压根就不使用Open-Sans字体;
  2. 在theme functions.php中反注册该style。

其中,反注册的代码如下:

function remove_open_sans() {
    wp_deregister_style( 'open-sans' );
    wp_register_style( 'open-sans', false );
    wp_enqueue_style('open-sans','');
}
add_action( 'init', 'remove_open_sans' );