2

I am building a calendar plugin that can be added within a page via shortcode. I am trying to find how I can build a link that links to the page the shortcode for the calendar is in. So another words if a user adds [calendar-shortcode] to a page I want to ability to link to that page from anywhere within the site. So far this is what I have come up with, but I am wondering if there is another way.

/**
* Get page shortcode is on.
*
* @param $shortcode string. The shortcode tag
* @param $transient string. Name of transient
* @return array|mixed
*/
function get_shortcode_page($shortcode, $transient){

if(false === ($shortcode_page = get_transient($transient))){

    //Search query for shortcode tag
    $the_query = new WP_Query(array('s' => $shortcode));

    //Build simple array of page info to pass into transient
    if(isset($the_query) && $the_query->post_count != 0){
        $shortcode_page = array('ID' => $the_query->posts[0]->ID, 'permalink' => get_permalink($the_query->posts[0]->ID));

        set_transient($transient, $shortcode_page);
    }
    return null;

   }
  return $calendar_page;
}

//Use function to find shortcode page info
$page = get_shortcode_page('class-schedule', 'shortcode_calendar_page');

 /**
 * Delete shortcode transients
 *
 * If page permalink changes then delete old transients.
 *
 *
 * @param $post_id
 */
function delete_calendar_shortcode($post_id){

//get shortcode page.
$calendar_page = get_shortcode_page('class-schedule', 'shortcode_calendar_page');

    if($post_id != $calendar_page['ID'])
        return $post_id;

    $current_permalink = get_permalink( $post_id );

    if ($current_permalink != $calendar_page['permalink']){
        delete_transient( 'shortcode_calendar_page' );
   }

   return $post_id;
}

add_action('save_post',  'delete_calendar_shortcode');

This has not been tested much but it does work. I want to know if there is a more efficient way of doing this?

Thanks

UPDATE:

Here is version 2 of above: This will remove the need for the get_shortcode_page() function above. Everything will be handled on page save.

/**
 * Delete shortcode transients vs 2
 *
 * If page permalink changes then delete old transients.
 *
 *
 * @param $post_id
 */
function delete_calendar_shortcode_vs2($post_id){

// If this is an autosave, our form has not been submitted,
if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE){
    return $post_id;
}

$content_post = get_post($post_id);
$content = $content_post->post_content;

//only run if current page being saved has the shortcode
if(has_shortcode($content, 'class-schedule')){

    //Build simple array of page info to pass into transient
    $page = array('ID' => $post_id, 'permalink' => get_permalink($post_id));

    if(false !== ($calendar_page = get_transient('shortcode_calendar_page'))){

        if($page['ID'] != $calendar_page['ID'] || $page['permalink'] !=   $calendar_page['permalink']){
            delete_transient('shortcode_calendar_page');
        }
    }

    set_transient('shortcode_calendar_page', $page);
  }

return $post_id;
}

 add_action('save_post', 'delete_calendar_shortcode_vs2');

//get shortcode page from transient
$page = get_transient('shortcode_calendar_page');

Again...I am looking to see if there is another way that makes more sense. I also need to add rewrite rules based off the page the shortcode is on. I figured one step at a time. thanks

//////// UPDATE V3 with the aid of @true ////////////

    /**
     * Shortcode page meta builder
     *
     * Checks on save if shortcode for page exist and saves info to meta.
     *
     * @param $post_id
     */


     function has_shortcode_meta_builder($post_id){

        // If this is an autosave, our form has not been submitted,
        if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE){
            return $post_id;
        }

        $content_post = get_post($post_id);
        $content = $content_post->post_content;

        if(has_shortcode($content, 'class-schedule')){
            update_post_meta($post_id, 'has_shortcode', '1');
            delete_transient('shortcode_pages');

        }else{
            //check if meta exist in the event shortcode was removed from page.
            $meta = get_post_meta($post_id, 'has_shortcode');
            if(!empty($meta)){
                delete_post_meta($post_id, 'has_shortcode');
                delete_transient('shortcode_pages');
            }
        }

        return $post_id;
    }

    add_action('save_post', 'has_shortcode_meta_builder');

This function will then be used to find all the pages with the shortcode. Either return the transient results or makes a sql call for new results to rebuild the transient.

    /**
     * Get all posts and their permalinks that have shortcode.
     * Find pages with shortcode and store them into transient
     *
     * @global wpdb $wpdb
     * @return array. 
     */
    public function get_pages_with_shortocde(){
        global $wpdb;

        if(false === ($post_data = get_transient('shortcode_pages'))){

            $post_data = $wpdb->get_results($wpdb->prepare(
                "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s", 'has_shortcode'));

            if(!empty($post_data)){
                foreach($post_data as $row){
                    $post = get_post($row->post_id);
                    $row->slug = $post->post_name;
                    $row->post_permalink = get_permalink($row->post_id);
                }
            }
            set_transient('shortcode_pages', $post_data);
        }

        return $post_data;
    }

Finally add an action so when post are deleted it will check if page does or does not have the shortcode within it. If it did then delete the shortcode transient (which will refresh next time called)

    /**
     * Delete actions when post is deleted.
     *
     * @param $post_id
     */
     function delete_post_data($post_id){
        global $wpdb;

        //Delete shortcode transient so that it can be rebuilt excluding this page
        $content_post = get_post($post_id);
        $content = $content_post->post_content;
        if(has_shortcode($content, 'class-calendar)){
            delete_transient('shortcode_pages');
        }

    }
 add_action('before_delete_post', 'delete_post_data');
  • rather than using a shortcode, I think it makes more sense to provide an interface to allow users to select the page they want the calendar to appear on, the same way WordPress lets you choose a static front page and posts page. – Milo Nov 10 '14 at 4:00
  • The only advantage with a shortcode is that the user will have control of where on the page the calendar can go. They can also control what content can come before and after the calendar on the page. – David Labbe Nov 10 '14 at 11:03
2

Transients are probably not the way to go here.

From what I understand:

  1. You want to know if a post has a shortcode by doing some form of look up

  2. You want to be able to get a list of permalinks / posts that have this shortcode.

Here's what I suggest.

/**
 * Checks for calender shortcode and flags the post with post meta.
 * @param integer $post_id
 * @return integer
 */
function save_calender_postmeta($post_id)
{
    // Make sure this isn't an autosave.
    if(!defined('DOING_AUTOSAVE') || !DOING_AUTOSAVE){
        // Get Post Content
        $content = get_post_field('post_content', $post_id);
        // Check for shortcode
        if(has_shortcode($content, 'class-schedule')) {
            // Update (or it will insert) the 'has_calender' post meta.
            update_post_meta($post_id, 'has_calender', '1');
        }
        else {
            // Delete the has calender post meta if it exists.
            delete_post_meta($post_id, 'has_calender');
        }
    }
    return $post_id;
}
add_action('save_post', 'save_calender_postmeta');

And then, if you want to get all the posts that have calenders you could use:

/**
 * Get all posts and their permalinks that have calenders.
 * @global wpdb $wpdb
 */
function get_calender_posts_links()
{
    global $wpdb;
    $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s",
            'has_calender'
    ));
    if(!empty($rows)) {
        // Let's include the post permalinks with each row.
        foreach($rows as $row) {
            $row->post_permalink = get_permalink($row->post_id);
        }
    }
    return $rows;
}

Or if you wanted to check if a single post had a calender:

/**
 * Check if a post has a calender.
 * @param int $post_id
 * @return boolean
 */
function post_has_calender($post_id)
{
    return (get_post_meta($post_id, 'has_calender'));
}

Let me know if this is what you were looking for.

  • thanks for this. I guess I have a few options to think about. My only concern is that the custom sql ($wpdb) is not cached however transients are naturally cached. Why would you not use transients? – David Labbe Nov 10 '14 at 23:59
  • @DavidLabbe Two reasons. First, transients are really meant for storing "any data that is expected to expire, or which can expire at any time." So transients are for data that you want to expire after a certain time period. In your problem, the data you are dealing with is permanent in that it only changes when a user saves a post. (Essentially making it the same kind of data as all the data Wordpress saves naturally to the database such as the title, content.) Second, post_meta is naturally cached by Wordpress which takes care of the caching problem as well. – true Nov 11 '14 at 12:17
  • I will agree, but they don't have to expire and since they are cached they are much faster to call. I am testing a version of your code from above but then storing those results into transients that will refresh whenever posts are saved, updated and deleted. This has saved 4 query calls per page load on pages needing the links to the shortcode page. – David Labbe Nov 11 '14 at 12:26
  • I do see what you mean with ($wpdb) not being cached. (I misread and couldn't edit) You could go with caching the results yourself: codex.wordpress.org/Class_Reference/WP_Object_Cache Or you could try for a DB caching plugin. But more than that, unless you're in a specific situation with millions of posts with calenders and every $wpdb use is critical, you should go with the most maintainable and easy to follow code first, and then profile performance later and change it if it is an issue. One ($wpdb) call like this should not impact performance in any meaningful way. – true Nov 11 '14 at 12:36
  • Also - maybe I'm missing something, but what if the transient you are using expires? Transient data isn't guaranteed to stick around and may disappear. If that happens, such as in your original problem, you will lose access to which posts have the calender flag and those posts won't be listed as having a calender until the user saves those posts again. This would make any code that relies on having an accurate list of calender posts inaccurate and unpredictable. Whether a post has a calender is permanent, persistent data, whereas a transient is not. – true Nov 11 '14 at 12:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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