• Resolved zyrorl

    (@zyrorl)


    I’ve currently run into a dillema where a plugin i’ve written detects a specific URL request on init hook, checks the url parameters and runs a query to overwrite the loaded post based on that, as well as parses the url for parameters to send into the plugin.

    Note: this whole situation is only applicable when permalinks are turned on. The plugin gracefully handles not having permalinks on, by generating get variables instead of the below examples. The point is to take advantage of WordPress’s handling of permalinks to make this work.

    Basically how it works is as follows:

    – Plugin loads, runs hook on init
    – Detects whether url contains ‘/query/’ pattern in it eg. http://www.example.com/wordpress-page-name/query/parameter/parameter/
    – If pattern detected, then grab url prefixed to that eg. http://www.example.com/wordpress-page-name/ and run get_page_by_path() with that url to grab the page object, then run a query_posts with that page_id which alters the main query currently running.
    – Plugin code then has access to these variables and passes it to the shortcode content that runs on the post content that belongs to that page, displaying dynamically relevant content.

    This does work. However there are problems with this approach.

    The problem is when a plugin or code or widget runs another query_posts on the page, and runs wp_reset_query() that query is lost, despite the fact that I want it to be the main query for the page.

    Because originally when wordpress starts and tries to determine the post for the page that url obviously cannot match any wordpress articles due to the added parameters, the main query basically returns no results/error 404, which gets overridden with the query_posts therefore page content shows.

    The only solutions I’ve found for this so far are kludgy at best which involve:

    – Running a second hook on pre_get_posts, then checking it is_main_query(), check if url matches pattern above, then over-writing the main query object with the WP_Query object created by the query_posts() function (after having stored it in a global variable).

    This works well, however I think it’s kludgy, dangerous, may not work in the future, not really future-proof and possibly not even future-release compatible.

    – Write custom .htaccess rules to handle it – I don’t like this method as not every client who uses this plugin may necessarily have a writeable .htaccess file, or even use apache (they could use IIS – as one of our clients does).

    – Edit every theme that uses wp_reset_query(), and replace it with code to reset it with the query that displays the content we want – which is unmaintainable really and not really compatible with most themes and other plugins that may be installed.

    – Use WP_Rewrite – I haven’t been able to find anything to suggest that this approach may work, however everything I’ve read so far doesn’t seem to indicate that this may necessarily work either. I don’t have a whole lot of experience with WP_Rewrite class so I’d appreciate someone else’s knowledge/experience giving me some input into this.

    I’m looking for a solution that preferrably is:

    – Not dodgy/kludgy, involve re-writing of global variables in my own code, rather than use WordPress API’s to handle things correctly.
    – Works with wordpress permalinks
    – Works inside of wordpress in a plugin – not some dodgy other page with wordpress headers/footers etc.
    – Doesn’t involve modifying or rewriting themes
    – Doesn’t involve manipulating .htaccess or other webserver configurations beyond the default wordpress setup

Viewing 6 replies - 1 through 6 (of 6 total)
  • Thread Starter zyrorl

    (@zyrorl)

    Just to add some example code here… the following code is run on init:

    $parsed_url = parse_url(home_url());
    			$path = str_replace(home_url(), '', $parsed_url['scheme'] . '://' . $parsed_url['host'] . $_SERVER['REQUEST_URI']);
    // For Search Queries
    					if ((strstr($path, '/query/')) && (get_page_by_path( $path ) == NULL)) {
    
    						if (substr($path, -1) == '/') {
    							$_SERVER['QUERY_STRING'] = substr($path, strpos($path, '/query/')+strlen('/query/'), -1);
    						} else {
    							$_SERVER['QUERY_STRING'] = substr($path, strpos($path, '/query/')+strlen('/query/'), strlen($path));
    						}
    
    						$real_page_path = substr($path, 0, strpos($path, '/query/'));
    						$pageObj = get_page_by_path( $real_page_path );
    
    						/*
    						 * If pageObj returns null it's either that
    						 * A) the page we're using is the home page or
    						 * B) we've not found what page this is.
    						 *
    						 * Eitherway load home page as default if page object isn't found
    						 */
    
    						if ($pageObj == NULL) {
    							query_posts(array( 'page_id' => get_option('page_on_front') ));
    						} else {
    							query_posts(array( 'page_id' => $pageObj->ID ));
    							global $wp_query;
    							$GLOBALS['stored_query'] = $wp_query;
    						}
    					}

    Then the following code gets run on pre_get_posts hook:

    $parsed_url = parse_url(home_url());
    		$path = str_replace(home_url(), '', $parsed_url['scheme'] . '://' . $parsed_url['host'] . $_SERVER['REQUEST_URI']);
    
    		if( $query->is_main_query() ) {
    			// For Search Queries
    			if ((strstr($path, '/query/')) && (get_page_by_path( $path ) == NULL)) {
    				global $wp_query, $wp_the_query, $stored_query;
    				$wp_the_query = $stored_query;
    			}
    		}

    As mentioned before, in my view this is dodgy. I don’t like the solution, however i’ve found also modifying the query object in pre_get_posts by doing something like $query->set(‘page_id’, ‘pageid_of_the_page_we_want’); causes a redirect to the page which strips the /query/parameter/parameter/ from the url, which means that the page doesn’t get access to what the parameters were to query on.

    Moderator bcworkz

    (@bcworkz)

    Either I misunderstand what you’re doing or you misunderstand what the Rewrite API does, because this sounds exactly like a job for the Rewrite API. Disclaimer: I have little direct experience with this API, however I believe I have a very good grasp of the concept and how it works in general. I am a little fuzzy on the exact coding needed since I have limited direct experience. That said, you should definitely explore this.

    What you are doing now is attempting to replicate th API’s functionality, with limited success because your code is outside the main query path. With the API, your parameters are part of the main query path, so nasty side effects like query resets should not be an issue.

    Using the API would mean defining a regex that identifies your data pattern to the query parser. Thus there is no issue with 404 errors, the parser recognizes your pattern and accepts it. If you also register permalink tags for your pattern, it becomes fully part of the permalink system, meaning your parameters will be available to the resulting page as query variables, so there should be no problem with query resets. If the page is loaded, the query variables are there. They are tied together, you will not get one without the other.

    I regret I don’t know enough of the API to give you specific instructions. I do know enough to assure you this is the direction to follow, it will work for you.

    Thread Starter zyrorl

    (@zyrorl)

    bcworkz… I suspected as much when I found the Rewrite API, but couldn’t find how to make it work. I would love to see some examples from people if they have any.

    I think you do have a good understanding of what I’m attempting to accomplish. I’ll continue to do some research on this, but if anyone browsing these forums has some specific examples for my use case I’d love to see them.

    Thread Starter zyrorl

    (@zyrorl)

    Okay, surprisingly I’ve got this working so far, with the only exception being if the url applies to the blog’s home page url.

    creating the following rewrite rules code worked:

    $wp_rewrite->add_rewrite_tag(‘%query%’,'(.*)’, ‘query=’);
    $rewrite_keywords_structure = $wp_rewrite->root . ‘%pagename%/query/%query%/’;
    $new_rule = $wp_rewrite->generate_rewrite_rules($rewrite_keywords_structure);
    $wp_rewrite->rules = $new_rule + $wp_rewrite->rules;

    It’ll work for any page like

    http://www.example.com/any-page-name/query/parameter/parameter/

    However it won’t work for the following:

    http://www.example.com/query/parameter/parameter/

    Where the root / – is the home page.

    It appears though that the query variables do get added to the wp_query however, it is not loading the right page/post that is set as the home page, instead it’ll attempt to load all blog posts.

    Anyone know anything about this?

    Moderator bcworkz

    (@bcworkz)

    Some good progress in a couple hours, especially considering much of it was probably wasted with the home page issue!

    The query is obviously falling back to the default WP behavior of the index page, while it should be loading the page in your settings. I don’t have a direct solution, but I have some ideas on how to go about solving the problem.

    Something is likely failing in the logic of the parse_query() method of WP_Query. Trying some detailed debugging here should reveal why the page in your settings is not getting loaded. Look in wp-includes/query.php, probably starting around line 1607. From what you discover here, perhaps there is something you can modify in your rewrite rules to get the correct page loaded.

    If all else fails, the debugging will tell you what to change by hooking the ‘parse_query’ filter and what conditional to use to apply it correctly.

    Thread Starter zyrorl

    (@zyrorl)

    I’ve found the problem. Clearly it’s a shortcoming/oversight in the wordpress code. This makes it really frustrating unfortunately!!!

    If you look at lines 1610 – 1626, you’ll notice there’s a check for:

    if ( empty($_query) || !array_diff( array_keys($_query), array('preview', 'page', 'paged', 'cpage') ) ) {

    Which will return false of course, since the point of what i’m doing is to pass some variables to the re-write rules for that page to use! Arrgh!

    So i could modify the if statement with a new array key ‘query’, which would be dodgy as all hell (and not to mention dangerous as we’re modifying wordpress core). This solution doesn’t really make me happy – and I’m staying away from it.

    I could instead of passing those parameters through to ‘query’ as a parameter, use one of the parameters above such as, preview, paged, page or cpage which is dodgy as all hell too, although it does work. I’m not happy with this as a solution either.

    The solution, ultimately, was to do as you suggested, hook into parse_query, and re-run the code in that if statement with a couple of modifications to the if statement.

    For the sake of posterity the code I used was as follows:

    function parse_query_filter($wp_query) {
    	// Correct is_* for page_on_front and page_for_posts
    	if ( $wp_query->is_home && 'page' == get_option('show_on_front') && get_option('page_on_front') ) {
    		$_query = wp_parse_args($wp_query->query);
    
    		if ( empty($_query) || isset($_query['query']) ) {
    			$wp_query->is_page = true;
    			$wp_query->is_home = false;
    			$wp_query->set('page_id', get_option('page_on_front'));
    			// Correct <!--nextpage--> for page_on_front
    			$paged = $wp_query->get('paged');
    			if ( !empty($paged) ) {
    				$wp_query->set('page', $paged);
    				unset($paged);
    			}
    		}
    	}
    }
    
    add_action('parse_query', 'parse_query_filter');

    I could have added the array_diff stuff, and just added ‘query’, however if more parameters are later added to the wordpress core, they would have been caught out in this, and instantly the plugin would stop working. I also removed the previous if statement on lines 1614 and 1615 as it would have already run before the parse_query filter.

    Thank you so much for your help bcworkz, really made a big difference to point me in the right direction!

Viewing 6 replies - 1 through 6 (of 6 total)
  • The topic ‘How to best handle permalink rules for dynamic plugin pages’ is closed to new replies.