社区新闻

WordPress 能力 API 简介

自从WordPress 核心 AI 团队正式宣布成立以来,该团队一直在进行的最令人兴奋的项目之一就是全新的能力 API。能力 API 是一个一流的、跨上下文的函数式 API,其他工具和应用程序可以使用它与 WordPress 进行交互。

该 API 旨在为 WordPress 核心、插件和主题定义和暴露其功能(或“能力”)的方式带来统一、可发现且安全的方法。能力 API 的首次实现将包含在 WordPress 6.9 中,为更顺畅的开发工作流程、自动化新可能性和 AI 集成奠定基础。

能力 API 提供了一个中央注册表,WordPress 功能(或能力)以机器可读且对人类友好的格式在其中注册。这意味着能力不仅对开发者可发现,还可以被不同平台(包括AI 代理)的自动化工具以编程方式访问。

该项目的关键目标是:

  • 可发现性:通过标准接口列出和检查所有可用能力。
  • 互操作性:统一的模式使不相关的组件能够组合工作流
  • 安全优先:对谁或什么可以调用能力进行明确的权限控制。
  • 渐进式采用:Composer包插件开始,计划平滑迁移到 WordPress 核心。

可以将其视为 WordPress 或任何插件/主题能做什么的一站式商店,以一种每个人(和每件事物)都能理解的方式注册。

你可以在这个 GitHub 仓库中找到插件代码的完整副本。

列出所有 URL

该插件本身是极简的。它注册一个工具子菜单项,打开一个管理页面,你可以在其中选择列出 WordPress 站点上所有文章、页面或自定义文章类型的 URL。

列出所有 URL 管理页面 列出所有 URL 管理页面渲染 URL 列表

有一个主要函数负责获取和显示数据,即 list_all_urls_generate_url_list() 函数。该函数接收两个参数,并生成要在页面上显示的 URL 列表。

/**
* 根据提供的参数生成 URL 列表
* 可选地使它们成为可点击的链接
*
* @param array $arguments 自定义 URL 生成的参数。
* @param bool  $makelinks 是否返回可点击链接或纯 URL(已转义)。
*
* @return array 生成的 URL 列表。
*/
function list_all_urls_generate_url_list( array $arguments = array(), bool $makelinks = false ): array {
   $default_args   = array(
           'post_type'      => 'post',
           'posts_per_page' => - 1,
           'post_status'    => 'publish',
   );
   $args           = wp_parse_args( $arguments, $default_args );
   $posts          = get_posts( $args );

   $links = array();
   foreach ( $posts as $post ) {
      $permalink = get_permalink( $post );
      if ( $makelinks ) {
         $links[] = '<a href="' . esc_url( $permalink ) . '">' . esc_html( $permalink ) . '</a>';
      } else {
         $links[] = esc_html( $permalink );
      }
   }

   return $links;
}

在内部,此函数调用 WordPress 的 get_posts() 函数来检索实际数据,然后根据是否应显示为可点击链接列表进行格式化。

我想添加几个较小的生活质量改进,例如限制具有大量 URL 站点的结果、按类别或日期范围过滤 URL 以及导出返回的 URL 的选项。

然而,可能有用且较大的两个功能是:

  1. 一种在 WordPress 外部访问 URL 列表的方法(例如,一个 REST API 端点),以便将数据连接到外部服务。
  2. 一个允许任何人将 URL 列表添加到块编辑器任何实现(例如,在任何文章或页面中,甚至在模板中)的块。

要实现这一点,你需要设置一些东西。

如果你更喜欢浏览此解决方案的代码,可以通过查看 GitHub 仓库的 rest-blocks 分支来查看完整的实现。

你需要使用 register_rest_route() 注册一个自定义 REST API 路由和关联的 GET 端点,该端点应获取该路由的文章。

这将需要一个回调函数来获取数据,该函数又可以调用不带 $makelinks 参数的 list_all_urls_generate_url_list 来返回数据。

add_action( 'rest_api_init', 'list_all_urls_register_rest_route' );
function list_all_urls_register_rest_route (): void {
   register_rest_route(
           'list-all-urls/v1',
           '/urls',
           array(
               'methods' => 'GET',
               'callback' => 'list_all_urls_rest_fetch_all_urls',
               'args' => array(
                       'type' => array(
                               'validate_callback' => function( $param ) {
                                   return is_string( $param );
                               }
                       ),
               ),
           )
   );
}

function list_all_urls_rest_fetch_all_urls( $arguments ){
   if ( isset($arguments['type'] ) ) {
       $post_type = sanitize_text_field( wp_unslash( $arguments['type'] ) );
   } else {
       $post_type = 'any';
   }
   $args = array(
       'post_type'      => $post_type,
   );
   return list_all_urls_generate_url_list( $args );
}

对于自定义块,你可以使用 create-block 搭建块结构,然后利用自定义 REST API 端点和 api-fetch 包来获取块 Edit 组件的数据并在编辑器中渲染它。

export default function Edit() {
    const [urls, setUrls] = useState([]);

    useEffect(() => {
        apiFetch( { path: '/list-all-urls/v1/urls' } ).then( ( urls ) => {
            setUrls( urls );
        } );
    }, []);

    if ( ! urls ) {
        return (
            <div { ...useBlockProps() }>
                <p>{ __(
                    'Loading...',
                    'list-all-urls'
                ) }</p>
            </div>
        );
    }

    let urlsList = urls.map( ( url ) => {
        return <li><a href={ url }>{ url }</a></li>;
    });

    return (
        <div { ...useBlockProps() }>
            <ul>{ urlsList }</ul>
        </div>
    );
}

将其设为动态块是有意义的,该块在 render.php 文件中调用 list_all_urls_generate_url_list 函数,该文件配置为在前端渲染。

<?php
/**
* 列出所有 URL 块的渲染文件。
*/
$block_attributes = get_block_wrapper_attributes();
$urls = list_all_urls_generate_url_list( array( 'post_type' => 'any' ), true );
$urlList = '';
foreach ( $urls as $url ) {
   $urlList .= '<li>' .  wp_kses_post( $url ) . '</li>';
}
?>
<div <?php echo $block_attributes; ?>>
   <ul>
       <?php echo $urlList; ?>
   </ul>
</div>

为了使用户能够选择要生成 URL 列表的文章类型,并向列表添加可点击链接,你需要更新块以支持其他属性。

然而,这已经是一段相当多的代码,仅仅是为了使通过 REST API 和块编辑器访问列出 URL 功能成为可能,同时还要维护现有的管理页面。

这是自定义能力的完美示例。你可以在一个地方注册所有内容,然后在需要的地方访问和执行它。

安装能力 API

要开始使用,请确保你使用的是 API 的最新版本。

目前,有三种方法可以安装和测试能力 API:

  • 你可以将 GitHub 仓库克隆到你的 wp-content/plugins 目录,安装任何依赖项,运行构建步骤,然后激活插件。
  • 你可以从 GitHub 仓库的发布页面下载最新版本,然后上传并安装插件 zip 文件。
  • 你可以将 Composer 包作为插件或主题的依赖项引入。

测试能力 API 的一种方法是克隆 GitHub 仓库,因为这确保你拥有 trunk 分支上的最新代码。

$ git clone git@github.com:WordPress/abilities-api.git
$ cd abilities-api
$ composer install
$ npm install
$ npm run build 

另一个选项是安装 composer 包。这允许你的插件访问能力 API 的最新稳定版本及其可能提供的任何新功能,同时与核心中可用的内容兼容。

$ cd /wp-content/plugins/list-all-urls
$ composer require wordpress/abilities-api

能力来救援

在这种情况下,自定义能力可以处理大部分(如果不是全部)所需的核心功能。PHP 文档包含了你需要了解的关于如何在 PHP 中注册和使用能力的所有信息,但让我们看看“列出所有 URL”需要什么。

和之前一样,如果你更喜欢浏览此实现的完整代码,可以查看 GitHub 仓库的 abilities 分支

在 PHP 中注册能力可以使用 wp_register_ability() 函数。为确保能力正确注册,此函数应始终在挂钩到 wp_abilities_api_init 操作钩子的回调中调用。

add_action( 'wp_abilities_api_init', 'list_all_urls_register_abilities' );
/**
 * 注册列出所有 URL 的能力
 *
 * @return void
 */
function list_all_urls_register_abilities() {
    wp_register_ability(
        'list-all-urls/urls',
        array(
            'label' => __( '获取所有 URL', 'list-all-urls' ),
            'description' => __( '从 WordPress 站点检索 URL 列表,可选地作为可点击的锚链接。', 'list-all-urls' ),
            'category' => 'site',
            'input_schema' => array(
                'type' => 'object',
                'properties' => array(
                        'post_type' => array(
                                'type' => 'string',
                                'description' => '要从中检索 URL 的文章类型(例如,post、page、自定义文章类型)。',
                            ),
                        'posts_per_page' => array(
                                'type' => 'integer',
                                'description' => '要检索的文章数量。使用 -1 检索所有文章。',
                        ),
                        'post_status' => array(
                                'type' => 'string',
                                'description' => '要检索的文章状态(例如,publish、draft)。',
                        ),
                        'makelinks' => array(
                                'type' => 'boolean',
                                'description' => '是否将 URL 作为可点击的锚链接返回。',
                        ),
                ),
            ),
            'output_schema' => array(
                    'type' => 'object',
                    'properties' => array(
                            'url' => array(
                                    'type' => 'string',
                                    'description' => 'URL 或指向 URL 的可点击链接'
                            )
                    )
            ),
            'execute_callback' => 'list_all_urls_generate_url_list',
            'permission_callback' => '__return_true',
        )
    );
}

深入探讨能力注册

注册能力需要一个唯一标识符(list-all-urls/urls)和一个参数数组。大多数参数是可选的,但必需的是:

  • label:能力的人类可读名称。
  • description:能力功能的简要描述。
  • category:能力所属的类别。你可以注册自己的能力类别,但在这里,我使用可用的 site 类别。
  • output_schema:定义能力返回数据结构的模式。
  • execute_callback:执行能力时将调用的函数。
  • permission_callback:确定当前用户是否有权限执行能力的函数。

input_schema 参数是可选的,但如果你的能力需要输入参数,则强烈推荐。在我的例子中,我想传入与 list_all_urls_generate_url_list() 接受的相同的参数。

设置这些模式不仅让能力知道它可以期望和返回什么数据,还支持对该数据的自动验证。例如,如果我尝试在输入中传递一个不计算为整数的 posts_per_page 值,它将自动导致验证错误并阻止能力执行。

你会注意到,我使用原始的 list_all_urls_generate_url_list() 函数作为能力 execute_callback,所以那里不需要任何更改。

在 PHP 中获取和使用能力

下一步是更新内部管理页面以获取和执行能力。

这里真正需要更改的是设置 $input 数组、获取能力并执行它。

$input = array(
    'post_type'      => $post_type,
    'posts_per_page' => - 1,
    'post_status'    => 'publish',
    'makelinks'      => $makelinks,
);

$urlsAbility = wp_get_ability( 'list-all-urls/urls' );
$urls = $urlsAbility->execute( $input );

现在,我真正喜欢这个实现的地方在于它的可扩展性。如果我想让其他插件或主题开发者利用此功能,我只需要记录能力标识符、输入模式和输出模式。

还有一些函数可用于检查哪些能力可用——wp_get_abilities()——以及特定能力是否可用——wp_has_ability()

在开发过程中,你可以使用 WP-CLIshell 命令来获取和检查单个能力。

例如,检查当前有哪些能力可用:

$ wp shell
wp> $abilities = wp_get_abilities();

检查特定能力是否可用:

$ wp shell
wp> $found = wp_has_ability('list-all-urls/urls');
=> bool(true)

获取单个能力:

当获取所有能力和单个能力时,会返回整个能力对象,因此你能够看到能力执行什么功能,以及预期的输入和输出是什么。

$ wp shell
wp> $ability = wp_get_ability('list-all-urls/urls');
=> object(WP_Ability)#2698 (9) {
  ["name":protected]=>
  string(18) "list-all-urls/urls"
  ["label":protected]=>
  string(12) "Get All URLs"
  ["description":protected]=>
  string(87) "Retrieves a list of URLs from the WordPress site, optionally as clickable anchor links."
  ["category":protected]=>
  string(13) "list-all-urls"
  ["input_schema":protected]=>
  array(2) {
    ["type"]=>
    string(6) "object"
    ["properties"]=>
    array(4) {
      ["post_type"]=>
      array(2) {
        ["type"]=>
        string(6) "string"
        ["description"]=>
        string(73) "The post type to retrieve URLs from (e.g., post, page, custom post type)."
      }
      ["posts_per_page"]=>
      array(2) {
        ["type"]=>
        string(7) "integer"
        ["description"]=>
        string(58) "Number of posts to retrieve. Use -1 to retrieve all posts."
      }
      ["post_status"]=>
      array(2) {
        ["type"]=>
        string(6) "string"
        ["description"]=>
        string(59) "The status of the posts to retrieve (e.g., publish, draft)."
      }
      ["makelinks"]=>
      array(2) {
        ["type"]=>
        string(7) "boolean"
        ["description"]=>
        string(49) "Whether to return URLs as clickable anchor links."
      }
    }
  }
  ["output_schema":protected]=>
  array(2) {
    ["type"]=>
    string(6) "object"
    ["properties"]=>
    array(1) {
      ["url"]=>
      array(2) {
        ["type"]=>
        string(6) "string"
        ["description"]=>
        string(32) "URL or clickable link to the URL"
      }
    }
  }
  ["execute_callback":protected]=>
  string(31) "list_all_urls_generate_url_list"
  ["permission_callback":protected]=>
  string(13) "__return_true"
  ["meta":protected]=>
  array(2) {
    ["annotations"]=>
    array(3) {
      ["readonly"]=>
      NULL
      ["destructive"]=>