• Resolved morespinach

    (@morespinach)


    Hi. WP is notoriously annoying with paginating custom post types. Before I add random other plugins or silly code to my functions.php, I wanted to check if Permalink Manager is causing the issues.

    We have a CPT called “stories”. So this permastructure works:

    stories/%category%/%stories%

    However this gives me a 404:

    stories/%category%/%stories%/page/2/

    I’m sure I’m not the first person to hit upon this issue. Is there a fix for this? Many thanks.

Viewing 14 replies - 1 through 14 (of 14 total)
  • Plugin Author Maciej Bis

    (@mbis)

    Hi @morespinach

    could you check if “Force 404 on non-existing pagination pages” option is disabled in Permalink Manager settings?
    https://permalinkmanager.pro/docs/faq/pagination-pages-return-404-error/

    If this does not help please send me the debug data from sample pagination page. You can display the data using ?debug_url=1 URL parameter, eg.:
    stories/%category%/%stories%/page/2/?debug_url=1

    Regards,
    Maciej

    Plugin Author Maciej Bis

    (@mbis)

    Hi again,

    I explained it a bit further and posted a solution here:
    https://stackoverflow.com/a/57307057/3613568

    Best regards,
    Maciej

    Thread Starter morespinach

    (@morespinach)

    Maciej, this is so kind. Thank you! Let me try. I didn’t receive the ping from these forums as they go into a separate folder in gmail.

    Anyway the “/page/2” URL is automatically generated. I wasn’t composing that manually. It happens with both paginate_links and the wp—pagenavi plugin.

    Let me try if this works when I’m at a computer soon.

    Thread Starter morespinach

    (@morespinach)

    Thank you. I actually need the page for a category of a custom post type, not for a single post.

    So this is what I need:
    stories/%category%/page/2

    Not this:
    stories/%category%/%stories%/page/2

    Thank you for the debug_url variable. It is different as follows across the main CPT category page and the paged version.

    The debug_url for /stories/discover?debug_url=1

        Array
        (
            [uri_parts] => Array
                (
                    [lang] => 
                    [uri] => stories/discover
                    [endpoint] => 
                    [endpoint_value] => 
                )
        
            [old_query_vars] => Array
                (
                    [category_name] => discover
                )
        
            [new_query_vars] => Array
                (
                    [category_name] => discover
                )
        
            [detected_id] => -
        )
    

    The debug_url for /stories/discover/page/2?debug_url=1

        Array
        (
            [uri_parts] => Array
                (
                    [lang] => 
                    [uri] => stories/discover
                    [endpoint] => page
                    [endpoint_value] => 2
                )
        
            [old_query_vars] => Array
                (
                    [page] => 2
                    [category_name] => discover
                    [stories] => page
                    [post_type] => stories
                    [name] => page
                )
        
            [new_query_vars] => Array
                (
                    [page] => 2
                    [category_name] => discover
                    [stories] => page
                    [post_type] => stories
                    [name] => page
                )
        
            [detected_id] => -
        )
    

    I have disabled the “Category Pagination Fix” plugin (or code from Github) now. Notably, the main category page uses the category.php template, while the paged version is a 404 now. It is not picking up archive-stories.php. If I create a category-discover.php it picks that up. But we have about 15 categories and I don’t see the point of creating 15 category pages if the archive for every custom post type category will be the same. According to template hierarchy which archive should I create?

    Many thanks for your help!

    • This reply was modified 5 years, 8 months ago by morespinach. Reason: Added correct code tags to the debug code
    Thread Starter morespinach

    (@morespinach)

    Btw, if it helps, this is the debug_url for the category page without the word “page”.

    So, for:
    /stories/discover/2?debug_url=1

            Array
            (
                [uri_parts] => Array
                    (
                        [lang] => 
                        [uri] => stories/discover
                        [endpoint] => 
                        [endpoint_value] => 2
                    )
            
                [old_query_vars] => Array
                    (
                        [page] => 
                        [category_name] => discover
                        [stories] => 2
                        [post_type] => stories
                        [name] => 2
                    )
            
                [new_query_vars] => Array
                    (
                        [page] => 
                        [category_name] => discover
                        [stories] => 2
                        [post_type] => stories
                        [name] => 2
                    )
            
                [detected_id] => -
            )
    

    These are our settings in Permalink Manager:

    View post on imgur.com

    Plugin Author Maciej Bis

    (@mbis)

    There is no possibility in either my plugin or vanilla version of WordPress to combine both:

    • post type archive
    • category archive

    into a single archive page, eg. the archive page displaying the stories assigned to single category.

    To make it working you need a custom rewrite rules, please copy the code below to PHP file and then upload it to wp-content/plugins directory and activate from admin dashboard:

    <?php
    /*
     * Plugin Name:       Stories Permalinks
     * Plugin URI:        https://maciejbis.net/
     * Description:       Plugin that allows to adjust the CPT archive permalink format
     * Version:           1.0.0
     * Author:            Maciej Bis
     * Author URI:        https://maciejbis.net/
     * License:           GPL-2.0+
     * License URI:       https://www.gnu.org/licenses/gpl-2.0.txt
     * Domain Path:       /languages
    */
    
    /**
     * Get a list of filtered post types
     */
    function bis_get_post_types($post_type = null) {
    	$post_types = array(
    		'stories' => 'stories/%category%',
    	);
    
    	return ($post_type && !empty($post_types[$post_type])) ? $post_types[$post_type] : $post_types;
    }
    
    /**
     * Register new rewrite rules
     */
    function bis_extra_rewrite_rules() {
    	// A. Taxonomies
    	$post_types = bis_get_post_types();
    
    	foreach($post_types as $post_type => $structure) {
    		$structure = str_replace('%category%', '([^/]+?)', $structure);
    		$taxonomy = 'category_name';
    
    		add_rewrite_rule("{$structure}/(feed|rdf|rss|rss2|atom)/?$",
    			sprintf('index.php?post_type=%s&%s=$matches[1]&feed=$matches[2]', $post_type, $taxonomy),
    			'top');
    		add_rewrite_rule("{$structure}/feed/(feed|rdf|rss|rss2|atom)/?$",
    			sprintf('index.php?post_type=%s&%s=$matches[1]&feed=$matches[2]', $post_type, $taxonomy),
    			'top');
    		add_rewrite_rule("{$structure}/embed/?$",
    			sprintf('index.php?post_type=%s&%s=$matches[1]&embed=true', $post_type, $taxonomy),
    			'top');
    		add_rewrite_rule("{$structure}/page/([0-9]{1,})/?$",
    			sprintf('index.php?post_type=%s&%s=$matches[1]&page=$matches[2]', $post_type, $taxonomy),
    			'top');
    		add_rewrite_rule("{$structure}/?$",
    			sprintf('index.php?post_type=%s&%s=$matches[1]', $post_type, $taxonomy),
    			'top');
    	}
    }
    add_action('init', 'bis_extra_rewrite_rules');
    
    /**
     * Flush rewrite rules
     */
    function bis_activate_plugin() {
      if(!get_option('bis_flush_rewrite_rules_flag')) {
        add_option('bis_flush_rewrite_rules_flag', true);
      }
    }
    register_activation_hook(__FILE__, 'bis_activate_plugin');
    
    function bis_flush_rewrite_rules_maybe() {
      if(get_option('bis_flush_rewrite_rules_flag')) {
    		flush_rewrite_rules();
    		delete_option('bis_flush_rewrite_rules_flag');
      }
    }
    add_action('init', 'bis_flush_rewrite_rules_maybe', 20);
    
    /**
     * Do not redirect if post tag is detected
     */
    function bis_do_not_redirect_tag_archive($request) {
    	global $wp;
    
    	if(!empty($request['post_type']) && !empty($request['tag'])) {
    		$request['post_type'] = str_replace(array('products', 'posts', 'recipes'), array('product', 'post', 'recipe'), $request['post_type']);
    		$request['do_not_redirect'] = 1;
      }
    
      return $request;
    }
    add_filter('request', 'bis_do_not_redirect_tag_archive', 999);
    

    Best regards,
    Maciej

    • This reply was modified 5 years, 8 months ago by Maciej Bis.
    Thread Starter morespinach

    (@morespinach)

    Maciej, this is exceptionally kind. Thank you so much for sharing this thoughtful code.

    Before I go and enter this in our functions.php because we actually have several CPTs, may I ask how Permalink Manager can help us handle a very straightforward structure that I’m sure everyone who creates custom post types needs. WP should really fix this silly reality from its blogging past.

    We have some CPTs like Stories or Shop. Both follow the same “categories” because those are the themes of our digital organization. (We may also have other “tags” that cut across CPTs, but that’s for a different day). For example:

    
    Stories (CPT1)
    |_ Arts  (Category1)
    |_ Food & Drink (Category2)
    |_ Home (Category3)
    
    Shop (CPT2)
    |_ Arts  (Category1)
    |_ Food & Drink (Category2)
    |_ Home (Category3)
    

    Inside each of them there will of course be specific posts. As such, the URL structure should quite sensibly and naturally be–

    
    /stories
    |_ /stories/arts
       |_ /stories/arts/post1 
       |_ /stories/arts/post2 
    |_ /stories/food-drink
       |_ /stories/food-drink/post3 
       |_ /stories/food-drink/post4 
    |_ /stories/home
       |_ /stories/home/post5
       |_ /stories/home/post6 
    
    /shop
    |_ /shop/arts
       |_ /shop/arts/post7 
       |_ /shop/arts/post8 
    |_ /shop/food-drink
       |_ /shop/food-drink/post9
       |_ /shop/food-drink/post10 
    |_ /shop/home
       |_ /shop/home/post11
       |_ /shop/home/post12
    

    And so on. This is clearly amiss in WP core with its blogging-focused ‘Permalinks’. This is where your plugin helps a lot.

    We naturally need the main CPT archive (/stories, /shop in the above examples), but then category pages, and then single post. Single posts are no issue and we don’t need pagination there, they show up in one long page. But both the overall CPT archive and the category pages within that CPT need pagination. In other words we need this:

    
    /stories
    /stories/page/2
    /stories/page/..
    
    |_ /stories/arts
       /stories/arts/page/2
       /stories/arts/page/..
    
       |_ /stories/arts/post1   (no pages)
       |_ /stories/arts/post2   (no pages)
    
    |_ /stories/food-drink
       /stories/food-drink/page/2
       /stories/food-drink/page/..
    
       |_ /stories/food-drink/post3   (no page) 
       |_ /stories/food-drink/post4   (no page) 
    
    (Repeat for /shop)
    

    How can we architecture Permastructures (and I suppose the template hierarchy) to achieve the above? Do we have to write functions.php code for every time we introduce a CPT?

    Plugin Author Maciej Bis

    (@mbis)

    Actually you need to change the first function to:

    function bis_get_post_types($post_type = null) {
    	$post_types = array(
    		'stories' => 'stories/%category%',
    		'shop' => 'shop/%category%',
    	);
    
    	return ($post_type && !empty($post_types[$post_type])) ? $post_types[$post_type] : $post_types;
    }

    After that, you will need to flush rewrite rules:
    https://typerocket.com/flushing-permalinks-in-wordpress/

    Thread Starter morespinach

    (@morespinach)

    Thank you. I had noticed the array, and presumed I’d need to lay out every CPT that way. Good to have it confirmed.

    Just to be sure then:

    1. What templates do we need for:

    
       /stories       (CPT)
       /stories/art   (category within CPT)
       /stories/art/post   (this is the single-stories.php, I know) 
    

    2. Do we need to set the correct permastructures in Permalink Manager, or will the functions.php code take care of URLs anyway?

    Thanks!

    Plugin Author Maciej Bis

    (@mbis)

    When it comes to the permalinks format:

    /stories (CPT)

    You can set this using rewrite base when the custom post type is registered:
    https://codex.www.ads-software.com/Function_Reference/register_post_type#rewrite

    Template: archive-{$post_type}.php => archive-stories.php

    /stories/art (category within CPT)

    This is controlled by the snippet I posted above.

    Template: taxonomy.php or category.php

    /stories/art/post (this is the single-stories.php, I know)

    This is controlled by Permalink Manager

    Template: single-{$post_type}.php => single-stories.php

    That is all I can do for you.

    • This reply was modified 5 years, 8 months ago by Maciej Bis.
    Thread Starter morespinach

    (@morespinach)

    Thank you so much. This is immensely helpful. I think your plugin should be taken up by WP Core.

    Thread Starter morespinach

    (@morespinach)

    Actually, sorry, this is not working. I’m sure I’m missing some small piece.

    In the CPT definition, I’ve now added has_archive = TRUE, and left it as the default slug. So archive-stories.php and archive-shop.php now work.

    However, because I have permastructs setup, I removed the “custom rewrite slug” from the CPT definition. This was set up as stories/%category%. If I remove this, the category URL doesn’t work inside this CPT (/stories/arts). Each category just uses the parent CPT template.

    If I add the “custom rewrite slug” back, then the core archive of the CPT (archive-stories) stops working.

    What am I missing, even with your extra plugin or functions.php code? And yes I did flush the permalinks, which are set up to be /%category%postname%.

    Many thanks for your patience!

    Plugin Author Maciej Bis

    (@mbis)

    The only way I can help you is to take a look at your current configuration on some live example. I would recommend to check your current rewrite rules using eg. this plugin:

    https://www.ads-software.com/plugins/monkeyman-rewrite-analyzer/

    Thread Starter morespinach

    (@morespinach)

    Thank you Maciej. I’ll email you as some of the info is sensitive. Much appreciated!

Viewing 14 replies - 1 through 14 (of 14 total)
  • The topic ‘Custom Post type pagination issue’ is closed to new replies.