diff --git a/src/flextype/bootstrap.php b/src/flextype/bootstrap.php new file mode 100755 index 00000000..c9db1425 --- /dev/null +++ b/src/flextype/bootstrap.php @@ -0,0 +1,168 @@ +set('flextype', $flextype_settings); + +/** + * Create new application + */ +$app = new App([ + 'settings' => [ + 'debug' => $registry->get('flextype.errors.display'), + 'whoops.editor' => $registry->get('flextype.whoops.editor'), + 'whoops.page_title' => $registry->get('flextype.whoops.page_title'), + 'displayErrorDetails' => $registry->get('flextype.display_error_details'), + 'addContentLengthHeader' => $registry->get('flextype.add_content_length_header'), + 'routerCacheFile' => $registry->get('flextype.router_cache_file'), + 'determineRouteBeforeAppMiddleware' => $registry->get('flextype.determine_route_before_app_middleware'), + 'outputBuffering' => $registry->get('flextype.output_buffering'), + 'responseChunkSize' => $registry->get('flextype.response_chunk_size'), + 'httpVersion' => $registry->get('flextype.http_version'), + 'images' => [ + 'driver' => $registry->get('flextype.image.driver'), + ], + ], +]); + +/** + * Set Flextype Dependency Injection Container + */ +$flextype = $app->getContainer(); + +/** + * Include Dependencies + */ +include_once 'dependencies.php'; + +/** + * Include Middlewares + */ +include_once 'middlewares.php'; + +/** + * Include API ENDPOINTS + */ +include_once 'api/delivery/images.php'; +include_once 'api/delivery/entries.php'; +include_once 'api/delivery/registry.php'; + +/** + * Set internal encoding + */ +function_exists('mb_language') and mb_language('uni'); +function_exists('mb_regex_encoding') and mb_regex_encoding($flextype['registry']->get('flextype.charset')); +function_exists('mb_internal_encoding') and mb_internal_encoding($flextype['registry']->get('flextype.charset')); + +/** + * Display Errors + */ +if ($flextype['registry']->get('flextype.errors.display')) { + + /** + * Add WhoopsMiddleware + */ + $app->add(new WhoopsMiddleware($app)); +} else { + error_reporting(0); +} + +/** + * Set default timezone + */ +date_default_timezone_set($flextype['registry']->get('flextype.timezone')); + +/** + * Init plugins + */ +$flextype['plugins']->init($flextype, $app); + +/** + * Run application + */ +$app->run(); diff --git a/src/flextype/config/locales.yaml b/src/flextype/config/locales.yaml new file mode 100644 index 00000000..45b031b2 --- /dev/null +++ b/src/flextype/config/locales.yaml @@ -0,0 +1,490 @@ +# +# A list of supported locales +# +af: + name: Afrikaans + nativeName: Afrikaans +af_ZA: + name: Afrikaans + nativeName: Afrikaans +ak: + name: Akan + nativeName: Akan +ast: + name: Asturian + nativeName: Asturianu +ar: + name: Arabic + nativeName: عربي + orientation: rtl +ar_SA: + name: Arabic + nativeName: عربي + orientation: rtl +as: + name: Assamese + nativeName: অসমীয়া +be: + name: Belarusian + nativeName: Беларуская +bg: + name: Bulgarian + nativeName: Български +bn: + name: Bengali + nativeName: বাংলা +bn_BD: + name: Bengali (Bangladesh) + nativeName: বাংলা (বাংলাদেশ) +bn_IN: + name: Bengali (India) + nativeName: বাংলা (ভারত) +br: + name: Breton + nativeName: Brezhoneg +bs: + name: Bosnian + nativeName: Bosanski +ca: + name: Catalan + nativeName: Català +ca_ES: + name: Catalan + nativeName: Català +ca_valencia: + name: Catalan (Valencian) + nativeName: Català (valencià) +cs: + name: Czech + nativeName: Čeština +cs_CZ: + name: Czech + nativeName: Čeština +cy: + name: Welsh + nativeName: Cymraeg +da: + name: Danish + nativeName: Dansk +da_DK: + name: Danish + nativeName: Dansk +de: + name: German + nativeName: Deutsch +de_AT: + name: German (Austria) + nativeName: Deutsch (Österreich) +de_CH: + name: German (Switzerland) + nativeName: Deutsch (Schweiz) +de_DE: + name: German (Germany) + nativeName: Deutsch (Deutschland) +dsb: + name: Lower Sorbian + nativeName: Dolnoserbšćina +el: + name: Greek + nativeName: Ελληνικά +el_GR: + name: Greek + nativeName: Ελληνικά +en: + name: English + nativeName: English +en_AU: + name: English (Australian) + nativeName: English (Australian) +en_CA: + name: English (Canadian) + nativeName: English (Canadian) +en_GB: + name: English (British) + nativeName: English (British) +en_NZ: + name: English (New Zealand) + nativeName: English (New Zealand) +en_US: + name: English (US) + nativeName: English (US) +en_ZA: + name: English (South African) + nativeName: English (South African) +eo: + name: Esperanto + nativeName: Esperanto +es: + name: Spanish + nativeName: Español +es_AR: + name: Spanish (Argentina) + nativeName: Español (de Argentina) +es_CL: + name: Spanish (Chile) + nativeName: Español (de Chile) +es_ES: + name: Spanish (Spain) + nativeName: Español (de España) +es_MX: + name: Spanish (Mexico) + nativeName: Español (de México) +et: + name: Estonian + nativeName: Eesti keel +eu: + name: Basque + nativeName: Euskara +fa: + name: Persian + nativeName: فارسی + orientation: rtl +fa_IR: + name: Persian + nativeName: فارسی + orientation: rtl +fi: + name: Finnish + nativeName: Suomi +fi_FI: + name: Finnish + nativeName: Suomi +fj_FJ: + name: Fijian + nativeName: Vosa vaka_Viti +fr: + name: French + nativeName: Français +fr_CA: + name: French (Canada) + nativeName: Français (Canada) +fr_FR: + name: French (France) + nativeName: Français (France) +fur: + name: Friulian + nativeName: Furlan +fur_IT: + name: Friulian + nativeName: Furlan +fy: + name: Frisian + nativeName: Frysk +fy_NL: + name: Frisian + nativeName: Frysk +ga: + name: Irish + nativeName: Gaeilge +ga_IE: + name: Irish (Ireland) + nativeName: Gaeilge (Éire) +gd: + name: Gaelic (Scotland) + nativeName: Gàidhlig +gl: + name: Galician + nativeName: Galego +gl_ES: + name: Galician + nativeName: Galego +gu: + name: Gujarati + nativeName: ગુજરાતી +gu_IN: + name: Gujarati + nativeName: ગુજરાતી +he: + name: Hebrew + nativeName: עברית + orientation: rtl +he_IL: + name: Hebrew + nativeName: עברית + orientation: rtl +hi: + name: Hindi + nativeName: हिन्दी +hi_IN: + name: Hindi (India) + nativeName: हिन्दी (भारत) +hr: + name: Croatian + nativeName: Hrvatski +hr_HR: + name: Croatian + nativeName: Hrvatski +hsb: + name: Upper Sorbian + nativeName: Hornjoserbsce +hu: + name: Hungarian + nativeName: Magyar +hu_HU: + name: Hungarian + nativeName: Magyar +hy: + name: Armenian + nativeName: Հայերեն +hy_AM: + name: Armenian + nativeName: Հայերեն +id: + name: Indonesian + nativeName: Bahasa Indonesia +id_ID: + name: Indonesian + nativeName: Bahasa Indonesia +is: + name: Icelandic + nativeName: íslenska +it: + name: Italian + nativeName: Italiano +it_IT: + name: Italian + nativeName: Italiano +ja: + name: Japanese + nativeName: 日本語 +ja_JP: + name: Japanese + nativeName: 日本語 +ka: + name: Georgian + nativeName: ქართული +kk: + name: Kazakh + nativeName: Қазақ +kn: + name: Kannada + nativeName: ಕನ್ನಡ +ko: + name: Korean + nativeName: 한국어 +ko_KR: + name: Korean + nativeName: 한국어 +ku: + name: Kurdish + nativeName: Kurdî +la: + name: Latin + nativeName: Latina +lb: + name: Luxembourgish + nativeName: Lëtzebuergesch +lg: + name: Luganda + nativeName: Luganda +lt: + name: Lithuanian + nativeName: Lietuvių kalba +lv: + name: Latvian + nativeName: Latviešu +mai: + name: Maithili + nativeName: मैथिली মৈথিলী +mg: + name: Malagasy + nativeName: Malagasy +mi: + name: Maori (Aotearoa) + nativeName: Māori (Aotearoa) +mk: + name: Macedonian + nativeName: Македонски +ml: + name: Malayalam + nativeName: മലയാളം +mn: + name: Mongolian + nativeName: Монгол +mr: + name: Marathi + nativeName: मराठी +'no': + name: Norwegian + nativeName: Norsk +no_NO: + name: Norwegian + nativeName: Norsk +nb: + name: Norwegian + nativeName: Norsk +nb_NO: + name: Norwegian (Bokmål) + nativeName: Norsk bokmål +ne_NP: + name: Nepali + nativeName: नेपाली +nn_NO: + name: Norwegian (Nynorsk) + nativeName: Norsk nynorsk +nl: + name: Dutch + nativeName: Nederlands +nl_NL: + name: Dutch + nativeName: Nederlands +nr: + name: Ndebele, South + nativeName: IsiNdebele +nso: + name: Northern Sotho + nativeName: Sepedi +oc: + name: Occitan (Lengadocian) + nativeName: Occitan (lengadocian) +or: + name: Oriya + nativeName: ଓଡ଼ିଆ +pa: + name: Punjabi + nativeName: ਪੰਜਾਬੀ +pa_IN: + name: Punjabi + nativeName: ਪੰਜਾਬੀ +pl: + name: Polish + nativeName: Polski +pl_PL: + name: Polish + nativeName: Polski +pt: + name: Portuguese + nativeName: Português +pt_BR: + name: Portuguese (Brazilian) + nativeName: Português (do Brasil) +pt_PT: + name: Portuguese (Portugal) + nativeName: Português (Europeu) +ro: + name: Romanian + nativeName: Română +ro_RO: + name: Romanian + nativeName: Română +rm: + name: Romansh + nativeName: Rumantsch +ru: + name: Russian + nativeName: Русский +ru_RU: + name: Russian + nativeName: Русский +rw: + name: Kinyarwanda + nativeName: Ikinyarwanda +si: + name: Sinhala + nativeName: සිංහල +sk: + name: Slovak + nativeName: Slovenčina +sl: + name: Slovenian + nativeName: Slovensko +son: + name: Songhai + nativeName: Soŋay +sq: + name: Albanian + nativeName: Shqip +sr: + name: Serbian + nativeName: Српски +sr_SP: + name: Serbian + nativeName: Српски +sr_Latn: + name: Serbian + nativeName: Srpski +ss: + name: Siswati + nativeName: siSwati +st: + name: Southern Sotho + nativeName: Sesotho +sv: + name: Swedish + nativeName: Svenska +sv_SE: + name: Swedish + nativeName: Svenska +ta: + name: Tamil + nativeName: தமிழ் +ta_IN: + name: Tamil (India) + nativeName: தமிழ் (இந்தியா) +ta_LK: + name: Tamil (Sri Lanka) + nativeName: தமிழ் (இலங்கை) +te: + name: Telugu + nativeName: తెలుగు +th: + name: Thai + nativeName: ไทย +tlh: + name: Klingon + nativeName: Klingon +tn: + name: Tswana + nativeName: Setswana +tr: + name: Turkish + nativeName: Türkçe +tr_TR: + name: Turkish + nativeName: Türkçe +ts: + name: Tsonga + nativeName: Xitsonga +tt: + name: Tatar + nativeName: Tatarça +tt_RU: + name: Tatar + nativeName: Tatarça +uk: + name: Ukrainian + nativeName: Українська +uk_UA: + name: Ukrainian + nativeName: Українська +ur: + name: Urdu + nativeName: اُردو + orientation: rtl +ve: + name: Venda + nativeName: Tshivenḓa +vi: + name: Vietnamese + nativeName: Tiếng Việt +vi_VN: + name: Vietnamese + nativeName: Tiếng Việt +wo: + name: Wolof + nativeName: Wolof +xh: + name: Xhosa + nativeName: isiXhosa +zh: + name: Chinese (Simplified) + nativeName: 中文 (简体) +zh_CN: + name: Chinese (Simplified) + nativeName: 中文 (简体) +zh_TW: + name: Chinese (Traditional) + nativeName: 正體中文 (繁體) +zu: + name: Zulu + nativeName: isiZulu diff --git a/src/flextype/config/settings.yaml b/src/flextype/config/settings.yaml new file mode 100644 index 00000000..be8d51d8 --- /dev/null +++ b/src/flextype/config/settings.yaml @@ -0,0 +1,200 @@ +# Set the timezone to be used on the website. +# For a list of valid timezone settings, see: +# http://php.net/manual/en/timezones.php +timezone: UTC + +# Charset +# +# Set internal character encoding. +# +# Currently the following names are supported: +# http://php.net/manual/en/function.mb-regex-encoding.php#121645 +charset: UTF-8 + +# The locale that'll be used by the Flextype. +# +# Available locales to use: flextype/config/locales.yaml +locale: en_US + +# Application URL +# +# Define custom application url +url: '' + +# Valid date format +# +# - date_format: Valid date format +# +# - date_display_format: Valid date format to display +# +# Date format variants: +# +# d-m-Y H:i" - 02-02-2020 09:41 +# Y-m-d H:i" - 2020-02-02 09:41 +# m/d/Y h:i a - 02/02/2020 09:41 pm +# H:i d-m-Y - 09:41 02-02-2020 +# h:i a m/d/Y - 09:41 pm 02/02/2020 +date_format: 'd-m-Y H:i' +date_display_format: 'd-m-Y H:i' + +# Display errors +# +# Please make sure to set false for error `display` in production! +# +# Displaying PHP errors on a public server can be a serious security risk: +# +# - Error messages are displayed with detailed information about the code structure (e.g. file path, class, method) +# - With Whoops enabled, there will be even more detailed information about the code structure +# - Detailed error messages for login failures could leak information to attackers +# +# In a production environment, always log errors to your PHP error logs. +# +# - display: Display errors or not. +errors: + display: false + +# Cache +# +# - enabled: Set to true to enable caching +# +# - prefix: Cache prefix string (prevents cache conflicts) +# +# - driver: Available drivers: auto (will get one from installed cache drivers), apcu, +# apc, array, wincache, xcache, memcache, memcached, redis, file. +# +# - lifetime: Lifetime of cached data in seconds +# +# - redis.socket: Path to redis unix socket (e.g. /var/run/redis/redis.sock), +# false = use server and port to connect +# +# - redis.password Redis password +# +# - redis.server Redis server +# +# - redis.port Redis port +# +# - memcache.server Memcache server +# +# - memcache.port Memcache port +# +# - memcached.server Memcached server +# +# - memcached.port Memcached port +# +# - sqlite3.database SQLite3 Database +# +# - sqlite3.table SQLite3 Table +cache: + enabled: true + prefix: flextype + driver: auto + lifetime: 604800 + memcache: + server: localhost + port: 11211 + memcached: + server: localhost + port: 11211 + redis: + socket: false + password: false + server: localhost + port: 6379 + sqlite3: + database: flextype + table: flextype + +# Whoops +# +# Error handler framework for PHP. +# +# - editor: emacs, idea, macvim, phpstorm, sublime, textmate, xdebug, vscode, atom, espresso +# +# - page_title: page title +whoops: + editor: atom + page_title: Error! + +# Slim +# +# - display_error_details: When true, additional information about exceptions are +# displayed by the default error handler. +# +# - add_content_length_header: When true, Slim will add a Content-Length header to +# the response. If you are using a runtime analytics tool, +# such as New Relic, then this should be disabled. +# +# - router_cache_file: Filename for caching the FastRoute routes. Must be set to +# a valid filename within a writeable directory. If the file +# does not exist, then it is created with the correct cache +# information on first run. Set to false to disable the FastRoute +# cache system. +# +# - determine_route_before_app_middleware: When true, the route is calculated before +# any middleware is executed. This means that you +# can inspect route parameters in middleware if you need to. +# +# - output_buffering: If false, then no output buffering is enabled. +# If 'append' or 'prepend', then any echo or print statements +# are captured and are either appended or prepended to the Response +# returned from the route callable. +# +# - response_chunk_size: Size of each chunk read from the Response body when sending to the browser. +# +# - http_version: The protocol version used by the Response object. +display_error_details: false +add_content_length_header: true +router_cache_file: false +determine_route_before_app_middleware: false +output_buffering: append +response_chunk_size: 4096 +http_version: '1.1' + +# Slugify +# +# - separator: By default Slugify will use dashes as separators. +# If you want to use a different default separator, +# you can set the separator option. +# +# - lowercase: By default Slugify will convert the slug to lowercase. +# If you want to preserve the case of the string you can set the +# lowercase option to false. +# +# - trim: By default Slugify will remove leading and trailing separators before +# returning the slug. If you do not want the slug to be trimmed you can +# set the trim option to false. +# +# - regexp: You can also change the regular expression that is used to replace +# characters with the separator. +# +# - lowercase_after_regexp: Lowercasing is done before using the regular expression. +# If you want to keep the lowercasing behavior but your +# regular expression needs to match uppercase letters, +# you can set the lowercase_after_regexp option to true. +# +# - strip_tags: Adds in an option to go through strip_tags() in case the string contains HTML etc. +slugify: + separator: "-" + lowercase: true + trim: true + regexp: "/[^A-Za-z0-9]+/" + lowercase_after_regexp: false + strip_tags: false + +# Image +# +# - driver: gd or imagick +image: + driver: gd + +# API's +api: + images: + enabled: true + default_token: + entries: + enabled: true + default_token: + registry: + enabled: true + default_token: diff --git a/src/flextype/core/Cache/AcpuAdapter.php b/src/flextype/core/Cache/AcpuAdapter.php new file mode 100644 index 00000000..6bbd38e2 --- /dev/null +++ b/src/flextype/core/Cache/AcpuAdapter.php @@ -0,0 +1,21 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + return new AcpuCache(); + } +} diff --git a/src/flextype/core/Cache/ArrayAdapter.php b/src/flextype/core/Cache/ArrayAdapter.php new file mode 100644 index 00000000..4264f38b --- /dev/null +++ b/src/flextype/core/Cache/ArrayAdapter.php @@ -0,0 +1,21 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + return new ArrayCache(); + } +} diff --git a/src/flextype/core/Cache/Cache.php b/src/flextype/core/Cache/Cache.php new file mode 100755 index 00000000..591fbf8c --- /dev/null +++ b/src/flextype/core/Cache/Cache.php @@ -0,0 +1,245 @@ +flextype = $flextype; + + // Create Cache Directory + ! Filesystem::has(PATH['cache']) and Filesystem::createDir(PATH['cache']); + + // Set current time + $this->now = time(); + + // Create cache key to allow invalidate all cache on configuration changes. + $this->key = ($this->flextype['registry']->get('flextype.cache.prefix') ?? 'flextype') . '-' . md5(PATH['site'] . 'Flextype::VERSION'); + + // Get Cache Driver + $this->driver = $this->getCacheDriver(); + + // Set the cache namespace to our unique key + $this->driver->setNamespace($this->key); + } + + /** + * Get Cache Driver + * + * @access public + */ + public function getCacheDriver() : object + { + return $this->flextype['cache_adapter']->getDriver(); + } + + /** + * Returns driver variable + * + * @access public + */ + public function driver() : object + { + return $this->driver; + } + + /** + * Get cache key. + * + * @access public + */ + public function getKey() : string + { + return $this->key; + } + + /** + * Fetches an entry from the cache. + * + * @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. + * + * @access public + */ + public function fetch(string $id) + { + if ($this->flextype['registry']->get('flextype.cache.enabled')) { + return $this->driver->fetch($id); + } + + return false; + } + + /** + * Returns a boolean state of whether or not the item exists in the cache based on id key + * + * @param string $id the id of the cached data entry + * + * @return bool true if the cached items exists + */ + public function contains(string $id) : bool + { + if ($this->flextype['registry']->get('flextype.cache.enabled')) { + return $this->driver->contains($id); + } + + return false; + } + + /** + * Puts data into the cache. + * + * @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). + * + * @access public + */ + public function save(string $id, $data, ?int $lifetime = null) : void + { + if (! $this->flextype['registry']->get('flextype.cache.enabled')) { + return; + } + + if ($lifetime === null) { + $lifetime = $this->getLifetime(); + } + $this->driver->save($id, $data, $lifetime); + } + + /** + * Delete item from the chache + */ + public function delete(string $id) : void + { + if (! $this->flextype['registry']->get('flextype.cache.enabled')) { + return; + } + + $this->driver->delete($id); + } + + /** + * Clear Cache + */ + public function clear(string $id) : void + { + // Clear stat cache + @clearstatcache(); + + // Clear opcache + function_exists('opcache_reset') and @opcache_reset(); + + // Remove cache dirs + Filesystem::deleteDir(PATH['cache'] . '/' . $id); + } + + /** + * Clear ALL Cache + */ + public function clearAll() : void + { + // Clear stat cache + @clearstatcache(); + + // Clear opcache + function_exists('opcache_reset') and @opcache_reset(); + + // Remove cache directory + Filesystem::deleteDir(PATH['cache']); + } + + /** + * Set the cache lifetime. + * + * @param int $future timestamp + * + * @access public + */ + public function setLifetime(int $future) : void + { + if (! $future) { + return; + } + + $interval = $future-$this->now; + + if ($interval <= 0 || $interval >= $this->getLifetime()) { + return; + } + + $this->lifetime = $interval; + } + + /** + * Retrieve the cache lifetime (in seconds) + * + * @return mixed + * + * @access public + */ + public function getLifetime() + { + if ($this->lifetime === null) { + $this->lifetime = $this->flextype['registry']->get('flextype.cache.lifetime') ?: 604800; + } + + return $this->lifetime; + } +} diff --git a/src/flextype/core/Cache/CacheAdapterInterface.php b/src/flextype/core/Cache/CacheAdapterInterface.php new file mode 100644 index 00000000..fda0f5f2 --- /dev/null +++ b/src/flextype/core/Cache/CacheAdapterInterface.php @@ -0,0 +1,22 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + $cache_directory = PATH['cache'] . '/doctrine/'; + + if (! Filesystem::has($cache_directory)) { + Filesystem::createDir($cache_directory); + } + + return new FilesystemCache($cache_directory); + } +} diff --git a/src/flextype/core/Cache/MemcachedAdapter.php b/src/flextype/core/Cache/MemcachedAdapter.php new file mode 100644 index 00000000..4c93d632 --- /dev/null +++ b/src/flextype/core/Cache/MemcachedAdapter.php @@ -0,0 +1,31 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + $memcached = new Memecached(); + $memcached->addServer( + $this->flextype['registry']->get('flextype.cache.memcached.server', 'localhost'), + $this->flextype['registry']->get('flextype.cache.memcache.port', 11211) + ); + + $driver = new MemcachedCache(); + $driver->setMemcached($memcached); + + return $driver; + } +} diff --git a/src/flextype/core/Cache/RedisAdapter.php b/src/flextype/core/Cache/RedisAdapter.php new file mode 100644 index 00000000..91583a6f --- /dev/null +++ b/src/flextype/core/Cache/RedisAdapter.php @@ -0,0 +1,44 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + $redis = new Redis(); + $socket = $this->flextype['registry']->get('flextype.cache.redis.socket', false); + $password = $this->flextype['registry']->get('flextype.cache.redis.password', false); + + if ($socket) { + $redis->connect($socket); + } else { + $redis->connect( + $this->flextype['registry']->get('flextype.cache.redis.server', 'localhost'), + $this->flextype['registry']->get('flextype.cache.redis.port', 6379) + ); + } + + // Authenticate with password if set + if ($password && ! $redis->auth($password)) { + throw new RedisException('Redis authentication failed'); + } + + $driver = new RedisCache(); + $driver->setRedis($redis); + + return $driver; + } +} diff --git a/src/flextype/core/Cache/SQLite3Adapter.php b/src/flextype/core/Cache/SQLite3Adapter.php new file mode 100644 index 00000000..1a9adce7 --- /dev/null +++ b/src/flextype/core/Cache/SQLite3Adapter.php @@ -0,0 +1,31 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + $cache_directory = PATH['cache'] . '/doctrine/'; + + if (! Filesystem::has($cache_directory)) { + Filesystem::createDir($cache_directory); + } + + $db = new SQLite3($cache_directory . $this->flextype['registry']->get('flextype.cache.sqlite3.database', 'flextype') . '.db'); + + return new SQLite3Cache($db, $this->flextype['registry']->get('flextype.cache.sqlite3.table', 'flextype')); + } +} diff --git a/src/flextype/core/Cache/WinCacheAdapter.php b/src/flextype/core/Cache/WinCacheAdapter.php new file mode 100644 index 00000000..1e80ed71 --- /dev/null +++ b/src/flextype/core/Cache/WinCacheAdapter.php @@ -0,0 +1,21 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + return new WinCacheCache(); + } +} diff --git a/src/flextype/core/Cache/ZendDataCacheAdapter.php b/src/flextype/core/Cache/ZendDataCacheAdapter.php new file mode 100644 index 00000000..5a674d39 --- /dev/null +++ b/src/flextype/core/Cache/ZendDataCacheAdapter.php @@ -0,0 +1,21 @@ +flextype = $flextype; + } + + public function getDriver() : object + { + return new ZendDataCache(); + } +} diff --git a/src/flextype/core/Entries/Entries.php b/src/flextype/core/Entries/Entries.php new file mode 100755 index 00000000..88fde5a3 --- /dev/null +++ b/src/flextype/core/Entries/Entries.php @@ -0,0 +1,603 @@ + Comparison::EQ, + '=' => Comparison::EQ, + + '<>' => Comparison::NEQ, + '!=' => Comparison::NEQ, + 'neq' => Comparison::NEQ, + + '<' => Comparison::LT, + 'lt' => Comparison::LT, + + '<=' => Comparison::LTE, + 'lte' => Comparison::LTE, + + '>' => Comparison::GT, + 'gt' => Comparison::GT, + + '>=' => Comparison::GTE, + 'gte' => Comparison::GTE, + + 'is' => Comparison::IS, + 'in' => Comparison::IN, + 'nin' => Comparison::NIN, + 'contains' => Comparison::CONTAINS, + 'like' => Comparison::CONTAINS, + 'member_of' => Comparison::MEMBER_OF, + 'start_with' => Comparison::STARTS_WITH, + 'ends_with' => Comparison::ENDS_WITH + ]; + + /** + * Set Order Direction + * + * @var array + * @access public + */ + public $direction = [ + 'asc' => Criteria::ASC, + 'desc' => Criteria::DESC, + ]; + + /** + * Set Visibility + * + * @var array + * @access public + */ + public $visibility = [ + 'draft' => 'draft', + 'hidden' => 'hidden', + 'visible' => 'visible' + ]; + + /** + * Flextype Dependency Container + * + * @access private + */ + private $flextype; + + /** + * Constructor + * + * @access public + */ + public function __construct($flextype) + { + $this->flextype = $flextype; + } + + /** + * Fetch entry(entries) + * + * @param string $id Entry ID + * @param array|null $args Query arguments. + * + * @return array The entry array data. + * + * @access public + */ + public function fetch(string $id, $args = null) : array + { + // If args is array then it is entries collection request + if (is_array($args)) { + return $this->fetchCollection($id, $args); + } else { + return $this->fetchSingle($id); + } + } + + /** + * Fetch single entry + * + * @param string $id Entry ID + * + * @return array The entry array data. + * + * @access public + */ + public function fetchSingle(string $id) : array + { + // Get entry file location + $entry_file = $this->getFileLocation($id); + + // If requested entry file founded then process it + if (Filesystem::has($entry_file)) { + // Create unique entry cache_id + // Entry Cache ID = entry + entry file + entry file time stamp + if ($timestamp = Filesystem::getTimestamp($entry_file)) { + $entry_cache_id = md5('entry' . $entry_file . $timestamp); + } else { + $entry_cache_id = md5('entry' . $entry_file); + } + + // Try to get the requested entry from cache + if ($this->flextype['cache']->contains($entry_cache_id)) { + // Try to fetch requested entry from the cache + if ($entry = $this->flextype['cache']->fetch($entry_cache_id)) { + // Run event onEntryAfterInitialized + $this->flextype['emitter']->emit('onEntryAfterInitialized'); + + // Return entry + return $entry; + } + + // Return empty array + return []; + + // else Try to get requested entry from the filesystem + } + + $entry_decoded = $this->flextype['parser']->decode(Filesystem::read($entry_file), 'frontmatter'); + + // + // Add predefined entry items + // + + // Entry Published At + $entry_decoded['published_at'] = isset($entry_decoded['published_at']) ? (int) strtotime($entry_decoded['published_at']) : (int) Filesystem::getTimestamp($entry_file); + + // Entry Created At + $entry_decoded['created_at'] = isset($entry_decoded['created_at']) ? (int) strtotime($entry_decoded['created_at']) : (int) Filesystem::getTimestamp($entry_file); + + // Entry Modified + $entry_decoded['modified_at'] = (int) Filesystem::getTimestamp($entry_file); + + // Entry Slug + $entry_decoded['slug'] = isset($entry_decoded['slug']) ? (string) $entry_decoded['slug'] : (string) ltrim(rtrim($id, '/'), '/'); + + // Entry Routable + $entry_decoded['routable'] = isset($entry_decoded['routable']) ? (bool) $entry_decoded['routable'] : true; + + // Entry Visibility + if (isset($entry_decoded['visibility']) && in_array($entry_decoded['visibility'], $this->visibility)) { + $entry_decoded['visibility'] = (string) $this->visibility[$entry_decoded['visibility']]; + } else { + $entry_decoded['visibility'] = (string) $this->visibility['visible']; + } + + // Save decoded entry content into the cache + $this->flextype['cache']->save($entry_cache_id, $entry_decoded); + + // Set entry to the Entry class property $entry + $this->entry = $entry_decoded; + + // Run event onEntryAfterInitialized + $this->flextype['emitter']->emit('onEntryAfterInitialized'); + + // Return entry from the Entry class property $entry + return $this->entry; + } + + // Return empty array + return []; + } + + /** + * Fetch entries collection + * + * @param string $id Entry ID + * @param array $args Query arguments. + * + * @return array The entries array data. + * + * @access public + */ + public function fetchCollection(string $id, array $args = []) : array + { + // Init Entries + $entries = []; + + // Init Entries + $this->entries = $entries; + + // Set Expression + $expression = $this->expression; + + // Set Direction + $direction = $this->direction; + + // Bind: Entry ID + $bind_id = $id; + + // Bind: recursive + $bind_recursive = $args['recursive'] ?? false; + + // Bind: set first result + $bind_set_first_result = $args['set_first_result'] ?? false; + + // Bind: set max result + $bind_set_max_result = $args['set_max_result'] ?? false; + + // Bind: where + $bind_where = []; + if (isset($args['where']['key']) && isset($args['where']['expr']) && isset($args['where']['value'])) { + $bind_where['where']['key'] = $args['where']['key']; + $bind_where['where']['expr'] = $expression[$args['where']['expr']]; + $bind_where['where']['value'] = $args['where']['value']; + } + + // Bind: and where + $bind_and_where = []; + if (isset($args['and_where'])) { + foreach ($args['and_where'] as $key => $value) { + if (! isset($value['key']) || ! isset($value['expr']) || ! isset($value['value'])) { + continue; + } + + $bind_and_where[$key] = $value; + } + } + + // Bind: or where + $bind_or_where = []; + if (isset($args['or_where'])) { + foreach ($args['or_where'] as $key => $value) { + if (! isset($value['key']) || ! isset($value['expr']) || ! isset($value['value'])) { + continue; + } + + $bind_or_where[$key] = $value; + } + } + + // Bind: order by + $bind_order_by = []; + if (isset($args['order_by']['field']) && isset($args['order_by']['direction'])) { + $bind_order_by['order_by']['field'] = $args['order_by']['field']; + $bind_order_by['order_by']['direction'] = $args['order_by']['direction']; + } + + // Get entries path + $entries_path = $this->getDirLocation($bind_id); + + // Get entries list + $entries_list = Filesystem::listContents($entries_path, $bind_recursive); + + // If entries founded in entries folder + if (count($entries_list) > 0) { + // Entries IDs + $entries_ids = ''; + + // Entries IDs timestamps + $entries_ids_timestamps = ''; + + // Create entries array from entries list and ignore current requested entry + foreach ($entries_list as $current_entry) { + if (strpos($current_entry['path'], $bind_id . '/entry' . '.' . 'md') !== false) { + // ignore ... + } else { + // We are checking... + // Whether the requested entry is a director and whether the file entry is in this directory. + if ($current_entry['type'] === 'dir' && Filesystem::has($current_entry['path'] . '/entry.md')) { + // Get entry uid + // 1. Remove entries path + // 2. Remove left and right slashes + $uid = ltrim(rtrim(str_replace(PATH['entries'], '', $current_entry['path']), '/'), '/'); + + // For each founded entry we should create $entries array. + $entry = $this->fetch($uid); + + // Add entry into the entries + $entries[$uid] = $entry; + + // Create entries IDs list + $entries_ids .= $uid; + + // Create entries IDs timestamps + $entries_ids_timestamps .= Filesystem::getTimestamp($current_entry['path'] . '/entry.md'); + } + } + } + + // Create unique entries $cache_id + $cache_id = md5( + $bind_id . + $entries_ids . + $entries_ids_timestamps . + ($bind_recursive ? 'true' : 'false') . + ($bind_set_max_result ? $bind_set_max_result : '') . + ($bind_set_first_result ? $bind_set_first_result : '') . + json_encode($bind_where) . + json_encode($bind_and_where) . + json_encode($bind_or_where) . + json_encode($bind_order_by) + ); + + // If requested entries exist with a specific cache_id, + // then we take them from the cache otherwise we look for them. + if ($this->flextype['cache']->contains($cache_id)) { + $entries = $this->flextype['cache']->fetch($cache_id); + } else { + // Save error_reporting state and turn it off + // because PHP Doctrine Collections don't works with collections + // if there is no requested fields to search: + // vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php + // line 40: return $object[$field]; + // + // @todo research this issue and find possible better solution to avoid this in the future + $oldErrorReporting = error_reporting(); + error_reporting(0); + + // Create Array Collection from entries array + $collection = new ArrayCollection($entries); + + // Create Criteria for filtering Selectable collections. + $criteria = new Criteria(); + + // Exec: where + if (isset($bind_where['where']['key']) && isset($bind_where['where']['expr']) && isset($bind_where['where']['value'])) { + $expr = new Comparison($bind_where['where']['key'], $bind_where['where']['expr'], $bind_where['where']['value']); + $criteria->where($expr); + } + + // Exec: and where + if (isset($bind_and_where)) { + $_expr = []; + foreach ($bind_and_where as $key => $value) { + $_expr[$key] = new Comparison($value['key'], $expression[$value['expr']], $value['value']); + $criteria->andWhere($_expr[$key]); + } + } + + // Exec: or where + if (isset($bind_or_where)) { + $_expr = []; + foreach ($bind_or_where as $key => $value) { + $_expr[$key] = new Comparison($value['key'], $expression[$value['expr']], $value['value']); + $criteria->orWhere($_expr[$key]); + } + } + + // Exec: order by + if (isset($bind_order_by['order_by']['field']) && isset($bind_order_by['order_by']['direction'])) { + $criteria->orderBy([$bind_order_by['order_by']['field'] => $direction[$bind_order_by['order_by']['direction']]]); + } + + // Exec: set max result + if ($bind_set_max_result) { + $criteria->setMaxResults($bind_set_max_result); + } + + // Exec: set first result + if ($bind_set_first_result) { + $criteria->setFirstResult($bind_set_first_result); + } + + // Get entries for matching criterias + $entries = $collection->matching($criteria); + + // Gets a native PHP array representation of the collection. + $entries = $entries->toArray(); + + // Restore error_reporting + error_reporting($oldErrorReporting); + + // Save entries into the cache + $this->flextype['cache']->save($cache_id, $entries); + } + + // Set entries into the property entries + $this->entries = $entries; + + // Run event onEntriesAfterInitialized + $this->flextype['emitter']->emit('onEntriesAfterInitialized'); + } + + // Return entries + return $this->entries; + } + + /** + * Rename entry + * + * @param string $id Entry ID + * @param string $new_id New Entry ID + * + * @return bool True on success, false on failure. + * + * @access public + */ + public function rename(string $id, string $new_id) : bool + { + return rename($this->getDirLocation($id), $this->getDirLocation($new_id)); + } + + /** + * Update entry + * + * @param string $id Entry ID + * @param array $data Data + * + * @return bool True on success, false on failure. + * + * @access public + */ + public function update(string $id, array $data) : bool + { + $entry_file = $this->getFileLocation($id); + + if (Filesystem::has($entry_file)) { + $body = Filesystem::read($entry_file); + $entry = $this->flextype['parser']->decode($body, 'frontmatter'); + return Filesystem::write($entry_file, $this->flextype['parser']->encode(array_merge($entry, $data), 'frontmatter')); + } + + return false; + } + + /** + * Create entry + * + * @param string $id Entry ID + * @param array $data Data + * + * @return bool True on success, false on failure. + * + * @access public + */ + public function create(string $id, array $data) : bool + { + $entry_dir = $this->getDirLocation($id); + + if (! Filesystem::has($entry_dir)) { + // Try to create directory for new entry + if (Filesystem::createDir($entry_dir)) { + // Check if new entry file exists + if (! Filesystem::has($entry_file = $entry_dir . '/entry.md')) { + $data['uuid'] = Uuid::uuid4()->toString(); + $data['published_at'] = date($this->flextype->registry->get('flextype.date_format'), time()); + $data['created_at'] = date($this->flextype->registry->get('flextype.date_format'), time()); + $data['published_by'] = (Session::exists('uuid') ? Session::get('uuid') : ''); + $data['created_by'] = (Session::exists('uuid') ? Session::get('uuid') : ''); + + if (isset($data['routable']) && is_bool($data['routable'])) { + $data['routable'] = $data['routable']; + } else { + $data['routable'] = true; + } + + if (isset($data['visibility']) && in_array($data['visibility'], ['visible', 'draft', 'hidden'])) { + $data['visibility'] = $data['visibility']; + } else { + $data['visibility'] = 'visible'; + } + + return Filesystem::write($entry_file, $this->flextype['parser']->encode($data, 'frontmatter')); + } + + return false; + } + } + + return false; + } + + /** + * Delete entry + * + * @param string $id Entry ID + * + * @return bool True on success, false on failure. + * + * @access public + */ + public function delete(string $id) : bool + { + return Filesystem::deleteDir($this->getDirLocation($id)); + } + + /** + * Copy entry(s) + * + * @param string $id Entry id + * @param string $new_id New entry id + * @param bool $recursive Recursive copy entries. + * + * @return bool|null True on success, false on failure. + * + * @access public + */ + public function copy(string $id, string $new_id, bool $recursive = false) + { + return Filesystem::copy($this->getDirLocation($id), $this->getDirLocation($new_id), $recursive); + } + + /** + * Check whether entry exists + * + * @param string $id Entry ID + * + * @return bool True on success, false on failure. + * + * @access public + */ + public function has(string $id) : bool + { + return Filesystem::has($this->getFileLocation($id)); + } + + /** + * Get entry file location + * + * @param string $id Entry ID + * + * @return string entry file location + * + * @access private + */ + public function getFileLocation(string $id) : string + { + return PATH['entries'] . '/' . $id . '/entry.md'; + } + + /** + * Get entry directory location + * + * @param string $id Entry ID + * + * @return string entry directory location + * + * @access private + */ + public function getDirLocation(string $id) : string + { + return PATH['entries'] . '/' . $id; + } +} diff --git a/src/flextype/core/Parsers/Frontmatter.php b/src/flextype/core/Parsers/Frontmatter.php new file mode 100644 index 00000000..a6883d67 --- /dev/null +++ b/src/flextype/core/Parsers/Frontmatter.php @@ -0,0 +1,65 @@ + trim($input)]; + } + + return Yaml::decode(trim($parts[1])) + ['content' => trim(implode(PHP_EOL . '---' . PHP_EOL, array_slice($parts, 2)))]; + } +} diff --git a/src/flextype/core/Parsers/Json.php b/src/flextype/core/Parsers/Json.php new file mode 100644 index 00000000..03b5a5ae --- /dev/null +++ b/src/flextype/core/Parsers/Json.php @@ -0,0 +1,114 @@ +text($input); + } +} diff --git a/src/flextype/core/Parsers/Parser.php b/src/flextype/core/Parsers/Parser.php new file mode 100644 index 00000000..fdaf1934 --- /dev/null +++ b/src/flextype/core/Parsers/Parser.php @@ -0,0 +1,178 @@ + [ + 'name' => 'frontmatter', + 'ext' => 'md', + ], 'json' => [ + 'name' => 'json', + 'ext' => 'json', + ], 'yaml' => [ + 'name' => 'yaml', + 'ext' => 'yaml', + ], 'markdown' => [ + 'name' => 'markdown', + 'ext' => 'md', + ], + ]; + + /** + * Constructor + * + * @access public + */ + public function __construct($flextype) + { + $this->flextype = $flextype; + } + + /** + * Get Parser Information + * + * @param string $input Content to parse + * @param string $parser Parser type [frontmatter, json, yaml] + * + * @return array + */ + public function getParserInfo(string $parser) : array + { + return $this->parsers[$parser]; + } + + /** + * Dumps a PHP value to a string CONTENT. + * + * @param mixed $input Content to parse + * @param string $parser Parser type [frontmatter, json, yaml] + * + * @return mixed PHP value converted to a string CONTENT. + */ + public function encode($input, string $parser) : string + { + switch ($parser) { + case 'frontmatter': + return Frontmatter::encode($input); + + break; + case 'json': + return Json::encode($input); + + break; + case 'yaml': + return Yaml::encode($input); + + break; + default: + break; + } + } + + /** + * Parse INPUT content into a PHP value. + * + * @param string $input Content to parse + * @param string $parser Parser type [frontmatter, json, yaml, markdown] + * @param bool $cache Cache result data or no. Default is true + * + * @return mixed The Content converted to a PHP value + */ + public function decode(string $input, string $parser, bool $cache = true) + { + switch ($parser) { + case 'frontmatter': + if ($cache === true && $this->flextype['registry']->get('flextype.cache.enabled') === true) { + $key = md5($input); + + if ($data_from_cache = $this->flextype['cache']->fetch($key)) { + return $data_from_cache; + } + + $data = Frontmatter::decode($input); + $this->flextype['cache']->save($key, $data); + + return $data; + } else { + return Frontmatter::decode($input); + } + + break; + case 'json': + if ($cache === true && $this->flextype['registry']->get('flextype.cache.enabled') === true) { + $key = md5($input); + + if ($data_from_cache = $this->flextype['cache']->fetch($key)) { + return $data_from_cache; + } + + $data = Json::decode($input); + $this->flextype['cache']->save($key, $data); + + return $data; + } else { + return Json::decode($input); + } + + break; + case 'yaml': + if ($cache === true && $this->flextype['registry']->get('flextype.cache.enabled') === true) { + $key = md5($input); + + if ($data_from_cache = $this->flextype['cache']->fetch($key)) { + return $data_from_cache; + } + + $data = Yaml::decode($input); + $this->flextype['cache']->save($key, $data); + + return $data; + } else { + return Yaml::decode($input); + } + + break; + case 'markdown': + if ($cache === true && $this->flextype['registry']->get('flextype.cache.enabled') === true) { + $key = md5($input); + + if ($data_from_cache = $this->flextype['cache']->fetch($key)) { + return $data_from_cache; + } + + $data = Markdown::decode($input); + $this->flextype['cache']->save($key, $data); + + return $data; + } else { + return Markdown::decode($input); + } + + break; + default: + // code... + break; + } + } +} diff --git a/src/flextype/core/Parsers/Yaml.php b/src/flextype/core/Parsers/Yaml.php new file mode 100644 index 00000000..1c370d65 --- /dev/null +++ b/src/flextype/core/Parsers/Yaml.php @@ -0,0 +1,124 @@ +getMessage(), 0, $e); + } + } + + /** + * Parses YAML into a PHP value. + * + * @param string $input A string containing YAML + * + * @return array The YAML converted to a PHP value + * + * @throws ParseException If the YAML is not valid + */ + public static function decode(string $input) : array + { + // Try native PECL YAML PHP extension first if available. + if (\function_exists('yaml_parse') && self::$native) { + // Safely decode YAML. + $saved = @ini_get('yaml.decode_php'); + @ini_set('yaml.decode_php', '0'); + $decoded = @yaml_parse($input); + @ini_set('yaml.decode_php', $saved); + + if ($decoded !== false) { + return $decoded; + } + } + + try { + return SymfonyYaml::parse($input, self::$flags); + } catch (SymfonyYamlParseException $e) { + throw new RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e); + } + } +} diff --git a/src/flextype/core/Plugins/Plugins.php b/src/flextype/core/Plugins/Plugins.php new file mode 100755 index 00000000..8720c5d2 --- /dev/null +++ b/src/flextype/core/Plugins/Plugins.php @@ -0,0 +1,285 @@ +flextype = $flextype; + $this->locales = $this->flextype['parser']->decode(Filesystem::read(ROOT_DIR . '/flextype/config/locales.yaml'), 'yaml'); + } + + /** + * Get locales + * + * @return array + * + * @access public + */ + public function getLocales() : array + { + return $this->locales; + } + + /** + * Init Plugins + * + * @return void + * + * @access private + */ + public function init($flextype, $app) : void + { + // Set empty plugins item + $this->flextype['registry']->set('plugins', []); + + // Set locale + $locale = $this->flextype['registry']->get('flextype.locale'); + + // Get plugins list + $plugins_list = $this->getPluginsList(); + + // Get plugins Cache ID + $plugins_cache_id = $this->getPluginsCacheID($plugins_list); + + // If Plugins List isnt empty then continue + if (! is_array($plugins_list) || count($plugins_list) <= 0) { + return; + } + + // Get plugins from cache or scan plugins folder and create new plugins cache item + if ($this->flextype['cache']->contains($plugins_cache_id)) { + $this->flextype['registry']->set('plugins', $this->flextype['cache']->fetch($plugins_cache_id)); + + if ($this->flextype['cache']->contains($locale)) { + I18n::add($this->flextype['cache']->fetch($locale), $locale); + } else { + // Save plugins dictionary + $dictionary = $this->getPluginsDictionary($plugins_list, $locale); + $this->flextype['cache']->save($locale, $dictionary[$locale]); + } + } else { + // Init plugin configs + $plugins = []; + $plugin_settings = []; + $plugin_manifest = []; + $default_plugin_settings = []; + $site_plugin_settings = []; + $default_plugin_manifest = []; + + // Go through... + foreach ($plugins_list as $plugin) { + + // Set plugin settings directory + $site_plugin_settings_dir = PATH['config']['site'] . '/plugins/' . $plugin['dirname']; + + // Set default plugin settings and manifest files + $default_plugin_settings_file = PATH['plugins'] . '/' . $plugin['dirname'] . '/settings.yaml'; + $default_plugin_manifest_file = PATH['plugins'] . '/' . $plugin['dirname'] . '/plugin.yaml'; + + // Set site plugin settings file + $site_plugin_settings_file = PATH['config']['site'] . '/plugins/' . $plugin['dirname'] . '/settings.yaml'; + + // Create site plugin settings directory + ! Filesystem::has($site_plugin_settings_dir) and Filesystem::createDir($site_plugin_settings_dir); + + // Check if default plugin settings file exists + if (! Filesystem::has($default_plugin_settings_file)) { + throw new RuntimeException('Load ' . $plugin['dirname'] . ' plugin settings - failed!'); + } + + // Get default plugin settings content + $default_plugin_settings_file_content = Filesystem::read($default_plugin_settings_file); + $default_plugin_settings = $this->flextype['parser']->decode($default_plugin_settings_file_content, 'yaml'); + + // Create site plugin settings file + ! Filesystem::has($site_plugin_settings_file) and Filesystem::write($site_plugin_settings_file, $default_plugin_settings_file_content); + + // Get site plugin settings content + $site_plugin_settings_file_content = Filesystem::read($site_plugin_settings_file); + if (trim($site_plugin_settings_file_content) === '') { + $site_plugin_settings = []; + } else { + $site_plugin_settings = $this->flextype['parser']->decode($site_plugin_settings_file_content, 'yaml'); + } + + // Check if default plugin manifest file exists + if (! Filesystem::has($default_plugin_manifest_file)) { + RuntimeException('Load ' . $plugin['dirname'] . ' plugin manifest - failed!'); + } + + // Get default plugin manifest content + $default_plugin_manifest_file_content = Filesystem::read($default_plugin_manifest_file); + $default_plugin_manifest = $this->flextype['parser']->decode($default_plugin_manifest_file_content, 'yaml'); + + // Merge plugin settings and manifest data + $plugins[$plugin['dirname']]['manifest'] = $default_plugin_manifest; + $plugins[$plugin['dirname']]['settings'] = array_replace_recursive($default_plugin_settings, $site_plugin_settings); + + // Check if isset plugin priority + if (isset($plugins[$plugin['dirname']]['settings']['priority'])) { + continue; + } + + // Set default plugin priority = 0 + $plugins[$plugin['dirname']]['settings']['priority'] = 0; + } + + // Sort plugins list by priority. + $plugins = Arr::sort($plugins, 'priority', 'DESC'); + + // Save plugins list + $this->flextype['registry']->set('plugins', $plugins); + $this->flextype['cache']->save($plugins_cache_id, $plugins); + + // Save plugins dictionary + $dictionary = $this->getPluginsDictionary($plugins_list, $locale); + $this->flextype['cache']->save($locale, $dictionary[$locale]); + } + + $this->includeEnabledPlugins($flextype, $app); + + $this->flextype['emitter']->emit('onPluginsInitialized'); + } + + /** + * Get plugins dictionary + * + * @param array $plugins_list Plugins list + * + * @access protected + */ + private function getPluginsDictionary(array $plugins_list, string $locale) : array + { + foreach ($plugins_list as $plugin) { + $language_file = PATH['plugins'] . '/' . $plugin['dirname'] . '/lang/' . $locale . '.yaml'; + + if (! Filesystem::has($language_file)) { + continue; + } + + if (($content = Filesystem::read($language_file)) === false) { + throw new RuntimeException('Load file: ' . $language_file . ' - failed!'); + } + + I18n::add($this->flextype['parser']->decode($content, 'yaml'), $locale); + } + + return I18n::$dictionary; + } + + /** + * Get plugins Cache ID + * + * @param array $plugins_list Plugins list + * + * @access protected + */ + private function getPluginsCacheID(array $plugins_list) : string + { + // Plugin cache id + $_plugins_cache_id = ''; + + // Go through... + if (is_array($plugins_list) && count($plugins_list) > 0) { + foreach ($plugins_list as $plugin) { + $default_plugin_settings_file = PATH['plugins'] . '/' . $plugin['dirname'] . '/settings.yaml'; + $default_plugin_manifest_file = PATH['plugins'] . '/' . $plugin['dirname'] . '/plugin.yaml'; + $site_plugin_settings_file = PATH['config']['site'] . '/plugins/' . $plugin['dirname'] . '/settings.yaml'; + + $f1 = Filesystem::has($default_plugin_settings_file) ? filemtime($default_plugin_settings_file) : ''; + $f2 = Filesystem::has($default_plugin_manifest_file) ? filemtime($default_plugin_manifest_file) : ''; + $f3 = Filesystem::has($site_plugin_settings_file) ? filemtime($site_plugin_settings_file) : ''; + + $_plugins_cache_id .= $f1 . $f2 . $f3; + } + } + + // Create Unique Cache ID for Plugins + $plugins_cache_id = md5('plugins' . PATH['plugins'] . '/' . $_plugins_cache_id); + + // Return plugin cache id + return $plugins_cache_id; + } + + /** + * Get plugins list + * + * @return array + * + * @access public + */ + public function getPluginsList() : array + { + // Get Plugins List + $plugins_list = []; + + foreach (Filesystem::listContents(PATH['plugins']) as $plugin) { + if ($plugin['type'] !== 'dir') { + continue; + } + + $plugins_list[] = $plugin; + } + + return $plugins_list; + } + + /** + * Include enabled plugins + * + * @return void + * + * @access protected + */ + private function includeEnabledPlugins($flextype, $app) : void + { + if (! is_array($this->flextype['registry']->get('plugins')) || count($this->flextype['registry']->get('plugins')) <= 0) { + return; + } + + foreach ($this->flextype['registry']->get('plugins') as $plugin_name => $plugin) { + if (! $this->flextype['registry']->get('plugins.' . $plugin_name . '.settings.enabled')) { + continue; + } + + include_once PATH['plugins'] . '/' . $plugin_name . '/bootstrap.php'; + } + } +} diff --git a/src/flextype/dependencies.php b/src/flextype/dependencies.php new file mode 100644 index 00000000..07b6bdd2 --- /dev/null +++ b/src/flextype/dependencies.php @@ -0,0 +1,190 @@ +pushHandler(new StreamHandler(PATH['logs'] . '/' . date('Y-m-d') . '.log')); + return $logger; +}; + +/** + * Add emitter service to Flextype container + */ +$flextype['emitter'] = static function ($container) { + return new Emitter(); +}; + +/** + * Add slugify service to Flextype container + */ +$flextype['slugify'] = static function ($container) { + return new Slugify([ + 'separator' => $container['registry']->get('flextype.slugify.separator'), + 'lowercase' => $container['registry']->get('flextype.slugify.lowercase'), + 'trim' => $container['registry']->get('flextype.slugify.trim'), + 'regexp' => $container['registry']->get('flextype.slugify.regexp'), + 'lowercase_after_regexp' => $container['registry']->get('flextype.slugify.lowercase_after_regexp'), + 'strip_tags' => $container['registry']->get('flextype.slugify.strip_tags'), + ]); +}; + +/** + * Adds the cache adapter to the Flextype container + */ +$flextype['cache_adapter'] = static function ($container) use ($flextype) { + $driver_name = $container['registry']->get('flextype.cache.driver'); + + if (! $driver_name || $driver_name === 'auto') { + if (extension_loaded('apcu')) { + $driver_name = 'apcu'; + } elseif (extension_loaded('wincache')) { + $driver_name = 'wincache'; + } else { + $driver_name = 'filesystem'; + } + } + + $class = ucfirst($driver_name); + $adapter = "Flextype\\Cache\\{$class}Adapter"; + + return new $adapter($flextype); +}; + +/** + * Add cache service to Flextype container + */ +$flextype['cache'] = static function ($container) use ($flextype) { + return new Cache($flextype); +}; + +/** + * Add parser service to Flextype container + */ +$flextype['parser'] = static function ($container) use ($flextype) { + return new Parser($flextype); +}; + +/** + * Add images service to Flextype container + */ +$flextype['images'] = static function ($container) { + // Get images settings + $imagesSettings = $container->get('settings')['images']; + + // Set source filesystem + $source = new Filesystem( + new Local(PATH['uploads'] . '/entries/') + ); + + // Set cache filesystem + $cache = new Filesystem( + new Local(PATH['cache'] . '/glide') + ); + + // Set watermarks filesystem + $watermarks = new Filesystem( + new Local(PATH['site'] . '/watermarks') + ); + + // Set image manager + $imageManager = new ImageManager($imagesSettings); + + // Set manipulators + $manipulators = [ + new Orientation(), + new Crop(), + new Size(2000*2000), + new Brightness(), + new Contrast(), + new Gamma(), + new Sharpen(), + new Filter(), + new Blur(), + new Pixelate(), + new Watermark($watermarks), + new Background(), + new Border(), + new Encode(), + ]; + + // Set API + $api = new Api($imageManager, $manipulators); + + // Setup Glide server + return ServerFactory::create([ + 'source' => $source, + 'cache' => $cache, + 'api' => $api, + 'response' => new SlimResponseFactory(), + ]); +}; + +/** + * Add entries service to Flextype container + */ +$flextype['entries'] = static function ($container) { + return new Entries($container); +}; + +/** + * Add plugins service to Flextype container + */ +$flextype['plugins'] = static function ($container) use ($flextype, $app) { + return new Plugins($flextype, $app); +}; diff --git a/src/flextype/endpoints/delivery/entries.php b/src/flextype/endpoints/delivery/entries.php new file mode 100644 index 00000000..3a211ef3 --- /dev/null +++ b/src/flextype/endpoints/delivery/entries.php @@ -0,0 +1,79 @@ +getQueryParams()['token'] . '/token.yaml'); +} + +/** + * Fetch entry(entries) + * + * endpoint: /api/delivery/entries + */ +$app->get('/api/delivery/entries', function (Request $request, Response $response) use ($flextype) { + + // Get Query Params + $query = $request->getQueryParams(); + + // Set variables + $id = $query['id']; + $args = isset($query['args']) ? $query['args'] : null; + + if ($flextype['registry']->get('flextype.api.entries.enabled')) { + + // Validate delivery token + if (validate_delivery_entries_token($request, $flextype)) { + $delivery_entries_token_file_path = PATH['tokens'] . '/delivery/entries/' . $request->getQueryParams()['token'] . '/token.yaml'; + + // Set delivery token file + if ($delivery_entries_token_file_data = $flextype['parser']->decode(Filesystem::read($delivery_entries_token_file_path), 'yaml')) { + if ($delivery_entries_token_file_data['state'] == 'disabled' || + ($delivery_entries_token_file_data['limit_calls'] != 0 && $delivery_entries_token_file_data['calls'] >= $delivery_entries_token_file_data['limit_calls'])) { + return $response->withJson(["detail" => "Incorrect authentication credentials."], 401); + } else { + // Fetch entry + $data = $flextype['entries']->fetch($id, $args); + + // Set response code + $response_code = (count($data) > 0) ? 200 : 404 ; + + // Update calls counter + Filesystem::write($delivery_entries_token_file_path, $flextype['parser']->encode(array_replace_recursive($delivery_entries_token_file_data, ['calls' => $delivery_entries_token_file_data['calls'] + 1]), 'yaml')); + + // Return response + return $response + ->withJson($data, $response_code) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } +}); diff --git a/src/flextype/endpoints/delivery/images.php b/src/flextype/endpoints/delivery/images.php new file mode 100644 index 00000000..78e786f6 --- /dev/null +++ b/src/flextype/endpoints/delivery/images.php @@ -0,0 +1,78 @@ +getQueryParams()['token'] . '/token.yaml'); +} + +/** + * Fetch image + * + * endpoint: /api/delivery/images + */ +$app->get('/api/delivery/images/{path:.+}', function (Request $request, Response $response, array $args) use ($flextype) { + + // Get Query Params + $query = $request->getQueryParams(); + + if ($flextype['registry']->get('flextype.api.images.enabled')) { + + // Validate delivery image token + if (validate_delivery_images_token($request, $flextype)) { + $delivery_images_token_file_path = PATH['tokens'] . '/delivery/images/' . $request->getQueryParams()['token'] . '/token.yaml'; + + // Set delivery token file + if ($delivery_images_token_file_data = $flextype['parser']->decode(Filesystem::read($delivery_images_token_file_path), 'yaml')) { + if ($delivery_images_token_file_data['state'] == 'disabled' || + ($delivery_images_token_file_data['limit_calls'] != 0 && $delivery_images_token_file_data['calls'] >= $delivery_images_token_file_data['limit_calls'])) { + return $response->withJson(["detail" => "Incorrect authentication credentials."], 401); + } else { + + // Update calls counter + Filesystem::write($delivery_images_token_file_path, $flextype['parser']->encode(array_replace_recursive($delivery_images_token_file_data, ['calls' => $delivery_images_token_file_data['calls'] + 1]), 'yaml')); + + if (Filesystem::has(PATH['uploads'] . '/entries/' . $args['path'])) { + header('Access-Control-Allow-Origin: *'); + return $flextype['images']->getImageResponse($args['path'], $_GET); + } else { + return $response + ->withJson([], 404) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + + return $response + ->withStatus(404) + ->withHeader('Access-Control-Allow-Origin', '*'); + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } +}); diff --git a/src/flextype/endpoints/delivery/registry.php b/src/flextype/endpoints/delivery/registry.php new file mode 100644 index 00000000..e1510e76 --- /dev/null +++ b/src/flextype/endpoints/delivery/registry.php @@ -0,0 +1,75 @@ +getQueryParams()['token'] . '/token.yaml'); +} + +/** + * Fetch registry + * + * endpoint: /api/delivery/registry + */ +$app->get('/api/delivery/registry', function (Request $request, Response $response) use ($flextype) { + + // Get Query Params + $query = $request->getQueryParams(); + + // Set variables + $id = $query['id']; + + if ($flextype['registry']->get('flextype.api.registry.enabled')) { + + // Validate delivery token + if (validate_delivery_registry_token($request, $flextype)) { + $delivery_registry_token_file_path = PATH['tokens'] . '/delivery/registry/' . $request->getQueryParams()['token'] . '/token.yaml'; + + // Set delivery token file + if ($delivery_registry_token_file_data = $flextype['parser']->decode(Filesystem::read($delivery_registry_token_file_path), 'yaml')) { + if ($delivery_registry_token_file_data['state'] == 'disabled' || + ($delivery_registry_token_file_data['limit_calls'] != 0 && $delivery_registry_token_file_data['calls'] >= $delivery_registry_token_file_data['limit_calls'])) { + return $response->withJson(["detail" => "Incorrect authentication credentials."], 401); + } else { + // Fetch registry + $data = $flextype['registry']->get($id); + + // Update calls counter + Filesystem::write($delivery_registry_token_file_path, $flextype['parser']->encode(array_replace_recursive($delivery_registry_token_file_data, ['calls' => $delivery_registry_token_file_data['calls'] + 1]), 'yaml')); + + // Return response + return $response + ->withJson($data, 200) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } + } else { + return $response + ->withJson(["detail" => "Incorrect authentication credentials."], 401) + ->withHeader('Access-Control-Allow-Origin', '*'); + } +}); diff --git a/src/flextype/middlewares.php b/src/flextype/middlewares.php new file mode 100644 index 00000000..e64f22a4 --- /dev/null +++ b/src/flextype/middlewares.php @@ -0,0 +1,10 @@ +