diff --git a/flextype/Cache.php b/flextype/Cache.php
new file mode 100755
index 00000000..d07f858f
--- /dev/null
+++ b/flextype/Cache.php
@@ -0,0 +1,227 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Cache
+{
+ /**
+ * An instance of the Cache class
+ *
+ * @var object
+ */
+ protected static $instance = null;
+ /**
+ * Unique cache key
+ *
+ * @var string Cache key.
+ */
+ protected static $key;
+ /**
+ * Lifetime
+ *
+ * @var int Lifetime.
+ */
+ protected static $lifetime;
+ /**
+ * Current time
+ *
+ * @var int Current time.
+ */
+ protected static $now;
+ /**
+ * Cache Driver
+ *
+ * @var DoctrineCache
+ */
+ protected static $driver;
+ /**
+ * Protected clone method to enforce singleton behavior.
+ *
+ * @access protected
+ */
+ protected function __clone()
+ {
+ // Nothing here.
+ }
+ /**
+ * Constructor.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ // Set current time
+ static::$now = time();
+ // Cache key allows us to invalidate all cache on configuration changes.
+ static::$key = (Config::get('site.cache.prefix') ? Config::get('site.cache.prefix') : 'fansoro') . '-' . md5(ROOT_DIR . 'Fansoro::VERSION');
+ // Get Cache Driver
+ static::$driver = static::getCacheDriver();
+ // Set the cache namespace to our unique key
+ static::$driver->setNamespace(static::$key);
+ }
+ /**
+ * Get Cache Driver
+ *
+ * @access public
+ * @return object
+ */
+ public static function getCacheDriver()
+ {
+ $driver_name = Config::get('site.cache.driver');
+ if (!$driver_name || $driver_name == 'auto') {
+ if (extension_loaded('apc')) {
+ $driver_name = 'apc';
+ } elseif (extension_loaded('wincache')) {
+ $driver_name = 'wincache';
+ } elseif (extension_loaded('xcache')) {
+ $driver_name = 'xcache';
+ }
+ } else {
+ $driver_name = 'file';
+ }
+ switch ($driver_name) {
+ case 'apc':
+ $driver = new \Doctrine\Common\Cache\ApcCache();
+ break;
+ case 'wincache':
+ $driver = new \Doctrine\Common\Cache\WinCacheCache();
+ break;
+ case 'xcache':
+ $driver = new \Doctrine\Common\Cache\XcacheCache();
+ break;
+ case 'memcache':
+ $memcache = new \Memcache();
+ $memcache->connect(Config::get('site.cache.memcache.server', 'localhost'),
+ Config::get('site.cache.memcache.port', 11211));
+ $driver = new \Doctrine\Common\Cache\MemcacheCache();
+ $driver->setMemcache($memcache);
+ break;
+ case 'redis':
+ $redis = new \Redis();
+ $redis->connect(Config::get('site.cache.redis.server', 'localhost'),
+ Config::get('site.cache.redis.port', 6379));
+ $driver = new \Doctrine\Common\Cache\RedisCache();
+ $driver->setRedis($redis);
+ break;
+ default:
+ // Create doctrine cache directory if its not exists
+ !Flextype::$filesystem->exists($cache_directory = CACHE_PATH . '/doctrine/') and Flextype::$filesystem->mkdir($cache_directory);
+ $driver = new \Doctrine\Common\Cache\FilesystemCache($cache_directory);
+ break;
+ }
+ return $driver;
+ }
+
+ /**
+ * Returns driver variable
+ *
+ * @access public
+ * @return object
+ */
+ public static function driver()
+ {
+ return static::$driver;
+ }
+ /**
+ * Get cache key.
+ *
+ * @access public
+ * @return string
+ */
+ public static function getKey()
+ {
+ return static::$key;
+ }
+ /**
+ * Fetches an entry from the cache.
+ *
+ * @access public
+ * @param string $id The id of the cache entry to fetch.
+ * @return mixed The cached data or FALSE, if no cache entry exists for the given id.
+ */
+ public function fetch($id)
+ {
+ if (Config::get('site.cache.enabled')) {
+ return static::$driver->fetch($id);
+ } else {
+ return false;
+ }
+ }
+ /**
+ * Puts data into the cache.
+ *
+ * @access public
+ * @param string $id The cache id.
+ * @param mixed $data The cache entry/data.
+ * @param int $lifeTime The lifetime in number of seconds for this cache entry.
+ * If zero (the default), the entry never expires (although it may be deleted from the cache
+ * to make place for other entries).
+ */
+ public function save($id, $data, $lifetime = null)
+ {
+ if (Config::get('site.cache.enabled')) {
+ if ($lifetime === null) {
+ $lifetime = static::getLifetime();
+ }
+ static::$driver->save($id, $data, $lifetime);
+ }
+ }
+ /**
+ * Clear Cache
+ */
+ public static function clear()
+ {
+ Flextype::$filesystem->remove(CACHE_PATH . '/doctrine/');
+ }
+ /**
+ * Set the cache lifetime.
+ *
+ * @access public
+ * @param int $future timestamp
+ */
+ public static function setLifetime($future)
+ {
+ if (!$future) {
+ return;
+ }
+ $interval = $future - $this->now;
+ if ($interval > 0 && $interval < static::getLifetime()) {
+ static::$lifetime = $interval;
+ }
+ }
+ /**
+ * Retrieve the cache lifetime (in seconds)
+ *
+ * @access public
+ * @return mixed
+ */
+ public static function getLifetime()
+ {
+ if (static::$lifetime === null) {
+ static::$lifetime = Config::get('site.cache.lifetime') ?: 604800;
+ }
+ return static::$lifetime;
+ }
+ /**
+ * Initialize Fansoro Cache
+ *
+ *
+ * Cache::init();
+ *
+ *
+ * @access public
+ * @return object
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new Cache();
+ }
+}
diff --git a/flextype/Config.php b/flextype/Config.php
new file mode 100755
index 00000000..ae493e28
--- /dev/null
+++ b/flextype/Config.php
@@ -0,0 +1,112 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Config
+{
+
+ /**
+ * An instance of the Config class
+ *
+ * @var object
+ * @access protected
+ */
+ protected static $instance = null;
+
+ /**
+ * Config
+ *
+ * @var array
+ * @access protected
+ */
+ protected static $config = [];
+
+ /**
+ * Protected clone method to enforce singleton behavior.
+ *
+ * @access protected
+ */
+ protected function __clone()
+ {
+ // Nothing here.
+ }
+
+ /**
+ * Constructor.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ if (Flextype::$filesystem->exists($site_config = CONFIG_PATH . '/' . 'site.yml')) {
+ static::$config['site'] = Yaml::parse(file_get_contents($site_config));
+ } else {
+ throw new RuntimeException("Flextype site config file does not exist.");
+ }
+ }
+
+ /**
+ * Set new or update existing config variable
+ *
+ * @access public
+ * @param string $key Key
+ * @param mixed $value Value
+ */
+ public static function set($key, $value)
+ {
+ Arr::set(static::$config, $key, $value);
+ }
+
+ /**
+ * Get config variable
+ *
+ * @access public
+ * @param string $key Key
+ * @param mixed $default Default value
+ * @return mixed
+ */
+ public static function get($key, $default = null)
+ {
+ return Arr::get(static::$config, $key, $default);
+ }
+
+ /**
+ * Get config array
+ *
+ *
+ * $config = Config::getConfig();
+ *
+ *
+ * @access public
+ * @return array
+ */
+ public static function getConfig()
+ {
+ return static::$config;
+ }
+
+ /**
+ * Initialize Flextype Config
+ *
+ *
+ * Config::init();
+ *
+ *
+ * @access public
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new Config();
+ }
+}
diff --git a/flextype/Events.php b/flextype/Events.php
new file mode 100644
index 00000000..b2949c1d
--- /dev/null
+++ b/flextype/Events.php
@@ -0,0 +1,101 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Events
+{
+
+ /**
+ * Events
+ *
+ * @var array
+ * @access protected
+ */
+ protected static $events = [];
+
+ /**
+ * Protected constructor since this is a static class.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ // Nothing here
+ }
+
+ /**
+ * Hooks a function on to a specific event.
+ *
+ * @access public
+ * @param string $event_name Event name
+ * @param mixed $added_function Added function
+ * @param integer $priority Priority. Default is 10
+ * @param array $args Arguments
+ */
+ public static function addListener(string $event_name, $added_function, int $priority = 10, array $args = null)
+ {
+ // Hooks a function on to a specific event.
+ static::$events[] = array(
+ 'event_name' => $event_name,
+ 'function' => $added_function,
+ 'priority' => $priority,
+ 'args' => $args
+ );
+ }
+
+ /**
+ * Run functions hooked on a specific event.
+ *
+ * @access public
+ * @param string $event_name Event name
+ * @param array $args Arguments
+ * @param boolean $return Return data or not. Default is false
+ * @return mixed
+ */
+ public static function dispatch(string $event_name, array $args = [], bool $return = false)
+ {
+ // Redefine arguments
+ $event_name = $event_name;
+ $return = $return;
+
+ // Run event
+ if (count(static::$events) > 0) {
+
+ // Sort actions by priority
+ $events = Arr::subvalSort(static::$events, 'priority');
+
+ // Loop through $events array
+ foreach ($events as $action) {
+
+ // Execute specific action
+ if ($action['event_name'] == $event_name) {
+ // isset arguments ?
+ if (isset($args)) {
+ // Return or Render specific action results ?
+ if ($return) {
+ return call_user_func_array($action['function'], $args);
+ } else {
+ call_user_func_array($action['function'], $args);
+ }
+ } else {
+ if ($return) {
+ return call_user_func_array($action['function'], $action['args']);
+ } else {
+ call_user_func_array($action['function'], $action['args']);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/flextype/Filters.php b/flextype/Filters.php
new file mode 100755
index 00000000..2f78c37d
--- /dev/null
+++ b/flextype/Filters.php
@@ -0,0 +1,125 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Filters
+{
+
+ /**
+ * @var Flextype
+ */
+ protected $flextype;
+
+ /**
+ * Filters
+ *
+ * @var array
+ * @access protected
+ */
+ protected static $filters = [];
+
+
+ /**
+ * Protected constructor since this is a static class.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ // Nothing here
+ }
+
+ /**
+ * Dispatch filters
+ *
+ *
+ * Filter::dispatch('content', $content);
+ *
+ *
+ * @access public
+ * @param string $filter_name The name of the filter hook.
+ * @param mixed $value The value on which the filters hooked.
+ * @return mixed
+ */
+ public static function dispatch(string $filter_name, $value)
+ {
+ $args = array_slice(func_get_args(), 2);
+
+ if (! isset(static::$filters[$filter_name])) {
+ return $value;
+ }
+
+ foreach (static::$filters[$filter_name] as $priority => $functions) {
+ if (! is_null($functions)) {
+ foreach ($functions as $function) {
+ $all_args = array_merge(array($value), $args);
+ $function_name = $function['function'];
+ $accepted_args = $function['accepted_args'];
+ if ($accepted_args == 1) {
+ $the_args = array($value);
+ } elseif ($accepted_args > 1) {
+ $the_args = array_slice($all_args, 0, $accepted_args);
+ } elseif ($accepted_args == 0) {
+ $the_args = null;
+ } else {
+ $the_args = $all_args;
+ }
+ $value = call_user_func_array($function_name, $the_args);
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Add filter
+ *
+ *
+ * Filter::add('content', 'replacer');
+ *
+ * function replacer($content) {
+ * return preg_replace(array('/\[b\](.*?)\[\/b\]/ms'), array('\1'), $content);
+ * }
+ *
+ *
+ * @access public
+ * @param string $filter_name The name of the filter to hook the $function_to_add to.
+ * @param string $function_to_add The name of the function to be called when the filter is applied.
+ * @param integer $priority Function to add priority - default is 10.
+ * @param integer $accepted_args The number of arguments the function accept default is 1.
+ * @return boolean
+ */
+ public static function addListener($filter_name, $function_to_add, $priority = 10, $accepted_args = 1)
+ {
+ // Redefine arguments
+ $filter_name = (string) $filter_name;
+ $function_to_add = $function_to_add;
+ $priority = (int) $priority;
+ $accepted_args = (int) $accepted_args;
+
+ // Check that we don't already have the same filter at the same priority. Thanks to WP :)
+ if (isset(static::$filters[$filter_name]["$priority"])) {
+ foreach (static::$filters[$filter_name]["$priority"] as $filter) {
+ if ($filter['function'] == $function_to_add) {
+ return true;
+ }
+ }
+ }
+
+ static::$filters[$filter_name]["$priority"][] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
+
+ // Sort
+ ksort(static::$filters[$filter_name]["$priority"]);
+
+ return true;
+ }
+}
diff --git a/flextype/Flextype.php b/flextype/Flextype.php
new file mode 100755
index 00000000..36f26a2c
--- /dev/null
+++ b/flextype/Flextype.php
@@ -0,0 +1,136 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Flextype
+{
+ /**
+ * An instance of the Flextype class
+ *
+ * @var object
+ * @access protected
+ */
+ protected static $instance = null;
+
+ /**
+ * Filesystem
+ *
+ * @var object
+ * @access public
+ */
+ public static $filesystem = null;
+
+ /**
+ * Finder
+ *
+ * @var object
+ * @access public
+ */
+ public static $finder = null;
+
+ /**
+ * Protected clone method to enforce singleton behavior.
+ *
+ * @access protected
+ */
+ protected function __clone()
+ {
+ // Nothing here.
+ }
+
+ /**
+ * The version of Flextype
+ *
+ * @var string
+ */
+ const VERSION = '0.0.0';
+
+ /**
+ * Constructor.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+
+ static::$finder = new Finder();
+ static::$filesystem = new Filesystem();
+
+ // Init Config
+ Config::init();
+
+ // Turn on output buffering
+ ob_start();
+
+ // Display Errors
+ if (Config::get('site.errors.display')) {
+ define('DEVELOPMENT', true);
+ error_reporting(-1);
+ } else {
+ define('DEVELOPMENT', false);
+ error_reporting(0);
+ }
+
+ // Set internal encoding
+ function_exists('mb_language') and mb_language('uni');
+ function_exists('mb_regex_encoding') and mb_regex_encoding(Config::get('site.charset'));
+ function_exists('mb_internal_encoding') and mb_internal_encoding(Config::get('site.charset'));
+
+ // Set Error handler
+ set_error_handler('ErrorHandler::error');
+ register_shutdown_function('ErrorHandler::fatal');
+ set_exception_handler('ErrorHandler::exception');
+
+ // Set default timezone
+ date_default_timezone_set(Config::get('site.timezone'));
+
+ // Start the session
+ Session::start();
+
+ // Init Cache
+ Cache::init();
+
+ // Init I18n
+ I18n::init();
+
+ // Init Themes
+ Themes::init();
+
+ // Init Plugins
+ Plugins::init();
+
+ // Render current page
+ Pages::init();
+
+ // Flush (send) the output buffer and turn off output buffering
+ ob_end_flush();
+ }
+
+ /**
+ * Initialize Flextype Application
+ *
+ *
+ * Rawium::init();
+ *
+ *
+ * @access public
+ * @return object
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new Flextype();
+ }
+}
diff --git a/flextype/I18n.php b/flextype/I18n.php
new file mode 100644
index 00000000..e1fc26a6
--- /dev/null
+++ b/flextype/I18n.php
@@ -0,0 +1,145 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class I18n
+{
+ /**
+ * An instance of the Cache class
+ *
+ * @var object
+ */
+ protected static $instance = null;
+
+ /**
+ * Locales array
+ *
+ * @var array
+ */
+ public static $locales = [
+ 'ar' => 'العربية',
+ 'bg' => 'Български',
+ 'ca' => 'Català',
+ 'cs' => 'Česky',
+ 'da' => 'Dansk',
+ 'de' => 'Deutsch',
+ 'el' => 'Ελληνικά',
+ 'en' => 'English',
+ 'es' => 'Español',
+ 'fa' => 'Farsi',
+ 'fi' => 'Suomi',
+ 'fr' => 'Français',
+ 'gl' => 'Galego',
+ 'ka-ge' => 'Georgian',
+ 'hu' => 'Magyar',
+ 'it' => 'Italiano',
+ 'id' => 'Bahasa Indonesia',
+ 'ja' => '日本語',
+ 'lt' => 'Lietuvių',
+ 'nl' => 'Nederlands',
+ 'no' => 'Norsk',
+ 'pl' => 'Polski',
+ 'pt' => 'Português',
+ 'pt-br' => 'Português do Brasil',
+ 'ru' => 'Русский',
+ 'sk' => 'Slovenčina',
+ 'sl' => 'Slovenščina',
+ 'sv' => 'Svenska',
+ 'sr' => 'Srpski',
+ 'tr' => 'Türkçe',
+ 'uk' => 'Українська',
+ 'zh-cn' => '简体中文',
+ ];
+
+ /**
+ * Dictionary
+ *
+ * @var array
+ */
+ public static $dictionary = [];
+
+ /**
+ * Protected clone method to enforce singleton behavior.
+ *
+ * @access protected
+ */
+ protected function __clone()
+ {
+ // Nothing here.
+ }
+
+ /**
+ * Construct
+ */
+ protected function __construct()
+ {
+
+ // Get Plugins and Site Locales list
+ (array) $plugins_list = Config::get('site.plugins');
+ (array) $locales = Config::get('site.locales');
+ (array) $dictionary = [];
+
+ // Create dictionary
+ if (is_array($plugins_list) && count($plugins_list) > 0) {
+ foreach ($locales as $locale) {
+ foreach ($plugins_list as $plugin) {
+ $language_file = PLUGINS_PATH . '/' . $plugin . '/languages/' . $locale . '.yml';
+ if (file_exists($language_file)) {
+ $dictionary[$plugin][$locale] = Yaml::parse(file_get_contents($language_file));
+ }
+ }
+ }
+ }
+
+ // Save dictionary
+ static::$dictionary = $dictionary;
+ }
+
+ /**
+ * Returns translation of a string. If no translation exists, the original
+ * string will be returned. No parameters are replaced.
+ *
+ * @param string $string Text to translate
+ * @param string $namespace Namespace
+ * @param string $locale Locale
+ * @return string
+ */
+ public static function find(string $string, string $namespace, string $locale, array $values = []) : string
+ {
+ // Search current string to translate in the Dictionary
+ if (isset(static::$dictionary[$namespace][$locale][$string])) {
+ $string = static::$dictionary[$namespace][$locale][$string];
+ $string = empty($values) ? $string : strtr($string, $values);
+ } else {
+ $string = $string;
+ }
+
+ // Return translation of a string
+ return $string;
+ }
+
+ /**
+ * Initialize Flextype I18n
+ *
+ *
+ * I18n::init();
+ *
+ *
+ * @access public
+ * @return object
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new I18n();
+ }
+}
diff --git a/flextype/Markdown.php b/flextype/Markdown.php
new file mode 100644
index 00000000..4b7165ea
--- /dev/null
+++ b/flextype/Markdown.php
@@ -0,0 +1,42 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Markdown
+{
+ /**
+ * Parsedown Extra Object
+ *
+ * @var object
+ * @access protected
+ */
+ protected static $markdown;
+
+ /**
+ * Markdown parser
+ *
+ *
+ * $content = Markdown::parse($content);
+ *
+ *
+ * @access public
+ * @param string $content Content to parse
+ * @return string Formatted content
+ */
+ public static function parse(string $content) : string
+ {
+ !static::$markdown and static::$markdown = new ParsedownExtra();
+
+ return static::$markdown->text($content);
+ }
+}
diff --git a/flextype/Pages.php b/flextype/Pages.php
new file mode 100755
index 00000000..f215776a
--- /dev/null
+++ b/flextype/Pages.php
@@ -0,0 +1,207 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Pages
+{
+ /**
+ * An instance of the Cache class
+ *
+ * @var object
+ */
+ protected static $instance = null;
+
+ /**
+ * Page
+ *
+ * @var Page
+ */
+ public static $page;
+
+ /**
+ * Constructor
+ *
+ * @param Flextype $flextype
+ */
+ protected function __construct()
+ {
+ // The page is not processed and not sent to the display.
+ Events::dispatch('onPageBeforeRender');
+
+ // Get current page
+ static::$page = static::getPage(Url::getUriString());
+
+ // Display page for current requested url
+ static::renderPage(static::$page);
+
+ // The page has been fully processed and sent to the display.
+ Events::dispatch('onPageAfterRender');
+ }
+
+ /**
+ * Page finder
+ */
+ public static function finder($url = '', $url_abs = false)
+ {
+
+ // If url is empty that its a homepage
+ if ($url_abs) {
+ if ($url) {
+ $file = $url;
+ } else {
+ $file = PAGES_PATH . '/' . Config::get('site.pages.main') . '/' . 'index.md';
+ }
+ } else {
+ if ($url) {
+ $file = PAGES_PATH . '/' . $url . '/index.md';
+ } else {
+ $file = PAGES_PATH . '/' . Config::get('site.pages.main') . '/' . 'index.md';
+ }
+ }
+
+ // Get 404 page if file not exists
+ if (Flextype::$filesystem->exists($file)) {
+ $file = $file;
+ } else {
+ $file = PAGES_PATH . '/404/index.md';
+ Response::status(404);
+ }
+
+ return $file;
+ }
+
+ /**
+ * Render page
+ */
+ public static function renderPage($page)
+ {
+ $template_ext = '.php';
+ $template_name = empty($page['template']) ? 'index' : $page['template'];
+ $site_theme = Config::get('site.theme');
+ $template_path = THEMES_PATH . '/' . $site_theme . '/' . $template_name . $template_ext;
+
+ if (Flextype::$filesystem->exists($template_path)) {
+ include $template_path;
+ } else {
+ throw new RuntimeException("Template {$template_name} does not exist.");
+ }
+ }
+
+ /**
+ * Page parser
+ */
+ public static function parse($file)
+ {
+ $page = trim(file_get_contents($file));
+ $page = explode('---', $page, 3);
+
+ $frontmatter = Shortcodes::parse($page[1]);
+ $result_page = Yaml::parse($frontmatter);
+
+ // Get page url
+ $url = str_replace(PAGES_PATH, Url::getBase(), $file);
+ $url = str_replace('index.md', '', $url);
+ $url = str_replace('.md', '', $url);
+ $url = str_replace('\\', '/', $url);
+ $url = rtrim($url, '/');
+ $result_page['url'] = $url;
+
+ // Get page slug
+ $url = str_replace(Url::getBase(), '', $url);
+ $url = ltrim($url, '/');
+ $url = rtrim($url, '/');
+ $result_page['slug'] = str_replace(Url::getBase(), '', $url);
+
+ $result_page['content'] = $page[2];
+
+ return $result_page;
+ }
+
+
+ /**
+ * Get page
+ */
+ public static function getPage(string $url = '', bool $raw = false, bool $url_abs = false)
+ {
+ $file = static::finder($url, $url_abs);
+
+ if ($raw) {
+ $page = trim(file_get_contents($file));
+ static::$page = $page;
+ Events::dispatch('onPageContentRawAfter');
+ } else {
+ $page = static::parse($file);
+ static::$page = $page;
+ static::$page['content'] = Filters::dispatch('content', static::parseContent(static::$page['content']));
+ Events::dispatch('onPageContentAfter');
+ }
+
+ return static::$page;
+ }
+
+ public static function parseContent(string $content) : string
+ {
+ $content = Shortcodes::parse($content);
+ $content = Markdown::parse($content);
+
+ return $content;
+ }
+
+ /**
+ * getPage
+ */
+ public static function getPages($url = '', $raw = false, $order_by = 'title', $order_type = 'DESC', $limit = null)
+ {
+ // Get pages list for current $url
+ $pages_list = Flextype::$finder->files()->name('*.md')->in(PAGES_PATH . '/' . $url);
+
+ // Go trough pages list
+ foreach ($pages_list as $key => $page) {
+ if (strpos($page->getPathname(), $url.'/index.md') !== false) {
+
+ } else {
+ $pages[$key] = static::getPage($page->getPathname(), $raw, true);
+ }
+ }
+
+ // Sort and Slice pages if !$raw
+ if (!$raw) {
+ $pages = Arr::subvalSort($pages, $order_by, $order_type);
+
+ if ($limit != null) {
+ $pages = array_slice($_pages, null, $limit);
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Initialize Flextype Pages
+ *
+ *
+ * Pages::init();
+ *
+ *
+ * @access public
+ * @return object
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new Pages();
+ }
+}
diff --git a/flextype/Plugins.php b/flextype/Plugins.php
new file mode 100755
index 00000000..e5b9f0c2
--- /dev/null
+++ b/flextype/Plugins.php
@@ -0,0 +1,104 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Plugins
+{
+ /**
+ * An instance of the Cache class
+ *
+ * @var object
+ */
+ protected static $instance = null;
+
+ /**
+ * Init Plugins
+ *
+ * @access public
+ * @return mixed
+ */
+ protected function __construct()
+ {
+ // Plugin manifest
+ $plugin_manifest = [];
+
+ // Plugin cache id
+ $plugins_cache_id = '';
+
+ // Get Plugins List
+ $plugins_list = Config::get('site.plugins');
+
+ // If Plugins List isnt empty then create plugin cache ID
+ if (is_array($plugins_list) && count($plugins_list) > 0) {
+
+ // Go through...
+ foreach ($plugins_list as $plugin) {
+ if (Flextype::$filesystem->exists($_plugin = PLUGINS_PATH . '/' . $plugin . '/' . $plugin . '.yml')) {
+ $plugins_cache_id .= filemtime($_plugin);
+ }
+ }
+
+ // Create Unique Cache ID for Plugins
+ $plugins_cache_id = md5('plugins' . PLUGINS_PATH . $plugins_cache_id);
+ }
+
+ // Get plugins list from cache or scan plugins folder and create new plugins cache item
+ if (Cache::driver()->contains($plugins_cache_id)) {
+ Config::set('plugins', Cache::driver()->fetch($plugins_cache_id));
+ } else {
+
+ // If Plugins List isnt empty
+ if (is_array($plugins_list) && count($plugins_list) > 0) {
+
+ // Go through...
+ foreach ($plugins_list as $plugin) {
+
+ if (Flextype::$filesystem->exists($_plugin_manifest = PLUGINS_PATH . '/' . $plugin . '/' . $plugin . '.yml')) {
+ $plugin_manifest = Yaml::parseFile($_plugin_manifest);
+ }
+
+ $_plugins_config[basename($_plugin_manifest, '.yml')] = $plugin_manifest;
+ }
+
+ Config::set('plugins', $_plugins_config);
+ Cache::driver()->save($plugins_cache_id, $_plugins_config);
+ }
+ }
+
+ // Include enabled plugins
+ if (is_array(Config::get('plugins')) && count(Config::get('plugins')) > 0) {
+ foreach (Config::get('plugins') as $plugin_name => $plugin) {
+ if (Config::get('plugins.'.$plugin_name.'.enabled')) {
+ include_once PLUGINS_PATH .'/'. $plugin_name .'/'. $plugin_name . '.php';
+ }
+ }
+ }
+
+ Events::dispatch('onPluginsInitialized');
+ }
+
+ /**
+ * Initialize Flextype Plugins
+ *
+ *
+ * Plugins::init();
+ *
+ *
+ * @access public
+ * @return object
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new Plugins();
+ }
+}
diff --git a/flextype/Shortcodes.php b/flextype/Shortcodes.php
new file mode 100644
index 00000000..746f3269
--- /dev/null
+++ b/flextype/Shortcodes.php
@@ -0,0 +1,131 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Shortcodes
+{
+
+ /**
+ * Shortcode tags array
+ *
+ * @var shortcode_tags
+ */
+ protected static $shortcode_tags = [];
+
+ /**
+ * Protected constructor since this is a static class.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ // Nothing here
+ }
+
+ /**
+ * Add new shortcode
+ *
+ * @param string $shortcode Shortcode tag to be searched in content.
+ * @param string $callback_function The callback function to replace the shortcode with.
+ */
+ public static function add(string $shortcode, $callback_function)
+ {
+ // Add new shortcode
+ if (is_callable($callback_function)) {
+ static::$shortcode_tags[$shortcode] = $callback_function;
+ }
+ }
+
+ /**
+ * Remove a specific registered shortcode.
+ *
+ * @param string $shortcode Shortcode tag.
+ */
+ public static function delete(string $shortcode)
+ {
+ // Delete shortcode
+ if (static::exists($shortcode)) {
+ unset(static::$shortcode_tags[$shortcode]);
+ }
+ }
+
+ /**
+ * Remove all registered shortcodes.
+ *
+ *
+ * Shortcode::clear();
+ *
+ *
+ */
+ public static function clear()
+ {
+ static::$shortcode_tags = array();
+ }
+
+ /**
+ * Check if a shortcode has been registered.
+ *
+ * @param string $shortcode Shortcode tag.
+ */
+ public static function exists(string $shortcode)
+ {
+ // Check shortcode
+ return array_key_exists($shortcode, static::$shortcode_tags);
+ }
+
+ /**
+ * Parse a string, and replace any registered shortcodes within it with the result of the mapped callback.
+ *
+ * @param string $content Content
+ * @return string
+ */
+ public static function parse(string $content)
+ {
+ if (! static::$shortcode_tags) {
+ return $content;
+ }
+
+ $shortcodes = implode('|', array_map('preg_quote', array_keys(static::$shortcode_tags)));
+ $pattern = "/(.?)\{([$shortcodes]+)(.*?)(\/)?\}(?(4)|(?:(.+?)\{\/\s*\\2\s*\}))?(.?)/s";
+
+ return preg_replace_callback($pattern, 'static::_handle', $content);
+ }
+
+ /**
+ * _handle()
+ */
+ protected static function _handle($matches)
+ {
+ $prefix = $matches[1];
+ $suffix = $matches[6];
+ $shortcode = $matches[2];
+
+ // Allow for escaping shortcodes by enclosing them in {{shortcode}}
+ if ($prefix == '{' && $suffix == '}') {
+ return substr($matches[0], 1, -1);
+ }
+
+ $attributes = array(); // Parse attributes into into this array.
+
+ if (preg_match_all('/(\w+) *= *(?:([\'"])(.*?)\\2|([^ "\'>]+))/', $matches[3], $match, PREG_SET_ORDER)) {
+ foreach ($match as $attribute) {
+ if (! empty($attribute[4])) {
+ $attributes[strtolower($attribute[1])] = $attribute[4];
+ } elseif (! empty($attribute[3])) {
+ $attributes[strtolower($attribute[1])] = $attribute[3];
+ }
+ }
+ }
+
+ // Check if this shortcode realy exists then call user function else return empty string
+ return (isset(static::$shortcode_tags[$shortcode])) ? $prefix . call_user_func(static::$shortcode_tags[$shortcode], $attributes, $matches[5], $shortcode) . $suffix : '';
+ }
+}
diff --git a/flextype/Templates.php b/flextype/Templates.php
new file mode 100644
index 00000000..4ddfbc68
--- /dev/null
+++ b/flextype/Templates.php
@@ -0,0 +1,47 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Templates
+{
+
+ /**
+ * Protected constructor since this is a static class.
+ *
+ * @access protected
+ */
+ protected function __construct()
+ {
+ // Nothing here
+ }
+
+ /**
+ * Get Themes template
+ *
+ * @access public
+ * @param string $template_name Template name
+ * @return mixed
+ */
+ public static function display(string $template_name)
+ {
+ $template_ext = '.php';
+
+ $page = Pages::$page;
+
+ $template_path = THEMES_PATH . '/' . Config::get('site.theme') . '/' . $template_name . $template_ext;
+
+ if (Flextype::$filesystem->exists($template_path)) {
+ include $template_path;
+ } else {
+ throw new RuntimeException("Template {$template_name} does not exist.");
+ }
+ }
+}
diff --git a/flextype/Themes.php b/flextype/Themes.php
new file mode 100644
index 00000000..7b01da29
--- /dev/null
+++ b/flextype/Themes.php
@@ -0,0 +1,58 @@
+
+ * @link http://flextype.org
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Themes
+{
+ /**
+ * An instance of the Themes class
+ *
+ * @var object
+ */
+ protected static $instance = null;
+
+ /**
+ * Init Themes
+ *
+ * @access public
+ * @return mixed
+ */
+ protected function __construct()
+ {
+ // Theme Manifest
+ $theme_manifest = [];
+
+ // Get current theme
+ $theme = Config::get('site.theme');
+
+ if (Flextype::$filesystem->exists($theme_manifest_file = THEMES_PATH . '/' . $theme . '/' . $theme . '.yml')) {
+ $theme_manifest = Yaml::parseFile($theme_manifest_file);
+ Config::set('themes.'.Config::get('site.theme'), $theme_manifest);
+ }
+ }
+
+ /**
+ * Initialize Flextype Themes
+ *
+ *
+ * Themes::init();
+ *
+ *
+ * @access public
+ * @return object
+ */
+ public static function init()
+ {
+ return !isset(self::$instance) and self::$instance = new Themes();
+ }
+}