Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I have 700 posts mostly in excess of 1000 words each. I'm setting this up as a membership site. I'm using wishlist member and would like to protect all the content after the first 350 words. This requires placing a short tag after the 350th word and another short tag at the very end of the post.

Adding the shortcode at the end seems pretty straightforward. However, I can’t figure out how to get the short code into the middle of post_content..

I’ve considered a couple different approaches including:

a. Trying to figure out a regular expression find and replace in dreamweaver working with the xml flat files b. Trying to work with a query in mysql to query the post content field, count the words in that field, and then insert the shortcode tag there c. Running some php script to achieve the result (something similar here, but not quite the same http://bacsoftwareconsulting.com/blog/index.php/wordpress-cat/how-to-create-a-variable-length-excerpt-in-wordpress-without-a-plugin/ ) d. Or some combination of the above or some more incisive simpler answer

As I am looking to make a permanent change to the data (and not do something on the fly with the theme each time an article loads up) the mysql option seems like the most appropriate way to do this.

This is not a find and replace in the simple context of looking for a specific string and replacing it, but instead counting to a certain position in post_content and then either

  1. adding a shortcode [example_shortcode] or
  2. copying the first portion of post_content up to the and including the 350th word, concatenating that with [example_shortcode], plus all remaining text after the 350th word, plus the closing [/example_shortcode]

Some of my research leads so far: ergo a little more involved than example of search and replace query

I suspect it may require the substring function more on substrings in mysql as in this example where substrings were used for pagination pagination link

SELECT SUBSTRING(post_content,1000,1000) FROM posts WHERE id=x

Visual example of my goal

How can I insert the first tag after say the first 350 words of a post and the last tag at the end of a post and repeat across all of our posts 700+ posts? ergo

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per

skip to -> ** word 350 ->Vestibulum [example_shortcode]**ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit

skip to end of post Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum si. [/example_shortcode]


For a variety of reasons, the path of simply writing a function to dynamically add the tags as opposed to updating the db has proven unworkable. The main reason is due to a deficiency (lack of options) in the Wishlist member api.

bottom line it doesn't support using the php version of short tags. (note if I had it to do over again I probably would have gone with a different member plugin solution and not something so closed down as wishlist).

That said, the only viable option at this point is to simply update the db by inserting the shortags into the posts with the appropriate category. (This is what I asked here in the original question and thus far no solutions have been suggested, although the alternatives were plausible they were not workable in this situation).

So now, I'm half way back to the first step. How to do a mass update of almost all wordpress posts. Specifically all posts that are not in two categories.

There are 3 related suggestions that I have found to do mass updates in wordpress although they mostly perform updates on fields in wp_posts and not an update within the string of post_content.

  1. See http://stackoverflow.com/questions/7917725/update-all-wordpress-posts

    /* Plugin Name: Example Description: This is not just a plugin, it's CODE.. Author: */ add_action('init','example_hide');

    function example_hide(){

    $my_posts = get_posts( array('post_type' => 'post', 'numberposts' => 10 ) );

    foreach ( $my_posts as $my_post ):

    $my_post['post_content'] = 'This is the updated content.';

    wp_update_post( $my_post );

    endforeach;
    

    }

  2. Then see this example that only updates slugs (something far easier to do in mysql btw http://stackoverflow.com/questions/12707469/update-all-the-posts-at-once-nothing-want-to-add-or-delete-only-update-so-that-p

    // get all posts $posts = get_posts( array ( 'numberposts' => -1 ) );

    foreach ( $posts as $post ) { // check the slug and run an update if necessary $new_slug = sanitize_title( $post->post_title ); wp_update_post( array ( 'ID' => $post->ID, 'post_name' => $new_slug ) );

    }

Then 3 and I think this has the most promise http://www.michaelbrentecklund.com/wordpress-mass-update-posts-mass-update-pages-custom-post-types/07-18-2012/

/** 
 * Mass Update Posts
 *
 * Mass update portions of posts, pages or custom post types.
 * Get all posts from a specified post type, determine the
 * status of the post, choose a portion of the post structure,
 * search for a string value associated with the specified key,
 * specify a replacement string, and choose whether you want to 
 * search for a partial or exact match.
 *
 * @param string $post_type Post type to fetch posts from.
 * @param string $post_status Status of the post (eg. publish, private).
 * @param string $key Portion of the post (eg. post_status, post_type).
 * @param string $find Value of the portion of post (eg. publish, page).
 * @param string $replace String to replace value with. (eg. private, post).
 * @param string $match_type Accepts 'partial' or 'exact' match.
 **/
function mass_update_posts($post_type, $post_status, $key, $find, $replace, $match_type){
    $args = array(
        'numberposts'   => -1,
        'post_type' => $post_type,
        'post_status'   => $post_status
    );
    $posts = get_posts($args);
    $return = '';
    for($i = 0; $i < count($posts); $i++){
        // Check if the portion of the post exists.
        if(array_key_exists($key, $posts[$i])){
            if($match_type == 'exact'){
                // Check if the portion of the post matches exact search.
                if($posts[$i]->$key == $find){
                    $action = 'update';
                } else{
                    $action = 'return';
                }
            } elseif($match_type == 'partial'){
                // Check if the portion of the post matches partial search.
                $search = strpos($posts[$i]->$key, $find);
                if($search !== false){
                    $action = 'update';
                } else{
                    $action = 'return';
                }
            } else{
                $return .= '<p><strong>Error!</strong> Specify either &quot;partial&quot; or &quot;exact&quot; match.</p>';
            }
            // Found the search query. Carry on.
            if($action == 'update'){
                // Set the portion of the post to be updated.
                $update['ID'] = $posts[$i]->ID;
                $update[$key] = $replace;
                // Update the portion of the post.
                $updated = wp_update_post($update);
                // Check if update passes/fails.
                if($updated){
                    //array_push($replaced, array($posts[$i]->$key => str_replace($find, $replace, $posts[$i]->$key)));
                    $replaced[][$posts[$i]->$key] = str_replace($find, $replace, $posts[$i]->$key);
                } else{
                    $return .= '<p><strong>Error!</strong> Key: '.$key.' was found, however, failed to update the post: <strong>&#35;'.$posts[$i]->ID.'</strong> <em>'.$posts[$i]->post_title.'</em>.</p>'; 
                }   
            } elseif($action == 'return'){
                $return .= '<p><strong>Error!</strong> Could not find &quot;'.$find.'&quot; in key: <strong>'.$key.'</strong>.</p>';
            }
        } else{
            $return .= '<p><strong>Error!</strong> Invalid key specified.</p>';
        }
    }
    // Posts were updated. Output the result.
    if(!empty($replaced)){
        $return .= '<p><strong>Success!</strong> Updated '.count($replaced).' posts.</p>';
        foreach($replaced as $key => $value){
            foreach($value as $old => $new){
                $return .= '<p><strong>Old:</strong> <em>'.$old.'</em> <span style="padding: 0px 24px;">&#61;&#62;</span><br /> <strong>New:</strong> '.$new.'</p>';
            }
        }
    }
    echo $return;
}

I think that a combination of that last function combined with the evolving content limit function as initially conceived below by @userabuser and modestly amended by myself, might ultimately prove to be the solution...

//* Limited Content
add_filter('the_content', 'limited_content', 11);


function post_is_in_descendant_category( $cats, $_post = null ) {
    foreach ( (array) $cats as $cat ) {
        // get_term_children() accepts integer ID only
        $descendants = get_term_children( (int) $cat, 'category' );
        if ( $descendants && in_category( $descendants, $_post ) )
            return true;
    }
    return false;
}

add_filter('the_content', 'do_shortcode', 11); // From shortcodes.php


function limited_content($content) {

    $limit   = 350; //word limit for non-member/non-subscriber
    $words   = str_word_count($content);
    $user_id = get_current_user_id();

//if it is not in either category we have to test membership
if ( in_category( array('2497','2811')) /*|| post_is_in_descendant_category(array('2497','2811')) */ ) {
    return $content;

    //check if the user is part of the "member" role if so, return the full content
    } elseif ( user_can($user_id, 'member') ) {
                return $content;

            //otherwise for non members return 350 of the content
            } elseif ( $words >= $limit) {

                $contentteaser = explode(' ', $content, $limit);
                array_pop($contentteaser);
                $contentremainder = explode(' ', $content);
                $contentremainder = array_slice($contentremainder, 349);
                $contentremainder = implode(" ", $contentremainder);
                $contentteaser = implode(" ", $contentteaser);
                echo do_shortcode($contentteaser.' '.'[private_Silver level]'.$contentremainder.'[/private_Silver level]'. '... <a href="#">sign up to read more</a>'); 



            //if there's less than 350 words, just return whatever exists
            } else {
                return $content;
            }

}  // end function Limited Content
share|improve this question
add comment

2 Answers

The following will allow you to limit content to any word count you like based upon whether the user is part of a certain user role or not. The function can be improved and or made more efficient but at least it provides you the basis for filtering your content without having to physically edit the content or run any complex regular expressions.

add_filter('the_content', 'limited_content', 11);

function limited_content($content) {

    $limit   = 350; //word limit for non-member/non-subscriber
    $words   = str_word_count($content);
    $user_id = get_current_user_id();

    //check if the user is part of the "member" role if so, return the full content
    if( user_can($user_id, 'member') ) {

        return $content;

    //otherwise for non members return 350 of the content
    } elseif ( $words >= $limit) {

        $content = explode(' ', $content, $limit);
        array_pop($content);
        $content = implode(" ", $content);
        return $content . '... <a href="#">sign up to read more</a>';

    //if there's less than 350 words, just return whatever exists
    } else {

        return $content '... <a href="#">access premium content now!</a>';;

    }

}

Modified your edit so you can see the logic properly,

add_filter('the_content', 'limited_content', 11);

function limited_content($content) {

    $cats    = array(2497, 2811);
    $limit   = 350; //word limit for non-member/non-subscriber
    $words   = str_word_count($content);
    $user_id = get_current_user_id();

    if ( in_category( $cats ) || post_is_in_descendant_category( $cats ) ) {

                if( user_can($user_id, 'member') ) {

                    //return full content to members, script will stop at this point        

                } elseif ( $words >= $limit) {

                    //limit content where content exceeds 350 words to non-members

                } else {

                    //if content contains less than 350 words, return all of it, even if non-member
                }

    } else { 

        return $content; //if we are not in/descendant of $cats the content returns normally regardless of user role.
    }

} 

I anticipated that you would say something like this,

Although I'd rather do a one time change/update of the database so as to avoid continually running a function on the site... - Brett Bumeter

You might think that what you're doing is a one-time change, but in fact its not!

Wrapping your content after 350 words in shortcodes for each of your 700 posts is potentially disasterous and here's why.

The shortcode itself is a function or rather it refers to a function, so when any piece of content that contains this shortcode (in the_content) is called upon, it will execute its logic.

You would need to wrap each of your 700 posts with a shortcode at the mark of 350 words, so that's 700 shortcodes added to your database in the post_content - which although may be considered a small increase, is still an uneccesary increase in database size.

What happens if for some reason you decide you want to now restrict content after 200 words instead of 350?

You're in trouble. Can't be done, you need to physically move the shortcode position.

Or what about the scenario where you decide that you want to BOTH restrict content after 200 words and 350 words depending upon membership levels?

You're in trouble. Can't be done, you need to physically move the shortcode position and modify the function that controls the shortcode to allow for conditional restrictions.

What a nightmare right?

This is where filters come into play and that's why they exist and importantly this is why they are the most efficient way of handling and manipulating data without ever having to modify the original contents of your posts.

This is how your data should be treated. Instead of polluting your post content with logic, keep it pure and pristine, let filters do the crunching and heavy lifting for you to achieve your requirements.

This makes your content very flexible as it can be served in multiple ways to multiple people without you smacking your head against the wall. Ultimately, filters make your membership site powerful, shortcodes make your membership site weak. Filters allow you to scale your ideas, shortcodes box you into a corner.

Shortcodes are useful for repetitious tasks such as inserting verbose content into your post when writing, like maybe a bullet point image, or a special link or alert box that you might regularly use in the middle of your content or subscription links or... you get the gist. But when it comes to manipulating and controlling the output of content, shortcodes are a bad choice.

share|improve this answer
    
Although I'd rather do a one time change/update of the database so as to avoid continually running a function on the site, this function does move me along to the display I'm looking for almost... :) However, I need to only run that function on 2 categories or their dependents and I'm getting stuck on the syntax now I think. Slightly altered the above to the following but it either limits everything or limits nothing. (I suspect that it is getting confused by posts that have more than one category) –  Brett Bumeter Oct 24 '12 at 23:18
    
Thanks for the perspective, and I do not disagree in some ways. However, the Wishlist plugin which I'm using is built to use these shortcodes, not the other way around. So if I want to keep the site pristine and prepared for future changes/upgrades to the plugin, its best to go with their short codes. Similarly, removing those short codes from the post_content is very simple in terms of find and replace. It is the initial addition after the nth word that I'm stuck on. (that and can't get the filter above to execute properly yet) ;) –  Brett Bumeter Oct 25 '12 at 14:28
    
On second thought, the function can always be removed so from that perspective its a wash as to what's easier removing the short tags from content or removing the function. One of my main reasons against a filter would be adding anything that isn't absolutely necessary to call the db and perform a function, and in this case a rule that is hidden from users as opposed to shortcodes they can see and manipulate if they need to. –  Brett Bumeter Oct 25 '12 at 14:53
1  
@BrettBumeter I'm struggling to understand your main reason though because the_content gets called anyway, adding a filter which ONLY runs its logic when conditions are met places no more burden than a shortcode that runs its logic. Each time your query/loop iterates over the posts it returns, if it finds a shortcode it will execute it again and again for each instance where a shortcode is present. Not only that but you'll suffer the same issues I describe above, such as cluttering post content with repetitious post logic that otherwise need not be there. That's poor design, imo. –  userabuser Oct 25 '12 at 15:05
1  
+1 @BrettBumeter You should really not!! modify the DB. Shortcodes are basically a bad thing - read the explanations. They stay in your content, can get moved (lacking the flexibility of a filter, which can simply be plugged out - or turned off using the code as a plugin). It's also a pain to remove shortcodes from the content if you decide to switch the shortcode for something else. Then the left over one will simply get printed on screen and finito! –  kaiser Oct 25 '12 at 15:18
show 3 more comments

Instead of going to the database, what's the problem in doing it directly on the front-end when displaying the text? It would be extremely simple, take a look at this example

add_filter('the_content', 'split_content', 11);//high priority to let wordpress apply it's own filters first
function split_content($content) {
    $premium_member = false; //determining if we need to protect the post

    if($premium_member)
        return $content;
    else
        return substr($content, 0, 350); //return the first 350 characters
}

substr is not something you should use as it might break the html, but you get the idea ;)

share|improve this answer
    
In that case you could use strip_tags to avoid breaking the html but that will remove all formatting. Or perhaps balanceTags() would be sufficient? –  userabuser Oct 24 '12 at 13:38
    
that definitely simplifies the if else conditions a bit... I'm still exploring the other answer at the moment, but will circle back. I suspect this along with the other answer might offer a good hybrid maybe. –  Brett Bumeter Oct 24 '12 at 23:44
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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