diff --git a/Plugin.php b/Plugin.php
new file mode 100644
index 0000000..a1f2d93
--- /dev/null
+++ b/Plugin.php
@@ -0,0 +1,90 @@
+ 'Shortcode',
+ 'description' => 'Shortcode integration for OctoberCMS.',
+ 'author' => 'Sensory 5',
+ 'icon' => 'icon-code'
+ ];
+ }
+
+ /**
+ * Register service provider, Twig extensions, and alias facade.
+ */
+ public function boot()
+ {
+ // Service provider
+ App::register('\Sensory5\Shortcode\Classes\ShortcodeServiceProvider');
+
+ // Register alias
+ $alias = AliasLoader::getInstance();
+ $alias->alias('Shortcode', '\Sensory5\Shortcode\Classes\ShortcodeFacade');
+
+ // Enable shortcodes on all pages if requested
+ if (Settings::get('enable_on_render', false))
+ {
+ Event::listen('cms.page.render', function($controller, $content) {
+
+ return \Shortcode::parse($content);
+
+ });
+ }
+
+ }
+
+ /**
+ * Register twig filters
+ */
+ public function registerMarkupTags()
+ {
+
+ return [
+ 'filters' => [ 'shortcode' => ['\Shortcode', 'parse'] ],
+ 'functions' => ['shortcode' => ['\Shortcode', 'parse'] ]
+ ];
+
+ }
+
+ /**
+ * Register backend settings
+ */
+ public function registerSettings()
+ {
+
+ return [
+ 'settings' => [
+ 'label' => 'Shortcode Settings',
+ 'description' => 'Manage shortcode settings',
+ 'category' => 'Shortcodes',
+ 'icon' => 'icon-code',
+ 'class' => 'Sensory5\Shortcode\Models\Settings',
+ 'order' => 600,
+ 'keywords' => 'shortcode shortcodes'
+ ]
+ ];
+
+ }
+
+}
diff --git a/README.md b/README.md
index 7011f9a..4373188 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,62 @@
-# oc-shortcode-plugin
+# Shortcodes for October CMS
+
Adds the ability to use shortcodes within October CMS
+
+### Add new shortcodes
+
+Add the shortcode in the `boot` function of a plugin.
+
+Then, use the provided facade to add new shortcodes in your own plugins:
+
+ \Shortcode::add('code', function(ShortcodeInterface $s) {
+ $text = "
Shortcode Name: {$s->getName()}
";
+ $text .= "Shortcode Parameter: {$s->getParameter('param', 'Not Found')}
";
+ $text .= "Shortcode Content: {$s->getContent()}
";
+ return $text;
+ });
+
+The above registration would enable the following shortcode to be used:
+
+ [code param="Parameter Value"] Inside Content [/code]
+
+### Use the shortcode within a page or blog post:
+
+The usual shortcode syntax is supported via the [Thunderer\Shortcode](https://github.com/thunderer/Shortcode) project.
+
+ [code]
+ [code argument="value"]
+ [code novalue argument=simple other="complex value"]
+ [code]content[/code]
+ [code argument="value"]content[/code]
+
+### Enable shortcodes on all pages
+
+To enable shortcodes on all page rendering, go to **Shortcode Settings** in the admin settings panel and check "Enable Shortcodes on all page rendering".
+
+### Using filters and functions
+
+If you don't want to enable shortcodes on all page rendering, you can use the built-in filter or function.
+
+To use the `shortcode` filter within a blog post or page, add the `shortcode` filter before any `raw` filtering.
+
+ {{ post.content_html|shortcode|raw }}
+
+To use the `shortcode` function within a layout and against the October CMS `page` tag, change the `page` tag from a token to a function:
+
+ {% page %}
+
+Becomes:
+
+ {{ shortcode(page()) }}
+
+ -- or --
+
+ {{ page()|shortcode }}
+
+### Strip Shortcodes
+
+You can strip shortcodes from text using the `Shortcode::strip` function. For example, when using with the `Sensory5\BlogExtension` plugin, you can strip shortcodes from the summary using the following code:
+
+ Event::listen('sensory5.blog.summary', function($content) {
+ return Str::limit(Html::strip(\Shortcode::strip($content)), 600);
+ });
diff --git a/classes/Shortcode.php b/classes/Shortcode.php
new file mode 100644
index 0000000..91dc955
--- /dev/null
+++ b/classes/Shortcode.php
@@ -0,0 +1,153 @@
+handlers = new HandlerContainer();
+ }
+
+ /**
+ * Get the names for all registered shortcodes.
+ *
+ * @return array
+ */
+ public function getNames()
+ {
+ return $this->handlers->getNames();
+ }
+
+ /**
+ * Add a new shortcode to the handler container.
+ *
+ * @param string $name
+ * @param mixed $callback
+ */
+ public function add($name, $callback)
+ {
+ $this->handlers->add($name, $callback);
+ }
+
+ /**
+ * Remove the specified shortcode name from the handler.
+ *
+ * @param string $name
+ */
+ public function remove($name)
+ {
+ if ($this->exists($name)) {
+ $this->handlers->remove($name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove all registered shortcodes
+ *
+ * @return self
+ */
+ public function destroyAll()
+ {
+ $this->handlers = new HandlerContainer();
+
+ return $this;
+ }
+
+ /**
+ * Strip any shortcodes from the content.
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+ public function strip($content)
+ {
+ $handlers = new HandlerContainer();
+ $handlers->setDefault(function(ShortcodeInterface $s) { return $s->getContent(); });
+ $processor = new Processor(new RegexParser(), $handlers);
+
+ return $processor->process($content);
+ }
+
+ /**
+ * Get count from all shortcodes.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->handlers->getNames());
+ }
+
+ /**
+ * Return true is the given name exist in shortcodes array.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function exists($name)
+ {
+ return $this->handlers->has($name);
+ }
+
+ /**
+ * Return true is the given content contains the named shortcode.
+ *
+ * @param string $content
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function contains($content, $name)
+ {
+ $hasShortcode = false;
+
+ $handlers = new HandlerContainer();
+ $handlers->setDefault(function(ShortcodeInterface $s) use($name, &$hasShortcode) {
+ if($s->getName() === $name) {
+ $hasShortcode = true;
+ }
+ });
+ $processor = new Processor(new RegexParser(), $handlers);
+ $processor->process($content);
+
+ return $hasShortcode;
+ }
+
+ /**
+ * Parse content and replace parts of it using registered handlers
+ *
+ * @param $content
+ *
+ * @return string
+ */
+ public function parse($content)
+ {
+ $processor = new Processor(new RegexParser(), $this->handlers);
+
+ return $processor->process($content);
+ }
+}
+
diff --git a/classes/ShortcodeFacade.php b/classes/ShortcodeFacade.php
new file mode 100644
index 0000000..2c44795
--- /dev/null
+++ b/classes/ShortcodeFacade.php
@@ -0,0 +1,17 @@
+app['shortcode'] = $this->app->share(function ($app) {
+ return new Shortcode();
+ });
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides()
+ {
+ return array('shortcode');
+ }
+
+}
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..01189e9
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "sensory5/oc-shortcode-plugin",
+ "description": "Adds the ability to use shortcodes within October CMS",
+ "type": "october-plugin",
+ "extra": {
+ "installer-name": "shortcode"
+ },
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Patrick Ward",
+ "email": "patrick@sensory5.com"
+ }
+ ],
+ "minimum-stability": "dev",
+ "require": {
+ "php": ">=5.4.0",
+ "composer/installers": "~1.0",
+ "thunderer/shortcode": "dev-master"
+ },
+ "autoload": {
+ "psr-4": {
+ "Sensory5\\Shortcode\\": ""
+ }
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..a7efe24
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,169 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "1af5d57fe99292fd0a02e59af2fe9fd5",
+ "packages": [
+ {
+ "name": "composer/installers",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/installers.git",
+ "reference": "e420b539e8d7b38b7c6f3f99dccc0386bd3dfe41"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/installers/zipball/e420b539e8d7b38b7c6f3f99dccc0386bd3dfe41",
+ "reference": "e420b539e8d7b38b7c6f3f99dccc0386bd3dfe41",
+ "shasum": ""
+ },
+ "replace": {
+ "roundcube/plugin-installer": "*",
+ "shama/baton": "*"
+ },
+ "require-dev": {
+ "composer/composer": "1.0.*@dev",
+ "phpunit/phpunit": "4.1.*"
+ },
+ "type": "composer-installer",
+ "extra": {
+ "class": "Composer\\Installers\\Installer",
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Composer\\Installers\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kyle Robinson Young",
+ "email": "kyle@dontkry.com",
+ "homepage": "https://github.com/shama"
+ }
+ ],
+ "description": "A multi-framework Composer library installer",
+ "homepage": "http://composer.github.com/installers/",
+ "keywords": [
+ "Craft",
+ "Dolibarr",
+ "Hurad",
+ "MODX Evo",
+ "OXID",
+ "SMF",
+ "Thelia",
+ "WolfCMS",
+ "agl",
+ "aimeos",
+ "annotatecms",
+ "bitrix",
+ "cakephp",
+ "chef",
+ "codeigniter",
+ "concrete5",
+ "croogo",
+ "dokuwiki",
+ "drupal",
+ "elgg",
+ "fuelphp",
+ "grav",
+ "installer",
+ "joomla",
+ "kohana",
+ "laravel",
+ "lithium",
+ "magento",
+ "mako",
+ "mediawiki",
+ "modulework",
+ "moodle",
+ "phpbb",
+ "piwik",
+ "ppi",
+ "puppet",
+ "roundcube",
+ "shopware",
+ "silverstripe",
+ "symfony",
+ "typo3",
+ "wordpress",
+ "zend",
+ "zikula"
+ ],
+ "time": "2015-06-13 15:30:38"
+ },
+ {
+ "name": "thunderer/shortcode",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thunderer/Shortcode.git",
+ "reference": "8a57fc61058e616279e758d0437fb5c525fdf837"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thunderer/Shortcode/zipball/8a57fc61058e616279e758d0437fb5c525fdf837",
+ "reference": "8a57fc61058e616279e758d0437fb5c525fdf837",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.1"
+ },
+ "suggest": {
+ "ext-dom": "if you want to use XML serializer",
+ "ext-json": "if you want to use JSON serializer",
+ "symfony/yaml": "if you want to use YAML serializer"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Thunder\\Shortcode\\": "src/",
+ "Thunder\\Shortcode\\Tests\\": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tomasz Kowalczyk",
+ "email": "tomasz@kowalczyk.cc"
+ }
+ ],
+ "description": "Advanced shortcode (BBCode) parser and engine for PHP",
+ "keywords": [
+ "bbcode",
+ "engine",
+ "library",
+ "parser",
+ "shortcode"
+ ],
+ "time": "2015-10-19 19:17:59"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": {
+ "thunderer/shortcode": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.4.0"
+ },
+ "platform-dev": []
+}
diff --git a/models/Settings.php b/models/Settings.php
new file mode 100644
index 0000000..416543f
--- /dev/null
+++ b/models/Settings.php
@@ -0,0 +1,17 @@
+assertSame($expect, $this->getShortcode()->parse($text));
+ }
+
+ public function provideTexts()
+ {
+ return [
+ ['[name]', 'name'],
+ ['[content]', ''],
+ ['[content]thunder[/content]', 'thunder'],
+ ['[content][name][/content]', 'name'],
+ ['[nc][name][/nc]', 'nc: name'],
+ ];
+ }
+
+ public function testCount()
+ {
+ $this->assertSame(3, $this->getShortcode()->count());
+ }
+
+ public function testAll()
+ {
+ $this->assertSame(['name', 'content', 'nc'], $this->getShortcode()->all());
+ }
+
+ public function testUnregister()
+ {
+ $this->assertSame('[name]', $this->getShortcode()->unregister('name')->parse('[name]'));
+ }
+
+ public function testDestroy()
+ {
+ $this->assertSame('[name]', $this->getShortcode()->destroy()->parse('[name]'));
+ }
+
+ public function testStrip()
+ {
+ $this->assertSame('', $this->getShortcode()->strip('[name]'));
+ $this->assertSame('x y', $this->getShortcode()->strip('x [name]y'));
+ $this->assertSame('x a a y', $this->getShortcode()->strip('x [name] a [content /] a [/name] y'));
+ }
+
+ public function testExists()
+ {
+ $shortcode = $this->getShortcode();
+
+ $this->assertTrue($shortcode->exists('name'));
+ $this->assertTrue($shortcode->exists('content'));
+ $this->assertTrue($shortcode->exists('nc'));
+ $this->assertFalse($shortcode->exists('invalid'));
+ }
+
+ public function testContains()
+ {
+ $shortcode = $this->getShortcode();
+
+ $this->assertTrue($shortcode->contains('[name]', 'name'));
+ $this->assertFalse($shortcode->contains('[x]', 'name'));
+ }
+
+ private function getShortcode()
+ {
+ $shortcode = new Shortcode();
+
+ $shortcode->register('name', function(ShortcodeInterface $s) {
+ return $s->getName();
+ });
+ $shortcode->register('content', function(ShortcodeInterface $s) {
+ return $s->getContent();
+ });
+ $shortcode->register('nc', function(ShortcodeInterface $s) {
+ return $s->getName().': '.$s->getContent();
+ });
+
+ return $shortcode;
+ }
+}