Create custom [sourcecode] shortcode, the right way?

What I want to do

Sharing code in posts on WordPress is quite a pain with all the HTML escaping to take care of. So, I plan to enclose code in posts within a custom shortcode called [sourcecode], as shown below, and let it automatically escape the special characters.

[sourcecode]
<?php

    // Some code here.

?>
[/sourcecode]

The code

And this is what the relevant function (in my theme’s functions.php) looks like:

Read More
/**
 * Functionality to set up custom shortcode correctly.
 *
 * This function is attached to the 'the_content' filter hook.
 */
add_filter( 'the_content', 'run_bbcodes', 7 );
function run_bbcodes( $content ) {
    global $shortcode_tags;

    $orig_shortcode_tags = $shortcode_tags;
    $shortcode_tags = array();

    // New shortcodes below
    add_shortcode( 'sourcecode', 'bbcode_code' );

    $content = do_shortcode( $content );
    $shortcode_tags = $orig_shortcode_tags;

    return $content;
}

/**
 * Add Custom shortcode functions below
 *
 * This function is attached to the 'sourcecode' shortcode hook.
 */
function bbcode_code( $atts, $content = null ) {

    // Ensure contents of a <pre>...</pre> HTML block aren't converted into paragraphs or line-breaks
    $content = clean_pre( $content );

    $content = str_replace(

        // Replace these special characters...
        array( '&', '', '<', '>', ''', '"', '/', '[', ']' ),

        // ...with the HTML entities below, respectively
        array( '&amp;', '\0', '&lt;', '&gt;', '&apos;', '&quot;', '/', '[', ']' ),

        $content
    );

    return '<pre><code>' . trim( $content ) . '</code></pre>';
}

/**
 * Related sourcecode worth reading:
 *
 * https://bitbucket.org/cbavota/syntax-highlighter-plugin/src
 *
 * https://github.com/mdawaffe/Highlight.js-for-WordPress/blob/master/highlight-js.php
 *
 * https://github.com/fracek/highlight-wp/blob/master/highlight.php
 *
 * http://plugins.svn.wordpress.org/syntaxhighlighter/trunk/
 *
 * http://blog.webspace.jp/235
 *
 * http://core.trac.wordpress.org/browser/trunk/src/wp-includes/shortcodes.php
 */

Question(s)

Now that the explanation is out of the way…

  1. Am I missing anything else?

    For example, until I read the source of SyntaxHighlighter Evolved plugin, I didn’t know that also needs to be replaced with \0 “to work around kses” (which wasn’t clear enough for me).

  2. And other than escaping the special characters, is there anything else that I could possibly be missing? Am I doing it the right way?

    (It would be great if anyone could take a look at this file (PHP source code of SyntaxHighlighter Evolved plugin) and see if you can find something that I need to implement. I already did try my best though.)


PS: Why not use the plugin itself? For one, I don’t want the syntax highlighting that it does; and I want to know if what it does can be implemented easily.

Related posts

2 comments

  1. You’re asking about X, but I’ll answer with Y. IMHO, there are too many exceptions to handle to grant another solution.

    SyntaxHighlighter Evolved has the following Pro Tip (strong, uppercase, in the original):

    TIP: Don’t use the Visual editor if you don’t want your code mangled. TinyMCE will “clean up” your HTML.

    Not only that, it’s quite common to have plugins and themes messing around with the_content. Rough (and totally unscientific) measure: 10 occurrences within 50 plugins; 15 occurrences within 50 themes.

    Another Pro Tip:

    enter image description here

    Proposed solution

    1) To handle the code, I think it’s either a Custom Field/Box in the Edit Post screen or a bridge between the Post and a “private” Custom Post Type that will store the code. I prefer the latter. Steps:

    • paste the embeddable code into a <textarea> rendered with edit_form_after_title
    • use esc_html to display in the backend
    • and '<pre class="pretty">'.htmlentities($code).</pre> in the frontend

    2) To handle the syntax highlight, it’s up to the reader to choose its preferred method. Google Prettify is literally 2 lines of code and adding a class to the <pre> tag. Enqueue the script only when the shortcode is present.

    Sample plugin

    Custom post type
    enter image description here

    Post post type
    enter image description here

    The following is a skeleton and only outlines the logic. And here the links for some goodies:
    Working skeleton at Gist
    Full plugin at GitHub

    <?php
    /**
     * Plugin Name: Skeleton for the plugin Snippets Shortcode
     * Plugin URI: https://gist.github.com/brasofilo/6804951
     */
    
    /**
     * Based on Plugin Class Demo
     * https://gist.github.com/toscho/3804204
     */
    add_action(
        'plugins_loaded',
        array ( B5F_Snippets_Shortcode::get_instance(), 'plugin_setup' )
    );
    
    /**
     * Main class
     *
     * Fires all classes
     * Handles save_post action for other classes
     */
    class B5F_Snippets_Shortcode {
        protected static $instance = NULL;
        public $post_type = 'snippet';    
        public function plugin_setup() {
            new B5F_SS_Cpt();
            new B5F_SS_Posts_Pages_Metabox();
            new B5F_SS_Shortcode();
            add_action( 'save_post', array( $this, '_save_post' ), 10, 2 );
        }
    } 
    
    /**
     * Register Snippet post type and render Textarea Field after the Title
     *
     * CPT is hierarchical 
     * Custom Metabox for CPT is left empty, can be used as Language Selector
     * Uses save_post for CPT 
     */
    class B5F_SS_Cpt {
        public function __construct() {
            add_action( 'init', array( $this, '_cpt' ) );
            add_action( 'edit_form_after_title', array( $this, 'input_text_area' ) );
        }
    }
    
    /**
     * Metabox to select the Snippet and to display the correspondent Shortcode
     *
     * Displayed in Posts and Pages post types
     * Add jQuery to listen to the Snippets Dropdown changes and update the sample Text Field
     * Uses save_post for other Post Types 
     */
    class B5F_SS_Posts_Pages_Metabox {
        public function __construct() {
            add_action( 'add_meta_boxes', array( $this, '_meta_boxes' ) );
        }
    }
    
    /**
     * Snippets Shortcode
     *
     * First gets the Snippet Post ID, then its associated Meta Data, 
     * finally filters the string with http://php.net/manual/en/function.htmlentities.php 
     *
     * Uses Google Code Prettify
     * https://code.google.com/p/google-code-prettify/
     */
    class B5F_SS_Shortcode {
        public function __construct() {
            add_shortcode( 'snippet', array( $this, '_shortcode' ) );
        }
    }
    
  2. Just in case Rodolfo’s answer isn’t clear, here’s what (I think) he’s suggesting — an excellent idea, by the way:

    1. Create a private custom post type called Snippets, in whose posts we’ll be storing our code snippets.

      We’ll use the supports argument in register_post_type to our advantage. We don’t want the Content Editor on the edit screen for the custom post type as we’ll be storing our content in a custom meta box[1] (essentially a custom field), so we’ll hide it; and other unnecessary fields too as this’ll be a private custom post type.

      Here’s an example to give you an idea as to how it’d be done:

      /*
       * Register Custom Post Types
       * DON'T FORGET TO FLUSH PERMALINKS
       *
       * http://justintadlock.com/archives/2013/09/13/register-post-type-cheat-sheet
       * https://gist.github.com/justintadlock/6552000
       * http://core.trac.wordpress.org/browser/trunk/src/wp-includes/post.php
       */
      add_action( 'init', 'itsme_register_post_types' );
      function itsme_register_post_types() {
      
          // Post Type: Snippets
          register_post_type(
      
              // Keeping it unique so that it never conflicts with a plugin or theme
              'itsme_snippet',
      
              array(
                  'description'         => 'Code snippets for sharing in posts.',
                  'public'              => false, // NOTE!
                  'publicly_queryable'  => false, // NOTE!
                  'exclude_from_search' => true, // NOTE!
                  'show_in_nav_menus'   => false,
                  'show_ui'             => true,
                  'show_in_menu'        => true,
                  'show_in_admin_bar'   => false,
                  'menu_position'       => 5, // Your choice.
                  'menu_icon'           => null,
                  'can_export'          => false,
                  'delete_with_user'    => false,
                  'hierarchical'        => false,
                  'taxonomies'          => array(),
                  'has_archive'         => false, // NOTE!
                  'query_var'           => false, // NOTE!
                  'capability_type'     => 'post',
                  'rewrite' => array(
                      'slug'            => 'snippets',
                      'with_front'      => false,
                      'pages'           => false,
                      'feeds'           => false, // NOTE!
                  ),
      
                  // IMPORTANT! At least make sure that the 'editor' (Content Editor) is hidden - i.e. remove it from the array.
                  'supports'             => array( 'title', 'revisions', ),
      
                  'labels' => array(
                      'name'               => __( 'Snippets' ),
                      'singular_name'      => __( 'Snippet' ),
                      'menu_name'          => __( 'Snippets' ),
                      'name_admin_bar'     => __( 'Snippets' ),
                      'add_new'            => __( 'Add New' ),
                      'add_new_item'       => __( 'Add New Snippet' ),
                      'edit_item'          => __( 'Edit Snippet' ),
                      'new_item'           => __( 'New Snippet' ),
                      'view_item'          => __( 'View Snippet' ),
                      'search_items'       => __( 'Search Snippets' ),
                      'not_found'          => __( 'No Snippets found' ),
                      'not_found_in_trash' => __( 'No Snippets found in trash' ),
                      'all_items'          => __( 'All Snippets' ),
                      'parent_item'        => __( 'Parent Snippet' ),
                      'parent_item_colon'  => __( 'Parent Snippet:' ),
                      'archive_title'      => __( 'Snippets' ),
                  )
              )
          );
      
      }
      

      Related Links: (Because I haven’t given any explanation within the code.)

    2. Create a ‘textarea’ type meta box (essentially a custom field) which is shown only on the edit screen for the custom post type Snippets.

      If you don’t know how to code one, you can easily create one using a plugin like Advanced Custom Fields. Here’s a screenshot just to give you an idea:

      Create a custom meta box using Advanced Custom Fields plugin

    3. Create a custom shortcode that takes in the id of the snippet post and displays content (i.e. code snippet) between <pre> tags; with all HTML special characters properly escaped, of course.

      Here’s an example to give you a good idea:

      /*
       * Custom Shortcode: Snippet
       * USAGE: [snippet id="188"], where id = Snippet (CPT) post's ID
       *
       * http://wpquestions.com/question/showChrono?id=8901
       */
      add_shortcode( 'snippet', 'bbcode_snippet' );
      function bbcode_snippet( $atts ) {
          extract( shortcode_atts( array(
              'id' => null,
          ), $atts ) );
      
          $post = get_post( $id );
          $content = $post->post_content;
          $content = trim( htmlspecialchars( clean_pre( $content ) ) );
      
          return '<pre><code>' . $content . '</code></pre>';
      }
      

      Related Links:

    4. To ease things a bit, create a custom meta box that displays the shortcode to use (e.g. [snippet id="120"]) on the edit screen of the custom post type (Snippets). So once you publish the snippet, you can copy the shortcode for embedding the code in your posts.

      add_action( 'add_meta_boxes', 'itsme_custom_meta_boxes' );
      function itsme_custom_meta_boxes() {
          add_meta_box(
      
              // HTML 'id' attribute of the edit screen section (AKA meta box)
              'itsme-snippet-shortcode-box',
      
              // Title of the meta box, visible to user
              esc_html__( 'Snippet Shortcode' ),
      
              // Callback function
              'itsme_snippet_shortcode_box',
      
              // Custom post type on whose edit screen to show meta box
              'aahank_vault',
      
              // The part of edit screen where the meta box should be shown
              'side',
      
              // Priority within the context where the meta box should show
              'high'
      
          );
      }
      
      // Function to display the meta box
      function itsme_snippet_shortcode_box() {
      
          global $post;
          $snippet_id = $post->ID;
          echo '<textarea rows="1" readonly="readonly" placeholder="Publish post first!" onclick="this.focus();this.select();">[snippet id="' . $snippet_id . '"]</textarea>';
      
      }
      

    That’s pretty much all we need. The syntax highlighting and stuff are extra so I am not getting into any further details.

    Footnotes:

    1. Plugins and themes, like Rodolfo said, can easily mess up with the_content. Since we want to leave our code snippets untouched (unless WE want to), we are using a custom field to store our data, i.e. code snippets.

      Also, WordPress, by default, has a lot of filters on the_content, making it quite complex to do what we want. Using a custom field instead makes it a whole lot easier and straightforward.

    Ideas:

    1. You can use a custom taxonomy called language or something to organize posts of the custom post type, and even use it to syntax highlight code.

Comments are closed.