从任意站点检索分类

时间:2020-09-22 作者:XedinUnknown

背景

我需要在多站点网络的任意站点中检索帖子的术语。这不一定是WP实例当时正在运行的站点。wp_get_object_terms() 需要检索术语的分类法。因此,要检索所有分类法的所有术语,必须首先检索分类法。这似乎是不可能的,因为插件注册的分类法可能没有在实例运行的当前(原始)站点上注册,因为插件本身在该站点上不会处于活动状态。

问题

如何以编程方式检索任意站点的所有分类?换句话说,考虑到我有一个网站$siteId, 如何检索该站点上的所有分类法?

或者,如何以编程方式检索任意站点中帖子的所有分类术语?换句话说,给定的职位$post 和站点$siteId, 如何检索该站点中该帖子所有分类法的所有术语?

2 个回复
SO网友:Tom J Nowell

how can I retrieve all taxonomy terms for a post in an arbitrary site programmatically? In other words, given post $post and site $siteId, how can I retrieve all terms for all taxonomies of that post in that site?

You can do this by omitting the taxonomy from WP_Term_Query:

$query = new WP_Term_Query( [
    \'object_ids\' => [ $post_id ]
] );
$terms = $query->get_terms();

Note that this gives you no information that the taxonomys of those terms were registered with, so permalinks and labels may not be available

How can I retrieve all taxonomies of an arbitrary site? In other words, given that I have a site $siteId, how can I retrieve all taxonomies on that site?

Unfortunately, no, there is no API or function call in PHP that will give you this information.

Generally needing this information is a sign that something has gone wrong at the high level architecture, and that simpler easier solutions exist. Every decision has trade offs and built in constraints, and this is one of those situations where breaching a constraint requires a tradeoff or an architectural change.

The root problem is that switch_to_blog changes the tables and globals, but it doesn\'t load any code from the other blog being switched to, so we need to recover what the registered posts types and taxonomies are from that site.

Work Arounds

However, there are work arounds, which one is best for you will depend on several things, and each has their own advantages and disadvantages.

  1. Retrieving all terms and inspecting their taxonomy field
  2. WP CLI
  3. REST API
  4. Dedicated REST API endpoints
  5. Pushing Data
  6. Uniform taxonomies, but with different visibility
  7. Preparing data in advance

These are all work arounds that try to cater for the edge case you\'ve asked about.

Of all of these, only WP CLI reliably gives all the needed information without performance and scaling issues. Whichever method is used, cache the result.

Retrieving all Terms

We can retrieve all terms in a site, then loop over them to identify the taxonomy fields:

$query = new WP_Term_Query([]);
$terms = $query->get_terms();
$taxonomies = wp_list_pluck( $terms, \'taxonomy\' );
$taxonomies = array_unique( $taxonomies );

However, there are some major problems with this:

  • it does not scale, pulling all terms into memory takes time and memory, eventually the site will have more than can be held leading either to hitting the execution time limit, or memory exhaustion
  • it only shows used taxonomies, if there are no categories then the category taxonomy will not show
  • this gives you no information that the taxonomy of those terms were registered with, so permalinks and labels may not be available

WP CLI

WP CLI can give you exactly what you need for both of your questions, if it\'s available. By assembling a command and calling it from PHP, you can programmatically retrieve the data you wish while avoiding a HTTP request.

  • we call WP CLI via either exec or proc_open
  • we specify which site we want via the --url parameter, we can get the URL via a standard API call such as get_site_url( $site_id )
  • We add the --format parameter to ensure WP CLI gives us machine readable results, either --format="csv" for a CSV string that functions such as fgetcsv etc can process, or --format="json" which json_decode can process. JSON will be easier
  • we parse the result in PHP

For example, here is the local copy of my site:

vagrant@vvv:/srv/www/tomjn/public_html$ wp taxonomy list
+----------------+------------------+-------------+---------------+---------------+--------------+--------+
| name           | label            | description | object_type   | show_tagcloud | hierarchical | public |
+----------------+------------------+-------------+---------------+---------------+--------------+--------+
| category       | Categories       |             | post          | 1             | 1            | 1      |
| post_tag       | Tags             |             | post          | 1             |              | 1      |
| nav_menu       | Navigation Menus |             | nav_menu_item |               |              |        |
| link_category  | Link Categories  |             | link          | 1             |              |        |
| post_format    | Formats          |             | post          |               |              | 1      |
| technology     | Technologies     |             | project       | 1             |              | 1      |
| tomjn_talk_tag | Talk Tags        |             | tomjn_talks   | 1             |              | 1      |
| series         | Series           |             | post          |               |              | 1      |
+----------------+------------------+-------------+---------------+---------------+--------------+--------+

I can also pass --format=json or --format=csv to get a machine parseable result.

I can target individual sites in a multisite install by passing the --url parameter

e.g.

wp taxonomy list --url="https://example.com/" --format="json"

I can then call this from PHP and parse the result to get a list of taxonomies.

The same is true of terms, e.g. listing

❯ wp term list category
+---------+------------------+-----------------+-----------------------+-------------+--------+-------+
| term_id | term_taxonomy_id | name            | slug                  | description | parent | count |
+---------+------------------+-----------------+-----------------------+-------------+--------+-------+
| 129     | 136              | Auto-Aggregated | auto-aggregated       |             | 0      | 1     |
| 245     | 276              | Big WP          | big-wp                |             | 4      | 0     |
| 95      | 100              | CSS             | css                   |             | 7      | 1     |
| 7       | 7                | Design          | design                |             | 0      | 3     |
| 30      | 32               | Development     | development           |             | 17     | 31    |
...

Or a posts categories:

❯ wp post term list 15 category --format=csv
term_id,name,slug,taxonomy
5,WordCamp,wordcamp,category

Be sure to set the right working path, and the wp is installed available and executable.

Also be careful, if you ask WP CLI for every post in the database, it will give you it, even if it takes 1 hour to run. Your request will have ran out of time long before then leading to an error. So don\'t always provide an upper bounds on how many posts you want, even if you don\'t expect to reach it. It\'s also possible to request more data than the server has memory to hold, WP CLI will crash with an out of memory error in those situations. E.g. importing a 5GB wxr import file, or requesting 10k posts.

However, if WP CLI isn\'t available...

REST API

You can query for taxonomies with the REST API. Every site supports it, but there are 2 caveats:

  • HTTP requests are expensive, and you can\'t bypass this cost with a PHP function as you need to load WP from scratch to get the registered taxonomies and post types you desired
  • Private and hidden taxonomies won\'t be shown, some taxonomies will only show with authentication, requiring you to add an authentication plugin

But if that\'s okay with you, you can use the built in discovery to discover post types and taxonomies.

Visit example.com/wp-json/wp/v2/taxonomies and you\'ll get a JSON list of public taxonomies, each object in the list has a types subfield that lists the post types it applies to.

You can also fetch a post this way via the /wp/v2/posts endpoint, but you\'ll get term IDs rather than term names back, likely requiring additional queries.

Custom Endpoint

You can try to sidestep the restrictions above by building a custom endpoint. This would allow you to return everything you needed using a single request.

To do this, call register_rest_route to register an endpoint, and return an array of keys and values from the callback. The API will encode these as JSON

Pushing The Data

Rather than retrieving this information from other sites in a multisite installation, it\'s much more efficient to have those sites push the information to you then caching the result. This is the most performant method.

Uniform taxonomies, but with different visibility

Some sites can take advantage of this particular possibility. If all sites have the same taxonomies registered then you can query all terms by switching blogs regardless of sites. But nobody said the taxonomies need to have the same options

For example, site A has a category taxonomy, and site B has a tags taxonomy. A does not use tags, and b does not use categories. So we register both taxonomies on both sites, but on A we make tags a hidden private taxonomy, and on B we make categories a hidden private taxonomy.

This work around won\'t be suitable for all situations though, and can\'t account for taxonomies registered by 3rd party plugins

Preparing data in advance

We don\'t know the other sites registered taxonomies and terms for each post because we didn\'t load that sites code. However, that site does. So why not make the site store this information somewhere it can be retrieved?

For example, a site could store an option containing the registered taxonomies and their attributes.

The same could be done for a posts terms, a post meta field can be updated on the save hook to contain a list of terms with their names, URLs, and the taxonomy they belong to, so that a term list can be recreated elsewhere without needing to know the registered taxonomies.

SO网友:XedinUnknown

使用WP_Term_Query 没有taxonomy 参数虽然wp_get_object_terms() 奇怪的是不允许省略taxonomy 参数WP_Term_Query 这是在引擎盖下使用的。

function getPostTerms(int $postId, int $siteId): array
{
  switch_to_blog($siteId);

  $postId = 123;
  $query = new WP_Term_Query([\'object_ids\' => [$postId]]);
  $terms = $query->get_terms();

  restore_current_blog();

  /* @var $terms WP_Term[] */
  return $terms;
}
这将返回post#123的所有术语。Caveat: 似乎只返回与至少一篇文章相关的术语。

如果目标是列出所有分类法,那么应该可以从这里迭代这些术语,并获得它们的分类法:

function getAllTaxonomies(int $siteId): array
{
  switch_to_blog($siteId);

  $query = new WP_Term_Query([]);
  $terms = $query->get_terms();

  $taxonomies = [];
  foreach ($terms as $term) {
    assert($term instanceof WP_Term);
    $taxonomy = $term->taxonomy;
    $taxonomies[$taxonomy] = true;
  }

  restore_current_blog();

  $taxonomies = array_keys($taxonomies);
  return $taxonomies;
}

这允许检索特定站点的所有分类法段塞。Caveat: 它似乎只返回至少有1个post与至少1个术语关联的分类法。这并不是最有效的方法,因为它会循环遍历所有术语,但通常一个分类法的术语不会超过十几个,所以在大多数情况下应该可以。

限制无法获取没有相关帖子的术语。因此,无法获取没有与至少1篇文章关联的术语的分类法段塞WP_Taxonomy 对象,因为不保证注册来自当前站点中未运行的插件的分类法

相关推荐

get taxonomies from terms

我有两种帖子类型文档、新闻项目以及每种帖子类型的类别,文档--PDF、Word、Excel、新闻项目--文章、版本,但它们共享相同的标记。文档—BOSS、EFF、CDF—新闻项目—BOSS、EFF、CDF—如何检索其帖子(或帖子计数。无论帖子类型如何)具有指定标记的类别列表,例如。BOSS--PDF(2),版本(1)CDF——文章(1),Excel(1)