Version: current

      1 <?php
      2 /**
      3  * A simple parser that allows you to map BBCode-like "shortcodes" to an arbitrary callback.
      4  *
      5  * Shortcodes can take the form:
      6  * <code>
      7  * [shortcode]
      8  * [shortcode attributes="example" /]
      9  * [shortcode]enclosed content[/shortcode]
     10  * </code>
     11  *
     12  * @package sapphire
     13  * @subpackage misc
     14  */
     15 class ShortcodeParser {
     16
     17     private static $instances = array();
     18
     19     private static $active_instance = 'default';
     20
     21     // -----------------------------------------------------------------------------------------------------------------
     22
     23     protected $shortcodes = array();
     24
     25     // -----------------------------------------------------------------------------------------------------------------
     26
     27     /**
     28      * Get the {@link ShortcodeParser} instance that is attached to a particular identifier.
     29      *
     30      * @param string $identifier Defaults to "default".
     31      * @return ShortcodeParser
     32      */
     33     public static function get($identifier = 'default') {
     34         if(!array_key_exists($identifier, self::$instances)) {
     35             self::$instances[$identifier] = new ShortcodeParser();
     36         }
     37
     38         return self::$instances[$identifier];
     39     }
     40
     41     /**
     42      * Get the currently active/default {@link ShortcodeParser} instance.
     43      *
     44      * @return ShortcodeParser
     45      */
     46     public static function get_active() {
     47         return self::get(self::$active_instance);
     48     }
     49
     50     /**
     51      * Set the identifier to use for the current active/default {@link ShortcodeParser} instance.
     52      *
     53      * @param string $identifier
     54      */
     55     public static function set_active($identifier) {
     56         self::$active_instance = (string) $identifier;
     57     }
     58
     59     // -----------------------------------------------------------------------------------------------------------------
     60
     61     /**
     62      * Register a shortcode, and attach it to a PHP callback.
     63      *
     64      * The callback for a shortcode will have the following arguments passed to it:
     65      *   - Any parameters attached to the shortcode as an associative array (keys are lower-case).
     66      *   - Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within
     67      *     this will not have been parsed, and can optionally be fed back into the parser.
     68      *   - The {@link ShortcodeParser} instance used to parse the content.
     69      *   - The shortcode tag name that was matched within the parsed content.
     70      *
     71      * @param string $shortcode The shortcode tag to map to the callback - normally in lowercase_underscore format.
     72      * @param callback $callback The callback to replace the shortcode with.
     73      */
     74     public function register($shortcode, $callback) {
     75         if(is_callable($callback)) $this->shortcodes[$shortcode] = $callback;
     76     }
     77
     78     /**
     79      * Check if a shortcode has been registered.
     80      *
     81      * @param string $shortcode
     82      * @return bool
     83      */
     84     public function registered($shortcode) {
     85         return array_key_exists($shortcode, $this->shortcodes);
     86     }
     87
     88     /**
     89      * Remove a specific registered shortcode.
     90      *
     91      * @param string $shortcode
     92      */
     93     public function unregister($shortcode) {
     94         if($this->registered($shortcode)) unset($this->shortcodes[$shortcode]);
     95     }
     96
     97     /**
     98      * Remove all registered shortcodes.
     99      */
    100     public function clear() {
    101         $this->shortcodes = array();
    102     }
    103
    104     // -----------------------------------------------------------------------------------------------------------------
    105
    106     /**
    107      * Parse a string, and replace any registered shortcodes within it with the result of the mapped callback.
    108      *
    109      * @param string $content
    110      * @return string
    111      */
    112     public function parse($content) {
    113         if(!$this->shortcodes) return $content;
    114
    115         $shortcodes = implode('|', array_map('preg_quote', array_keys($this->shortcodes)));
    116         $pattern    = "/(.?)\[($shortcodes)(.*?)(\/)?\](?(4)|(?:(.+?)\[\/\s*\\2\s*\]))?(.?)/s";
    117
    118         return preg_replace_callback($pattern, array($this, 'handleShortcode'), $content);
    119     }
    120
    121     /**
    122      * @ignore
    123      */
    124     protected function handleShortcode($matches) {
    125         $prefix    = $matches[1];
    126         $suffix    = $matches[6];
    127         $shortcode = $matches[2];
    128
    129         // allow for escaping shortcodes by enclosing them in double brackets ([[shortcode]])
    130         if($prefix == '[' && $suffix == ']') {
    131             return substr($matches[0], 1, -1);
    132         }
    133
    134         $attributes = array(); // Parse attributes into into this array.
    135
    136         if(preg_match_all('/(\w+) *= *(?:([\'"])(.*?)\\2|([^ "\'>]+))/', $matches[3], $match, PREG_SET_ORDER)) {
    137             foreach($match as $attribute) {
    138                 if(!empty($attribute[4])) {
    139                     $attributes[strtolower($attribute[1])] = $attribute[4];
    140                 } elseif(!empty($attribute[3])) {
    141                     $attributes[strtolower($attribute[1])] = $attribute[3];
    142                 }
    143             }
    144         }
    145
    146         return $prefix . call_user_func($this->shortcodes[$shortcode], $attributes, $matches[5], $this, $shortcode) . $suffix;
    147     }
    148
    149 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -