WordPress.org

Make WordPress Core

Opened 6 years ago

Last modified 2 weeks ago

#12955 new feature request

Add get_post filter

Reported by: JohnLamansky Owned by:
Milestone: Future Release Priority: normal
Severity: normal Version:
Component: Posts, Post Types Keywords: has-patch dev-feedback
Focuses: Cc:

Description

This patch filters the return value of the get_post() function. I would find this very helpful for a plugin I'm developing.

Attachments (6)

post.php.diff (687 bytes) - added by JohnLamansky 6 years ago.
12955.2.diff (397 bytes) - added by Mte90 9 months ago.
patch refreshed
12955.3.diff (968 bytes) - added by MikeSchinkel 9 months ago.
Added PHPDoc for filter and $post, $output and $filter as $args.
12726-3.patch (992 bytes) - added by MikeSchinkel 9 months ago.
Updated PHPDoc for $post
12955.4.diff (4.8 KB) - added by MikeSchinkel 9 months ago.
Another approach: 'make_post_instance' and 'get_virtual_post_instance hooks' because some people at 10up were worried about calling the hook after caching (I don't think it would have any significant effect but I'm trying to provide an alternate solution.)
12955.5.diff (4.8 KB) - added by MikeSchinkel 9 months ago.
Added in correct first parameter for 'get_virtual_post_instance' hook

Download all attachments as: .zip

Change History (45)

#1 @scribu
6 years ago

  • Keywords has-patch added

#2 follow-up: @dd32
6 years ago

Out of interest, What is the use-case for this exactly?

#3 @mikeschinkel
6 years ago

  • Cc mikeschinkel@… added

#4 in reply to: ↑ 2 @JohnLamansky
6 years ago

Replying to dd32:

Out of interest, What is the use-case for this exactly?

I'd like to filter posts retrieved via XMLRPC/APP. Currently, the function chain goes like this: XMLRPC/APP method -> wp_get_single_post() -> get_post() -- None of these functions are filtered. Right now I have to manually override every single XMLRPC/APP method that retrieves posts, which is a pain. I'd love to be able to filter get_post() like this:

if ( defined('XMLRPC_REQUEST') || defined('APP_REQUEST') )
	add_filter( 'get_post', 'my_plugin_function' );

#5 @JohnLamansky
6 years ago

  • Cc JohnLamansky added
  • Keywords filter added

#6 @nacin
6 years ago

  • Milestone changed from 3.0 to Future Release

#7 @JohnLamansky
6 years ago

  • Milestone changed from Future Release to 3.0
  • Summary changed from Filter request for get_post to Add get_post filter

Since this filter would really help me with a plugin I'm developing, I'd really like to have it for 3.0 so I don't have to wait several more months for 3.1, if at all possible.

#8 @nacin
6 years ago

  • Milestone changed from 3.0 to Future Release

There is technically a workaround available. It's a pain as you say, but I don't want to change this so late in the development cycle.

#9 @meqia
5 years ago

  • Cc meqia added
  • Type changed from enhancement to feature request

Hello,
I apologize for my bad english but why you have abandoned the idea of ​​a filter to the function get_post(); ? Because it would be interesting to provide change the values ​​returned. For example when using the function wp_nav_menu() who call get_post();


Bonjour,
veuillez m'excuser pour mon mauvais anglais mais pourquoi vous avez abandonné l'idée de mettre un filtre sur la fonction get_post(); ? Car il serait intéressant de pourvoir modifier les valeurs retournées notamment lorsque l'on utilise la fonction wp_nav_menu(); qui appel get_post();

#10 @nacin
3 years ago

  • Component changed from General to Post Types

#11 @MikeSchinkel
3 years ago

I could really use this. Any chance this will get considered soon?

#12 @nikolov.tmw
22 months ago

I second the idea for a filter on get_post(), but I just ran into a possible side effect of the suggested patch.

Having the following super-simple code:

function my_post_filter( $post ) {
	$post->post_title .= ' | Filters are awesome';

	return $post;
}
add_filter( 'get_post', 'my_post_filter', 10 );

Results in the main post being displayed having it's title become something like this: "Hello world! | Filters are awesome | Filters are awesome | ...".
Now my question is the following - should we leave it up to the developers to make sure they're not filtering the same object multiple times, or should we come-up with an idea of how to avoid having to do that in the first place?

WP_Post::filter() seemed like a good place, but it never seems to run under normal circumstances, since the passed $filter is always the same as $this->filter.

#13 @sundquistm
16 months ago

I'm also interested in this filter.

Use-case: being able to properly support revisions complete with versioned post-meta and arbitrary data. Unless I'm mistaken, there's no way to fully and properly implement this without being able to filter get_post. The required workaround(s) are very ugly.

Last edited 16 months ago by sundquistm (previous) (diff)

#14 @danieliser
14 months ago

I support this as well. Use case, attaching extra fields into the post object.

Currently got a working concept for something like a partner table for posts, such as location information. This is useful in itself since querying post meta for radius location searches is very taxing if you have say 50k listings(CPT). A second table to manage these means easily querying using built in MySQL methods for these types of searches.

This patch applies simply because say I have address info in a partner table, I can then join that row to the post object so that $post->city, $post->state etc are available. Currently I am using a wrapper function that simply calls get_post() and then appends the extra keys. But being able to filter these on the initial get_post call would mean one less step. I'm sure there are many other uses for this as well.

#15 @Mte90
10 months ago

I would love this filter because this function is used in the menu editor in wordpress and i need to alter for inject virtual page.

#16 @sc0ttkclark
9 months ago

  • Keywords dev-feedback added

Following up on this, can we get dev eyes on this for 4.4 or 4.5-early?

#17 @sc0ttkclark
9 months ago

  • Keywords needs-refresh added

Patch needs refresh

@Mte90
9 months ago

patch refreshed

#18 @Mte90
9 months ago

  • Keywords needs-refresh removed

Patch Updated :-)

#19 @sc0ttkclark
9 months ago

  • Keywords needs-refresh added

Core code has changed since the original patch, I'm wondering whether the filter belongs before the filter, or before the check if $_post was found. This could allow a plugin to return a post, if it wasn't found.

#20 @MikeSchinkel
9 months ago

Per discussion with @sc0ttkclark I've added in $post, $output, $filter to the filter and also added PHPDoc for the filter to patch 12955.3.diff.

@MikeSchinkel
9 months ago

Added PHPDoc for filter and $post, $output and $filter as $args.

#21 @sc0ttkclark
9 months ago

  • Keywords filter needs-refresh removed

@MikeSchinkel
9 months ago

Updated PHPDoc for $post

#22 @sc0ttkclark
9 months ago

If we moved the filter to run only if $post wasn't an instanceof, that could greatly reduce the number of times this filter has to get called.

So that'd be from the current patch:

	if ( $post instanceof WP_Post ) {
		$_post = $post;
	} elseif ( is_object( $post ) ) {
		if ( empty( $post->filter ) ) {
			$_post = sanitize_post( $post, 'raw' );
			$_post = new WP_Post( $_post );
		} elseif ( 'raw' == $post->filter ) {
			$_post = new WP_Post( $post );
		} else {
			$_post = WP_Post::get_instance( $post->ID );
		}
	} else {
		$_post = WP_Post::get_instance( $post );
	}

	/**
	 * Filter that allows a post object to be further sanitized, virtualized and/or have properties annotated.
	 *
	 * @since 4.4.0
	 *
	 * @param WP_Post       $_post    Post object to saitize/virtualize/annotate/etc.
	 * @param WP_Post|int   $post     Original post object passed in.
	 * @param string        $output   Format of data to return, potentially 'OBJECT', 'ARRAY_A' or 'ARRAY_N'.
	 * @param string        $filter   Type of filter to apply, potentially 'raw', 'edit', 'db' or 'display'.
	 */
	$_post = apply_filters( 'get_post', $_post, $post, $output, $filter );

to

	if ( $post instanceof WP_Post ) {
		$_post = $post;
	} else {
		if ( is_object( $post ) ) {
			if ( empty( $post->filter ) ) {
				$_post = sanitize_post( $post, 'raw' );
				$_post = new WP_Post( $_post );
			} elseif ( 'raw' == $post->filter ) {
				$_post = new WP_Post( $post );
			} else {
				$_post = WP_Post::get_instance( $post->ID );
			}
		} else {
			$_post = WP_Post::get_instance( $post );
		}

		/**
		 * Filter that allows a post object to be further sanitized, virtualized and/or have properties annotated.
		 *
		 * @since 4.4.0
		 *
		 * @param WP_Post       $_post    Post object to saitize/virtualize/annotate/etc.
		 * @param WP_Post|int   $post     Original post object passed in.
		 * @param string        $output   Format of data to return, potentially 'OBJECT', 'ARRAY_A' or 'ARRAY_N'.
		 * @param string        $filter   Type of filter to apply, potentially 'raw', 'edit', 'db' or 'display'.
		 */
		$_post = apply_filters( 'get_post', $_post, $post, $output, $filter );
	}

This

#23 @MikeSchinkel
9 months ago

@sc0ttkclark That change would unfortunately result in not filtering all $post instances so it makes it a non-starter.

I'm going to look at WP_Post::get_instance() to see if I can instead add a filter there to achieve the same w/o running the filter more often than needed.

@MikeSchinkel
9 months ago

Another approach: 'make_post_instance' and 'get_virtual_post_instance hooks' because some people at 10up were worried about calling the hook after caching (I don't think it would have any significant effect but I'm trying to provide an alternate solution.)

#24 @MikeSchinkel
9 months ago

So attached is another way to skin this cat.

  1. Added a WP_Post::make_instance() method that is to be called instead of new WP_Post() except inside core. 2. Added deprecation warning if new WP_Post() is called in a theme or plugin directly.
  2. Added a 'make_post_instance' to filter instances when new WP_Post() is called.
  3. Added a 'get_virtual_post_instance' for when the SQL query in WP_Post::get_instance() fails to find a post in the database. This will allow code to retrieve the post via API and then return a valid post.

This approach is in advance of caching.

@MikeSchinkel
9 months ago

Added in correct first parameter for 'get_virtual_post_instance' hook

#25 @MikeSchinkel
9 months ago

In order to illustrate how beneficial this hook can be ,I've implemented a plugin as a proof-of-concept in the following Gist that virtualizes the results of the API found at http://apps.usa.gov/apps-gallery/api/registrations.json :

The plugin does not fully handle all aspects that a robust solution would handle, but it works well enough to show why this hook would be beneficial.

To see how it works just drop in any WordPress site and navigate to the URL:

  • /wp-admin/edit.php?post_type=vp_fed_mob_apps

From there click forward through the next pages and notice how it loads the results from the API into the database as if a post type. If you click back to earlier pages you notice it doesn't need to reload. Also notice that if it has been 15 minutes since a post has been updated it uses the 'make_post_instance' hook to reload the data from the API when you view a specific post.

Now imagine how much easier it would be for a themer who is not a strong PHP developer to access an API by treating it like a post type assuming someone has developed a plugin that presents the API as a post type.

If we get this functionality we'd like to implement the APIs that are found here: http://www.cdata.com/cloud/ which include Salesforce, Gmail, Facebook, QuickBooks, Google AdWords, Xero Accounting, Hubspot, Marketo, MailChimp and more.

But we can't without this hook.

Last edited 9 months ago by MikeSchinkel (previous) (diff)

#26 @Mte90
9 months ago

any news for that patch?

This ticket was mentioned in Slack in #core by mte90. View the logs.


9 months ago

This ticket was mentioned in Slack in #core by chriscct7. View the logs.


6 months ago

This ticket was mentioned in Slack in #core by mte90. View the logs.


5 months ago

This ticket was mentioned in Slack in #core by chriscct7. View the logs.


5 months ago

#32 in reply to: ↑ 31 @MikeSchinkel
5 months ago

Replying to chriscct7:

Feedback on the current patch (from https://wordpress.slack.com/archives/core/p1452877984010589):

The current patch proposes deprecating the ability to call new WP_Post in favor of calling WP_Post::make_instance(). I think a better path forward would be to have the __construct() call make_instance(), as there are so many plugins (and parts of core using new WP_Post) then this is going to cause so many issues (it will be like deprecating WP_Widget::WP_Widget but way worse).
The patch also needs unit tests.
Basically by using make_instance() the goal is to be able to filter the returned object in get_post()

Completely agreed that it would be ideal to have the __construct() call make_instance(), but unfortunately PHP does not allow specifying the object return value inside a constructor. A major goal here was to stop get_post() from changing the object such that the following is always true:

<?php
$_post = get_post( $post );
echo $_post === $post  // Can currently echo `false`

Yes it is an issue that plugins use new WP_Post() but that's the lesser concern. That they do so does not cause the problem above.

To address your concern we could have a "soft deprecation" where no warning or error is thrown for numerous releases, as long as get_post() can be made to not change the object that it is being effectively used to "cast" to WP_Post.

Last edited 5 months ago by MikeSchinkel (previous) (diff)

#33 @westonruter
8 weeks ago

Just to note that this ticket is a dependency for previewing changes to posts in the Customizer. As noted in the closed duplicate #36879, currently previewing posts relies on queries being made without suppress_filters so that the_posts filter is applied:

This was originally reported as an an issue in the Customize Posts feature plugin for #34923. The plugin currently previews post data via the the_posts filter. This is problematic, however, because this filter is not called in queries with suppress_filters on or in calls to get_post():

When querying posts via get_posts() or when using new WP_Query( array( 'suppress_filters' => true ) ), the the_posts filters which apply the previewed changes to the posts is not run. This is a problem with Core in that there are not always filters applied to post data. This necessitates, for example, for there to be a comments_open filter in addition to a the_posts filter, since get_post() will return a pristine copy of the post without any hooks for Customize Posts to apply the previewed changes. In reality, get_post() should be returning the dirty post as opposed to the pristine post, and so an additional comments_open filter wouldn't be needed.

So, what is needed is a new filter to run whenever getting a post via get_post() to allow the array or object to be mutated. This filter can only be applied if is_customize_preview() since applying such a filter generally could be too far reaching.

#34 @MikeSchinkel
8 weeks ago

#30292 was marked as a duplicate.

#35 @boonebgorges
8 weeks ago

I feel very uneasy about a generic 'get_post' filter. The WP_Post class is nothing more than a caching wrapper for a database query. It doesn't validate properties, it doesn't enforce types. Critically, it doesn't provide a stable interface that actually defines what a post-ish object needs to be able to do. If we want the ability for developers to mock post-ish objects without requiring that they be stored in the wp_posts table (which I think we do, in the long run), we should (a) decorate WP_Post and friends with actual getter/setter methods, (b) provide one or more interface(s) describing the capabilities of post-ish objects, and (c) move toward more consistent type hinting and instanceof checks where post objects are used throughout WP. In this way, we can more clearly describe what a "post" is, helping us to ensure that core is not saddled with undue backward compatibility concerns due to a lax/non-existent concept of Posthood, and will make it easier for developers to extend WP without guessing at what counts as a "post". Related: #24672

In any case, I'm inclined not to force the issue just for the sake of Customize Posts, especially since (if I understand correctly) the goal is to support plugins that use get_posts() or suppress_filters. In the same way that plugins have to opt in to have selective refresh enabled for widgets, it doesn't seem out of line to me that a plugin would have to conform to certain standards - like not using suppress_filters - to be compatible with Customizer editing. Also, you always have the sledgehammer available of forcing suppress_filters to false during a pre_get_posts callback.

If we were to consider a 'get_post' filter at this point in time, I think it would look more like @sc0ttkclark's suggestion in comment 22 than like 12955.5.diff.

@sc0ttkclark That change would unfortunately result in not filtering all $post instances so it makes it a non-starter.

Nearly every place where WordPress pulls up a post object, it runs it through get_post(). And in most of the cases where we call new WP_Post(), it looks like it's by accident and could easily be changed over to a get_post() call.

Moreover, it's a bad idea to filter before the cache. If you store a filtered value in the cache - especially one that contains more properties than a default WP_Post object - how will WP know how to invalidate? If we must have a pre-cache filter, it should be along the lines of pre_get_post, and would skip the cache and database altogether.

#36 @westonruter
7 weeks ago

@boonebgorges I will note that a get_post filter would seem to offer parity with other functions and filters in core:

  • The get_comment filter applies when the get_comment() function is called
  • The get_term filter applies when the get_term() function is called.
  • The get_{$meta_type}_metadata applies when get_{$meta_type}_meta() is called.
  • The filters pre_option_{$option}, default_option_{$option}, andoption_{$option} apply when get_option() is called.

From an API consistency perspective, the lack of a get_post filter seems to be a hole.

#37 @dd32
7 weeks ago

Just like to throw my support for @boonebgorges's comments above here.
We really can't introduce a get_post filter until we've opened up the ability for post types to have WP_Post subclasses (which can only happen after we actually implement WP_Post further than a cache wrapper, and have solidified it's API in stone forever). Doing so would cause a lot of compatibility issues down the road when we did do allow that.

I say this having run into lots of scenario's where having a get_post filter would've been very useful.

#38 @ocean90
2 weeks ago

#37316 was marked as a duplicate.

#39 @jason_the_adams
2 weeks ago

Want to throw in my support for this. I'd be happy with either a filter, as the ticket suggests, or the ability to define a subclass as @dd32 mentions. I have some classes that I wrap around WP_Post, WP_User, and WP_Term in a plugin I made, with the idea that it's easy to subclass the Post class for a post type or page template. When get_post_controller (get_post wrapper), it grabs the corresponding class based on the post's template/type. This is done by looking for a static property of the class ($post_type/$page_template, which can be an array), and falls back on the base Post class. You can check out the source here: https://github.com/JasonTheAdams/WP-Controllers

I say all that just as food for thought on how subclasses could be introduced and retrieved via get_post. I'm still open to the filter idea, however, and believe it's up to the developer to know when is the best time to use each filter.

Note: See TracTickets for help on using tickets.