1

Background

I need to retrieve terms for a post in an arbitrary site on a multisite network. This is not necessarily the site that the WP instance is running in at that moment. wp_get_object_terms() requires the taxonomy for which to retrieve the terms. So, to retrieve all terms for all taxonomies, it would be necessary to first retrieve the taxonomies. This does not appear to be possible, as the taxonomies that are registered by plugins may not be getting registered on the current (original) site that the instance is running in, because the plugins themselves would not be active on that site.

Question

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

Alternatively, 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?

7
  • A post can only have terms on a single site, this is because post IDs and posts are unique to a site, not a network. So a post with ID 5 on site A is unrelated to a post with ID 5 on site B. In the WP ecosystem, posts only appear on a single site. If the post is also on a second site then they are not the same post, even though they have the same title/content/meta
    – Tom J Nowell
    Commented Sep 22, 2020 at 14:58
  • As an aside, what exactly are you building that requires this? Are you trying to use a multisite as a singular site where each blog is a shard of that site somehow, e.g. for a single author, or so that you can use a theme for a sub-section of a site? There are better ways to solve that problem. Knowing the overarching high level plan of what you're trying to do will help put your questions in context and make it easier to provide actual solutions rather than having to focus on the specific question at hand.
    – Tom J Nowell
    Commented Sep 22, 2020 at 15:25
  • I am aware that a post exists on a single site, and that terms also exist on a single site. I am asking: given that I have a site $siteId, how can I retrieve all taxonomies on that site? Alternatively, given post $post and site $siteId, how can I retrieve all terms for all taxonomies of that post in that site? Commented Sep 22, 2020 at 16:13
  • Can you adjust your question so that it's stated more clearly? A lot of people won't read the comments. Also, describing the high level goal is still super useful, in this case there may be more than 1 solution but not all of them might apply to you. Otherwise I'd have to write a really long answer covering all bases, run the risk I'll get criticised for posting irrelevant solutions, and have to budget a big portion of my evening. Knowing what you're doing that requires this also means i can mention important things that might not normally apply, or exclude things that are irrelevant to you
    – Tom J Nowell
    Commented Sep 22, 2020 at 16:27
  • 1
    It is stated exactly like that: "How can I retrieve all taxonomies of an arbitrary site?". But valid point, I can add examples Commented Sep 22, 2020 at 16:30

2 Answers 2

4

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.

10
  • In the comments and in the original question, I mentioned Separation of Concerns, and so I assumed it's obvious that I need to retrieve this data programmatically. Parsing WP CLI output is hacky, but sure, it's possible. Nevertheless, I didn't quite understand how to retrieve taxonomies of an arbitrary website: running wp taxonomy list gives taxonomies of the current site (whatever that is, probably main). The URL parameter doesn't give me what I want, because I don't know the URL of the site - I know the ID - and the URL may change. Commented Sep 23, 2020 at 10:55
  • I don't see how this isn't done programmatically, there are no manual steps in my answer, you pass the --format parameter then parse the result. I also explained in the answer targeting arbitrary sites, e.g. when I used the --url parameter. I did not miss that part of the problem. Since you know the ID of the site, that gives you everything you need to retrieve its URL at runtime, so even if its URL changes that isn't an issue.
    – Tom J Nowell
    Commented Sep 23, 2020 at 15:23
  • If it's a simple PHP function call you wanted though, no, that does not exist. If you have additional constraints, they should have been mentioned in the original question, but they weren't. You should be clear and unambiguous about what you're asking and what kind of answer you expect
    – Tom J Nowell
    Commented Sep 23, 2020 at 15:24
  • I've added extra notes to my answer re: WP CLI, as it seems it was misunderstood, but I would keep it in mind, I'm not employed or paid to do this, I volunteer my time, writing this kind of text is not easy, and the subject at hand does not have easy straight forward solutions. You're right at the fringe of WP development practices doing very unusual things without offering much context or explanation
    – Tom J Nowell
    Commented Sep 23, 2020 at 15:38
  • Hi! Sorry for the long wait. Yes, I guess I could get the site URL first, and then use WP CLI to retrieve the taxonomies. But in any case, these things look like a very long workaround. I have something else in mind. But your answer is legit, thanks :) Commented Sep 24, 2020 at 16:50
0

Use WP_Term_Query without the taxonomy parameter. While wp_get_object_terms() strangely doesn't allow omitting the taxonomy parameter, WP_Term_Query that is used under the hood does.

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;
}

This returns all terms for post #123. Caveat: only terms that are associated with at least one post seem to be returned.

From here, it should be possible to iterate over the terms, and get their taxonomy, if the goal is to list all taxonomies:

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;
}

This allows retrieving all taxonomy slugs for a specific site. Caveat: it only appears to return taxonomies which have at least 1 post associated with at least 1 term. This is not the most performant way because it would loop through all of the terms, but there aren't usually more than a dozen terms for a taxonomy, so it should be fine in most cases.

Limitations

  1. Cannot get terms that have no posts associated with them. Consequently, cannot get taxonomy slugs for which there are no terms associated with at least 1 post.
  2. Cannot retrieve WP_Taxonomy objects for an arbitrary site, because taxonomies from plugins that are not running in the current site are not guaranteed to be registered at all.
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.