AesopInteractive / lasso

Code Repository for Editus (formerly Lasso) Commercial Plugin
https://edituswp.com
GNU General Public License v2.0
147 stars 25 forks source link

Don't replace shortcodes of other plugins with html #107

Closed rhurling closed 8 years ago

rhurling commented 8 years ago

It would be awesome if lasso didn't replace existing shortcodes with html. And I think I figured out a solution to that problem.

I'm wrapping shortcodes with two html comments before they are outputted (only if lasso is activated and the user can use lasso). After that I'm rereplacing it in the lasso_object_save_args filter with only the shortcode.

The main problem is that lasso is replacing html comments in the shortcodify function with undefined, since comments don't have a outerHTML property. I fixed that by adding the following here: https://github.com/AesopInteractive/lasso/blob/18500353f112cecf3048c10d329e56ca46252f73/public/assets/js/source/process-save.js#L147

} else if ( component.context.nodeType == 8 ) {
    processed += '<!--' + j[i].data + '-->';

And here is my PHP code which does the heavy lifting

class Editus {

    public static function load() {
        if ( is_plugin_active( 'lasso/lasso.php' ) ) {
            add_action( 'plugins_loaded', [ __CLASS__, 'register_actions' ] );
        }
    }

    public static function register_actions() {
        if ( lasso_user_can() ) {
            if ( ! is_admin() ) {
                add_filter( 'the_content', [ __CLASS__, 'wrap_shortcodes' ] );
            }
            add_filter( 'lasso_object_save_args', [ __CLASS__, 'replace_to_shortcode' ], 1 );
        }
    }

    public static function wrap_shortcodes( $content ) {
        global $shortcode_tags;

        if ( false === strpos( $content, '[' ) ) {
            return $content;
        }

        if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) {
            return $content;
        }

        $tagnames = array_keys( $shortcode_tags );
        $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
        $pattern = "/\\[($tagregexp)/s";

        if ( 1 !== preg_match( $pattern, $content ) ) {
            // Avoids parsing HTML when there are no shortcodes or embeds anyway.
            return $content;
        }

        $content = do_shortcodes_in_html_tags( $content, true );

        $pattern = get_shortcode_regex();
        $content = preg_replace_callback( "/$pattern/s", [ __CLASS__, 'wrap_shortcode_tag' ], $content );

        // Always restore square braces so we don't break things like <!--[if IE ]>
        $content = unescape_invalid_shortcodes( $content );

        return $content;
    }

    public static function wrap_shortcode_tag( $m ) {
        // allow [[foo]] syntax for escaping a tag
        if ( $m[1] == '[' && $m[6] == ']' ) {
            return substr( $m[0], 1, - 1 );
        }

        if ( strpos( $m[2], 'aesop_' ) === 0 ) {
            return $m[0];
        }

        return '<!--LASSO_NORMAL_SHORTCODE_REPLACE_START|[' . $m[0] . ']-->' . $m[0] . '<!--LASSO_NORMAL_SHORTCODE_REPLACE_END-->';
    }

    public static function replace_to_shortcode( $args ) {
        if ( false === strpos( $args['post_content'], '<!--LASSO_NORMAL_SHORTCODE_REPLACE_START|' ) ) {
            return $args;
        }

        $args['post_content'] = preg_replace(
            '/<!--LASSO_NORMAL_SHORTCODE_REPLACE_START\|\[(.*?)\]-->(.*?)<!--LASSO_NORMAL_SHORTCODE_REPLACE_END-->/s',
            '$1',
            $args['post_content']
        );

        return $args;
    }

}
Editus::load();

The wrap_shortcodes function is pretty much a copy of the do_shortcodes function, the only difference being formatting and the different callback.

I'm not using Aesop Story engine, so I'm not entirely sure how that is affected, but I added an exception to not wrap shortcodes beginning with aesop_ with the html comments.

bearded-avenger commented 8 years ago

Lasso should only be replacing aesop_XXX shortcodes with HTML. It was built to make Aesop Story Engine easier to use.

rhurling commented 8 years ago

For me Lasso is replacing every shortcode in the content with the rendered HTML once i press save (Contact Form 7 for example).

rhurling commented 8 years ago

I know that the title of this issue is probably not describing it that well, but I can't come up with something that describes my issue better.

bearded-avenger commented 8 years ago

Oh, yep that's exactly right. Lasso saves the HTML of the_content entirely. What you can do is add the css class of the parent div so that Lasso will "ignore" the item. Happy to help through proper support channels help@lasso.is. This is also documented in the owners manual.

bearded-avenger commented 8 years ago

Also, happy to accept a PR with your above work provided that it works out well!

rhurling commented 8 years ago

I tried using the ignore class, but for me that just removed it entirely after saving.

rhurling commented 8 years ago

Okay, I'll try to submit a PR with this, since I think it's pretty important that Lasso doesn't destroy shortcodes of other plugins.

bearded-avenger commented 8 years ago

The way the ignore class works is that it will remove the item when you go to edit. Lasso really needs a dedicated content div (which is well documented) since it saves the HTML entirely (after processing through kses).

bearded-avenger commented 8 years ago

Excellent! I agree we'd like to remove the restriction of having that dedicated content div as there are cases where folks will use non aesop shortcodes within the_content.

michaelbeil commented 8 years ago

Very nice work @rhurling. As @bearded-avenger stated Editus needs a dedicated content div to function properly. We have had no issues with ignoring classes when in edit mode that are created from other shortcodes.

Also, if you send a PR, be sure to search and replace Lasso with Editus. As of version 0.9.8 Lasso has been renamed Editus.

rhurling commented 8 years ago

@michaelbeil The problem with using the ignoring classes is that the shortcode is removed entirely on edit (so the rendered content gets lost), which is fine for plugins that hook into the the_content filter and add html before or after the content, or place it somewhere in between. But its entirely insufficient if you use a shortcode that outputs something dynamic (for example something like [current_date format="d.m.Y"]) since that's just replaced with the current output of that.

I won't have to replace Lasso with Editus if I'm branching off of the release/0.9.8 branch right?

Edit: Better example of the current behavior Lasso shows for me. Page with following content (as in database): <h1>Current Date: [current_date]</h1> After Save with Lasso: <h1>Current Date: 08/25/2015</h1> After Save with Lasso + PR i'm going to do: <h1>Current Date: [current_date]</h1>

hyunsupul commented 8 years ago

Thank you for this. I made a minor fix in lasso_wrap_shortcodes() to get rid of the warning.