From c1a57c64f2578f608b78794a5f3cf17aa83f2444 Mon Sep 17 00:00:00 2001 From: crispycat Date: Thu, 7 Dec 2023 14:07:15 -0500 Subject: [PATCH] initial commit --- .gitignore | 4 + README | 2 + README.md | 2 - build.sh | 23 ++ delete.list | 8 + .../Crispage/Actions/DefaultAction.php | 33 ++ patch/core/app/assets/Asset.php | 187 +++++++++ patch/core/app/assets/AssetManager.php | 359 ++++++++++++++++++ patch/core/app/assets/class/Module.php | 167 ++++++++ patch/core/app/assets/class/Plugin.php | 146 +++++++ patch/core/app/assets/class/Role.php | 122 ++++++ patch/core/app/assets/class/User.php | 189 +++++++++ patch/core/app/extensions/CoreExtensions.php | 71 ++++ .../Crispage/Components/DefaultComponent.php | 41 ++ .../Components/ErrorInfoComponent.php | 64 ++++ .../Crispage/Components/HeadTagsComponent.php | 91 +++++ .../Crispage/Components/MessagesComponent.php | 41 ++ patch/core/templates/default/layouts/api.php | 1 + .../templates/default/layouts/default.php | 36 ++ .../actions/CrispageLite/DefaultAction.php | 34 ++ 20 files changed, 1619 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 README delete mode 100644 README.md create mode 100755 build.sh create mode 100644 delete.list create mode 100755 patch/core/actions/Crispage/Actions/DefaultAction.php create mode 100644 patch/core/app/assets/Asset.php create mode 100644 patch/core/app/assets/AssetManager.php create mode 100644 patch/core/app/assets/class/Module.php create mode 100644 patch/core/app/assets/class/Plugin.php create mode 100644 patch/core/app/assets/class/Role.php create mode 100644 patch/core/app/assets/class/User.php create mode 100644 patch/core/app/extensions/CoreExtensions.php create mode 100755 patch/core/components/Crispage/Components/DefaultComponent.php create mode 100755 patch/core/components/Crispage/Components/ErrorInfoComponent.php create mode 100755 patch/core/components/Crispage/Components/HeadTagsComponent.php create mode 100755 patch/core/components/Crispage/Components/MessagesComponent.php create mode 100644 patch/core/templates/default/layouts/api.php create mode 100644 patch/core/templates/default/layouts/default.php create mode 100755 patch/user/actions/CrispageLite/DefaultAction.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e5cd5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +cache +lite +patch/config.php diff --git a/README b/README new file mode 100644 index 0000000..f1bc20d --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +# Crispage Lite +Crispage Lite is a version of the [Crispage CMS](https://crispy.cat/software/crispage) with many features removed, intended for the development of other applications based on the framework. diff --git a/README.md b/README.md deleted file mode 100644 index 0461540..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# crispage-lite - diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..ccdb282 --- /dev/null +++ b/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash +rm -rf build; + +if [[ -d cache ]]; then + cp -r cache build; +else + git clone https://git.calitabby.com/crispage/crispage.git build; + cp -r build cache; +fi; + +while IFS= read -r line; do + if [[ "$line" != "" ]]; then + rm -rf build/"$line"; + fi; +done < delete.list; + +cp -r patch/* build; + +read -p "Copy build folder? [y/n]" copy +if [[ "$copy" = "y" ]]; then + rm -rf lite; + cp -r build lite; +fi; diff --git a/delete.list b/delete.list new file mode 100644 index 0000000..d5e7f29 --- /dev/null +++ b/delete.list @@ -0,0 +1,8 @@ +.git +_crispage +core/app/assets +core/actions +core/components +core/templates +media/assets +media/uploads diff --git a/patch/core/actions/Crispage/Actions/DefaultAction.php b/patch/core/actions/Crispage/Actions/DefaultAction.php new file mode 100755 index 0000000..7095feb --- /dev/null +++ b/patch/core/actions/Crispage/Actions/DefaultAction.php @@ -0,0 +1,33 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Actions; + + defined("ROOT") or die(); + + class DefaultAction extends \Crispage\Response\Action { + public static function getExtensionInfo(): array { + return [ + "id" => "crispage.actions.default", + "version" => VERSION + ]; + } + + public function __construct(\Crispage $app, array $data) { + parent::__construct($app, $data); + } + + public function run(): void { + // do nothing + } + } +?> diff --git a/patch/core/app/assets/Asset.php b/patch/core/app/assets/Asset.php new file mode 100644 index 0000000..ffc3c4a --- /dev/null +++ b/patch/core/app/assets/Asset.php @@ -0,0 +1,187 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage; + + defined("ROOT") or die(); + + use \Crispage\Auth\CorePermissions; + + class Asset { + public static function getNames(): array { + return ["Asset", "Assets"]; + } + + public static function getPermissions(): array { + return [ + "create" => CorePermissions::NEVER, + "modify" => CorePermissions::NEVER, + "modify_own"=> CorePermissions::NEVER, + "delete" => CorePermissions::NEVER, + "delete_own"=> CorePermissions::NEVER + ]; + } + + public static function getRoutingInfo(): array { + return [ + "action" => null, + "data" => [] + ]; + } + + public static function getSortingInfo(): array { + return [ + "field" => "mtime", + "desc" => true + ]; + } + + public static function getListColumns(): array { + global $Crispage; + return [ + "id" => ["label" => "{%ID}"], + "slug" => [ + "label" => "{%SLUG}", + "filter" => "\\Crispage\\Utils\\TextUtils::codeify" + ], + "ctime" => [ + "label" => "{%CREATED}", + "filter" => [$Crispage->i18n, "datetime"] + ], + "mtime" => [ + "label" => "{%MODIFIED}", + "filter" => [$Crispage->i18n, "datetime"] + ] + ]; + } + + public static function getListActions(): array { + return [ + [ + "route" => "assets/editor", + "label" => "{%CREATE_NEW}", + "color" => "success" + ] + ]; + } + + public static function getListItemActions(): array { + return [ + [ + "route" => "assets/editor", + "label" => "{%EDIT}", + "color" => "primary", + "fields" => [ + "asset_id" => "id" + ] + ], + [ + "route" => "assets/delete", + "label" => "{%DELETE}", + "color" => "danger", + "fields" => [ + "asset_id" => "id" + ] + ] + ]; + } + + public static function getEditorFields(): array { + return [ + "slug" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%SLUG}:" + ], + "filter" => "strval" + ] + ]; + } + + public static function getOptionFields(): array { + return []; + } + + public static function slug(string $text = null): string { + if (empty($text)) return strval(time()); + $slug = strtolower($text); + $slug = preg_replace("/[^a-z\\d\\-_]+/", "-", $slug); + $slug = preg_replace("/-{2,}/", "-", $slug); + $slug = trim($slug, "-"); + if (empty($slug)) return strval(time()); + return $slug; + } + + public ?string $_table; + public int $id; + public string $slug; + public int $ctime; + public int $mtime; + public string $options; + protected array $_options_array; + + protected function __construct(array $props) { + $this->_table = $props["_table"] ?? null; + $this->id = $props["id"] ?? 0; + $this->slug = $props["slug"] ?? ""; + $this->ctime = $props["ctime"] ?? time(); + $this->mtime = $props["mtime"] ?? time(); + $this->options = $props["options"] ?? "[]"; + $this->_options_array = json_decode($this->options, true); + } + + public function toArray(): array { + $arr = []; + foreach ($this as $k => $v) { + if ($k[0] == "_") continue; + if (is_bool($v)) $v = intval($v); + $arr[$k] = $v; + } + return $arr; + } + + public function getOption(string $key, $default = null) { + return $this->_options_array[$key] ?? $default; + } + + public function getOptions(): array { + return $this->_options_array; + } + + public function setOption(string $key, $value): void { + $this->_options_array[$key] = $value; + $this->options = json_encode($this->_options_array); + } + + public function setOptions(array $data): void { + $this->_options_array = array_merge($this->_options_array, $data); + $this->options = json_encode($this->_options_array); + } + + public function isTemporary(): bool { + return $this->_table === null; + } + + public function getOwnerId(): int { + return 0; + } + + public function getName(): string { + return $this->slug; + } + + public function getParentId(): int { + return 0; + } + } +?> diff --git a/patch/core/app/assets/AssetManager.php b/patch/core/app/assets/AssetManager.php new file mode 100644 index 0000000..bc158d1 --- /dev/null +++ b/patch/core/app/assets/AssetManager.php @@ -0,0 +1,359 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage; + + defined("ROOT") or die(); + + require_once ROOT . "/core/app/assets/Asset.php"; + + + require_once ROOT . "/core/app/assets/class/Module.php"; + require_once ROOT . "/core/app/assets/class/Plugin.php"; + require_once ROOT . "/core/app/assets/class/Role.php"; + require_once ROOT . "/core/app/assets/class/User.php"; + + use \Crispage\Events\Receiver; + use \Crispage\Text\ShortcodeRenderer; + + class AssetManager { + public \Crispage $app; + private array $registeredClasses = []; + + public function __construct(\Crispage $app) { + $this->app = $app; + + // Register default assets + $this->registerClass("\\Crispage\\Assets\\Module"); + $this->registerClass("\\Crispage\\Assets\\Plugin"); + $this->registerClass("\\Crispage\\Assets\\Role"); + $this->registerClass("\\Crispage\\Assets\\User"); + + // Automatically update routes + $this->app->dispatcher->register( + new Receiver( + "crispage.assets.update_routes_created", + "crispage.assets.asset_created", 0, + function (Asset $asset): void { + $this->app->router->createContentRoute($asset); + $this->app->router->createAdditionalContentRoutes($asset); + } + ) + ); + + $this->app->dispatcher->register( + new Receiver( + "crispage.assets.update_routes_set", + "crispage.assets.asset_set", 0, + function (Asset $asset): void { + $this->app->router->deleteContentRoutes($asset); + $this->app->router->createContentRoute($asset); + $this->app->router->createAdditionalContentRoutes($asset); + } + ) + ); + + $this->app->dispatcher->register( + new Receiver( + "crispage.assets.update_routes_deleted", + "crispage.assets.asset_deleted", 0, + function (Asset $asset): void { + $this->app->router->deleteContentRoutes($asset); + } + ) + ); + + $this->app->shortcodes->mapShortcode(new ShortcodeRenderer( + "asset", fn(array $args): string => $this->app->resolveUri("asset://" . ($args["id"] ?? 0)) + )); + } + + public function getRegisteredClasses(): array { + return $this->registeredClasses; + } + + public function registerClass(string $classname): void { + if (!in_array($classname, $this->registeredClasses)) + $this->registeredClasses[] = $classname; + } + + public function unregisterClass(string $classname): void { + $this->registeredClasses = array_filter( + $this->registeredClasses, + fn(string $cn): bool => $cn != $classname + ); + } + + public function getTable(string $classname): string { + return "assets_" . substr(md5($classname), 0, 8) . @end(explode("\\", $classname)); + } + + public function getNewID(): int { + // Get id from table + $res = $this->app->database->select("system", ["next_asset_id"]); + $id = $res->fetchColumn(); + // Increment id + $this->app->database->increment("system", "next_asset_id"); + // Trigger event + $this->app->dispatcher->trigger("crispage.assets.id_incremented", $id + 1); + // Return id + return $id; + } + + public function createTable(string $classname): bool { + // Error if class is not an asset + if (!is_a($classname, "\\Crispage\\Asset", true)) return false; + + // Get table name + $table = $this->getTable($classname); + + // Create ReflectionClass + $rc = new \ReflectionClass($classname); + + // Build array of columns + $cols = [ + "id" => [$this->app->database::TYPE_INT, false], + "slug" => [$this->app->database::TYPE_STRING, false] + ]; + foreach ($rc->getProperties() as $prop) { + $name = $prop->getName(); + // If name starts with _, or is "id" or "slug", skip + if ($name[0] == "_" || $name == "id" || $name == "slug") + continue; + $type = $prop->getType(); + // If type is not \ReflectionNamedType, skip + if (!is_a($type, "\\ReflectionNamedType")) continue; + // Determine column type from property type + switch ($type->getName()) { + case "bool": + $ctype = $this->app->database::TYPE_BOOL; + break; + case "int": + $ctype = $this->app->database::TYPE_INT; + break; + case "float": + $ctype = $this->app->database::TYPE_FLOAT; + break; + default: + $ctype = $this->app->database::TYPE_STRING; + } + // Add to columns array + $cols[$name] = [$ctype, $type->allowsNull()]; + } + + // Create the table + $this->app->database->createTable($table, $cols); + $this->app->database->addPrimaryKey($table, "id"); + $this->app->database->addForeignKey($table, "id", "assets", "id"); + $this->app->database->addUnique($table, "slug"); + + // Trigger event + $this->app->dispatcher->trigger( + "crispage.assets.table_created", $classname, $table + ); + + return true; + } + + private function constructAsset(string $table, string $class, int $id): ?Asset { + // Get data from table + $res = $this->app->database->select($table, null, ["id" => $id]); + $row = $res->fetch(); + // Check that data exists and class exists + if (!$row) return null; + if (!class_exists($class)) return null; + // Return asset from data + $row["_table"] = $table; + return new $class($row); + } + + public function get(int $id): ?Asset { + // Get asset info from table + $res = $this->app->database->select("assets", null, ["id" => $id]); + $row = $res->fetch(); + // Check that data exists + if (!$row) return null; + // Return asset + return $this->constructAsset( + $row["tablename"], $row["classname"], $id + ); + } + + public function getBySlug(string $classname, string $slug): ?Asset { + // Get asset info from table + $res = $this->app->database->select( + "assets", null, ["classname" => $classname, "slug" => $slug] + ); + $row = $res->fetch(); + // Check that data exists + if (!$row) return null; + // Return asset + return $this->constructAsset( + $row["tablename"], $row["classname"], $row["id"] + ); + } + + public function getAll(string $classname = null): \Generator { + // Get asset info from table + $res = $this->app->database->select( + "assets", null, + ($classname) ? ["classname" => $classname] : [] + ); + // Walk through and yield assets + while (($row = $res->fetch()) !== false) { + yield $this->constructAsset( + $row["tablename"], $row["classname"], $row["id"] + ); + } + } + + public function getAllFiltered( + string $classname, array $filters = [], string $sortby = null, + bool $desc = false + ): \Generator { + if (!class_exists($classname)) return; + // Get table name + $table = $this->getTable($classname); + // Get ids from table + $res = $this->app->database->select( + $table, ["id"], $filters, $sortby, $desc + ); + // Walk through and yield assets + while (($row = $res->fetch()) !== false) + yield $this->constructAsset($table, $classname, $row["id"]); + } + + public function countAssets(string $classname = null): int { + // Get count + $res = $this->app->database->select( + "assets", ["COUNT(*)"], + ($classname) ? ["classname" => $classname] : [] + ); + return $res->fetchColumn(); + } + + public function create( + string $classname, array $data, bool $temp = false + ): Asset { + // Error if class is not an asset + if ( + !class_exists($classname) || + !is_a($classname, "\\Crispage\\Asset", true) + ) { + $this->app->handleException( + new CrispageException("$classname is not an Asset", 500) + ); + } + + // Create asset + $asset = new $classname($data); + $asset->id = $this->getNewID(); + + if (!$temp) { + // Get table name + $asset->_table = $this->getTable($classname); + + // Insert into tables + $this->app->database->insert("assets", [ + "id" => $asset->id, + "classname" => "\\" . $asset::class, + "tablename" => $asset->_table, + "slug" => $asset->slug + ]); + $this->app->database->insert( + $asset->_table, $asset->toArray() + ); + + // Trigger event + $this->app->dispatcher->trigger( + "crispage.assets.asset_created", $asset + ); + } else { + $this->app->dispatcher->trigger( + "crispage.assets.temporary_asset_created", $asset + ); + } + + return $asset; + } + + public function set(Asset $asset): void { + if ($asset->isTemporary()) return; + $asset->mtime = time(); + // Update tables + $this->app->database->update( + $asset->_table, $asset->toArray(), ["id" => $asset->id] + ); + $this->app->database->update( + "assets", ["slug" => $asset->slug], ["id" => $asset->id] + ); + // Trigger event + $this->app->dispatcher->trigger( + "crispage.assets.asset_set", $asset + ); + } + + public function delete(Asset $asset): void { + if ($asset->isTemporary) return; + // Delete from tables + $this->app->database->delete($asset->_table, ["id" => $asset->id]); + $this->app->database->delete("assets", ["id" => $asset->id]); + // Trigger event + $this->app->dispatcher->trigger( + "crispage.assets.asset_deleted", $asset + ); + } + + public function makePermanent(Asset $asset): Asset { + if (!$asset->isTemporary()) return $asset; + + // Get table name + $asset->_table = $this->getTable("\\" . $asset::class); + + // Insert into tables + $this->app->database->insert("assets", [ + "id" => $asset->id, + "classname" => "\\" . $asset::class, + "tablename" => $asset->_table, + "slug" => $asset->slug + ]); + $this->app->database->insert( + $asset->_table, $asset->toArray() + ); + + // Trigger event + $this->app->dispatcher->trigger( + "crispage.assets.asset_created", $asset + ); + + return $asset; + } + + public function getURI(Asset $asset, int $max = 15): string { + $uri = ""; + $l = 0; + while ($l < $max) { + $uri = "/" . $asset->slug . $uri; + $asset = $this->get($asset->getParentId()); + if (!$asset) return $uri; + $l++; + } + return $uri; + } + + public function assetNameFromID(int $id, string $none = "B_NONE"): string { + $asset = $this->get($id); + if (!$asset) return $this->app->i18n->getTranslation($none); + return $asset->getName(); + } + } +?> diff --git a/patch/core/app/assets/class/Module.php b/patch/core/app/assets/class/Module.php new file mode 100644 index 0000000..81c9268 --- /dev/null +++ b/patch/core/app/assets/class/Module.php @@ -0,0 +1,167 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Assets; + + defined("ROOT") or die(); + + use \Crispage\Auth\CorePermissions; + + class Module extends \Crispage\Asset { + public static function getNames(): array { + return ["{%MODULE}", "{%MODULES}"]; + } + + public static function getPermissions(): array { + return [ + "create" => CorePermissions::MODIFY_LAYOUT, + "modify" => CorePermissions::MODIFY_LAYOUT, + "modify_own"=> CorePermissions::MODIFY_LAYOUT, + "delete" => CorePermissions::MODIFY_LAYOUT, + "delete_own"=> CorePermissions::MODIFY_LAYOUT + ]; + } + + public static function getSortingInfo(): array { + return [ + "field" => "location", + "desc" => false + ]; + } + + public static function getListColumns(): array { + global $Crispage; + return [ + "id" => ["label" => "{%ID}"], + "slug" => [ + "label" => "{%SLUG}", + "filter" => "\\Crispage\\Utils\\TextUtils::codeify" + ], + "component" => [ + "label" => "{%TYPE}", + "filter" => "\\Crispage\\Utils\\TextUtils::codeify" + ], + "location" => [ + "label" => "{%LOCATION}", + "filter" => "htmlentities" + ], + "position" => [ + "label" => "{%POSITION}", + "filter" => null + ], + "enabled" => [ + "label" => "{%STATE}", + "filter" => function(bool $s): string { + global $Crispage; + return $Crispage->i18n->translate( + ($s) ? "{%ENABLED}" : "{%DISABLED}" + ); + } + ], + "mtime" => [ + "label" => "{%MODIFIED}", + "filter" => [$Crispage->i18n, "datetime"] + ] + ]; + } + + public static function getListActions(): array { + return [ + [ + "route" => "module_create", + "label" => "{%CREATE_NEW}", + "color" => "success" + ] + ]; + } + + public static function getEditorFields(): array { + return [ + "enabled" => [ + "column" => 0, + "component" => "\\Crispage\\Components\\Fields\\BooleanFieldComponent", + "com_data" => [ + "label" => "{%ENABLED}:" + ], + "filter" => "boolval" + ], + "slug" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%SLUG}:", + "attrs" => ["placeholder" => "automatic"] + ], + "filter" => "strval" + ], + "location" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%LOCATION}:", + "attrs" => ["required"] + ], + "filter" => "strval" + ], + "position" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\NumberFieldComponent", + "com_data" => [ + "label" => "{%POSITION}:", + "attrs" => ["required"] + ], + "filter" => "intval" + ] + ]; + } + + public static function getOptionFields(): array { + return [ + "config" => [ + "component" => "\\Crispage\\Components\\Fields\\ModuleConfigFieldComponent", + "com_data" => [], + "filter" => function($data): array { + global $Crispage; + if (!is_array($data)) return []; + $asset = $Crispage->assets->get( + intval($Crispage->request->params["asset_id"]) ?? 0 + ); + + $Crispage->loadClass($asset->component); + + $ndata = []; + foreach ($asset->component::getModuleFields() as $name => $field) + $ndata[$name] = $field["filter"]($data[$name] ?? null); + return $ndata; + } + ] + ]; + } + + public string $component; + public string $location; + public int $position; + public bool $enabled; + + public function __construct(array $props) { + parent::__construct($props); + $this->component = $props["component"] ?? "\\Crispage\\Components\\DefaultComponent"; + $this->location = $props["location"] ?? "default"; + $this->position = $props["position"] ?? 0; + $this->enabled = $props["enabled"] ?? false; + } + + public function config(string $key, $default = null) { + return $this->_options_array["config"][$key] ?? $default; + } + } +?> diff --git a/patch/core/app/assets/class/Plugin.php b/patch/core/app/assets/class/Plugin.php new file mode 100644 index 0000000..ef18206 --- /dev/null +++ b/patch/core/app/assets/class/Plugin.php @@ -0,0 +1,146 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Assets; + + defined("ROOT") or die(); + + use \Crispage\Auth\CorePermissions; + + class Plugin extends \Crispage\Asset { + public static function getNames(): array { + return ["{%PLUGIN}", "{%PLUGINS}"]; + } + + public static function getPermissions(): array { + return [ + "create" => CorePermissions::NEVER, + "modify" => CorePermissions::MODIFY_SETTINGS, + "modify_own"=> CorePermissions::MODIFY_SETTINGS, + "delete" => CorePermissions::MODIFY_SETTINGS, + "delete_own"=> CorePermissions::MODIFY_SETTINGS + ]; + } + + public static function getSortingInfo(): array { + return [ + "field" => "priority", + "desc" => false + ]; + } + + public static function getListColumns(): array { + global $Crispage; + return [ + "id" => ["label" => "{%ID}"], + "runnable" => [ + "label" => "{%TYPE}", + "filter" => "\\Crispage\\Utils\\TextUtils::codeify" + ], + "priority" => [ + "label" => "{%PRIORITY}", + "filter" => null + ], + "enabled" => [ + "label" => "{%STATE}", + "filter" => function(bool $s): string { + global $Crispage; + return $Crispage->i18n->translate( + ($s) ? "{%ENABLED}" : "{%DISABLED}" + ); + } + ], + "mtime" => [ + "label" => "{%MODIFIED}", + "filter" => [$Crispage->i18n, "datetime"] + ] + ]; + } + + public static function getListActions(): array { + return []; + } + + public static function getListItemActions(): array { + return [ + [ + "route" => "assets/editor", + "label" => "{%EDIT}", + "color" => "primary", + "fields" => [ + "asset_id" => "id" + ] + ] + ]; + } + + public static function getEditorFields(): array { + return [ + "enabled" => [ + "column" => 0, + "component" => "\\Crispage\\Components\\Fields\\BooleanFieldComponent", + "com_data" => [ + "label" => "{%ENABLED}:" + ], + "filter" => "boolval" + ], + "priority" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\NumberFieldComponent", + "com_data" => [ + "label" => "{%PRIORITY}:", + "attrs" => ["required"] + ], + "filter" => "intval" + ] + ]; + } + + public static function getOptionFields(): array { + return [ + "config" => [ + "component" => "\\Crispage\\Components\\Fields\\PluginConfigFieldComponent", + "com_data" => [], + "filter" => function($data): array { + global $Crispage; + if (!is_array($data)) return []; + $asset = $Crispage->assets->get( + intval($Crispage->request->params["asset_id"]) ?? 0 + ); + + $Crispage->loadClass($asset->component); + + $ndata = []; + foreach ($asset->component::getPluginFields() as $name => $field) + $ndata[$name] = $field["filter"]($data[$name] ?? null); + return $ndata; + } + ] + ]; + } + + public string $runnable; + public int $priority; + public bool $enabled; + + public function __construct(array $props) { + parent::__construct($props); + $this->runnable = $props["runnable"] ?? "\\Crispage\\Plugins\\DefaultPluginRunnable"; + $this->priority = $props["priority"] ?? 0; + $this->enabled = $props["enabled"] ?? false; + } + + public function config(string $key, $default = null) { + return $this->_options_array["config"][$key] ?? $default; + } + } +?> diff --git a/patch/core/app/assets/class/Role.php b/patch/core/app/assets/class/Role.php new file mode 100644 index 0000000..3dc57b0 --- /dev/null +++ b/patch/core/app/assets/class/Role.php @@ -0,0 +1,122 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Assets; + + defined("ROOT") or die(); + + require_once ROOT . "/core/app/assets/Asset.php"; + + use \Crispage\Auth\CorePermissions; + + class Role extends \Crispage\Asset { + public static function getNames(): array { + return ["{%ROLE}", "{%ROLES}"]; + } + + public static function getPermissions(): array { + return [ + "create" => CorePermissions::MODIFY_ROLES, + "modify" => CorePermissions::MODIFY_ROLES, + "modify_own"=> CorePermissions::MODIFY_ROLES, + "delete" => CorePermissions::MODIFY_ROLES, + "delete_own"=> CorePermissions::MODIFY_ROLES + ]; + } + + public static function getSortingInfo(): array { + return [ + "field" => "rank", + "desc" => false + ]; + } + + public static function getListColumns(): array { + global $Crispage; + return [ + "id" => ["label" => "{%ID}"], + "slug" => [ + "label" => "{%SLUG}", + "filter" => "\\Crispage\\Utils\\TextUtils::codeify" + ], + "name" => [ + "label" => "{%NAME}", + "filter" => "htmlentities" + ], + "rank" => [ + "label" => "{%RANK}", + "filter" => null + ], + "mtime" => [ + "label" => "{%MODIFIED}", + "filter" => [$Crispage->i18n, "datetime"] + ] + ]; + } + + public static function getEditorFields(): array { + return [ + "name" => [ + "column" => 0, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%NAME}:", + "attrs" => ["required"] + ], + "filter" => "strval" + ], + "permissions" => [ + "column" => 0, + "component" => "\\Crispage\\Components\\Fields\\PermissionsFieldComponent", + "com_data" => [ + "label" => "{%PERMISSIONS}:", + "attrs" => ["required"] + ], + "filter" => "intval" + ], + "slug" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%SLUG}:", + "attrs" => ["placeholder" => "automatic"] + ], + "filter" => "strval" + ], + "rank" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\NumberFieldComponent", + "com_data" => [ + "label" => "{%RANK}:", + "attrs" => ["required"] + ], + "filter" => "intval" + ] + ]; + } + + public string $name; + public int $permissions; + public int $rank; + + public function __construct(array $props) { + parent::__construct($props); + $this->name = $props["name"] ?? ""; + $this->permissions = $props["permissions"] ?? 0; + $this->rank = $props["rank"] ?? 0; + } + + public function getName(): string { + return $this->name; + } + } +?> diff --git a/patch/core/app/assets/class/User.php b/patch/core/app/assets/class/User.php new file mode 100644 index 0000000..37a6207 --- /dev/null +++ b/patch/core/app/assets/class/User.php @@ -0,0 +1,189 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Assets; + + defined("ROOT") or die(); + + require_once ROOT . "/core/app/assets/Asset.php"; + + use \Crispage\Auth\CorePermissions; + + class User extends \Crispage\Asset { + public const UNCONFIRMED = 0; + public const CONFIRMED = 1; + public const ACTIVATED = 2; + + public static function getNames(): array { + return ["{%USER}", "{%USERS}"]; + } + + public static function getPermissions(): array { + return [ + "create" => CorePermissions::NONE, + "modify" => CorePermissions::MODIFY_OTHERS, + "modify_own"=> CorePermissions::MODIFY_SELF, + "delete" => CorePermissions::NEVER, + "delete_own"=> CorePermissions::NEVER + ]; + } + + public static function getSortingInfo(): array { + return [ + "field" => "mtime", + "desc" => true + ]; + } + + public static function getListColumns(): array { + global $Crispage; + return [ + "id" => ["label" => "{%ID}"], + "slug" => [ + "label" => "{%USERNAME}", + "filter" => "\\Crispage\\Utils\\TextUtils::codeify" + ], + "displayname" => [ + "label" => "{%DISPLAY_NAME}", + "filter" => "htmlentities" + ], + "last_login" => [ + "label" => "{%LAST_LOGIN}", + "filter" => [$Crispage->i18n, "datetime"] + ], + "state" => [ + "label" => "{%STATE}", + "filter" => function(int $s): string { + global $Crispage; + switch ($s) { + case self::CONFIRMED: + $str = "CONFIRMED"; + break; + case self::ACTIVATED: + $str = "ACTIVATED"; + break; + default: + $str = "UNCONFIRMED"; + } + return $Crispage->i18n->translate("{%$str}"); + } + ], + "mtime" => [ + "label" => "{%MODIFIED}", + "filter" => [$Crispage->i18n, "datetime"] + ] + ]; + } + + public static function getEditorFields(): array { + return [ + "displayname" => [ + "column" => 0, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%DISPLAY_NAME}:", + "attrs" => ["required"] + ], + "filter" => "strval" + ], + "email" => [ + "column" => 0, + "component" => "\\Crispage\\Components\\Fields\\TextFieldComponent", + "com_data" => [ + "label" => "{%EMAIL}:", + "attrs" => ["required"] + ], + "filter" => "strval" + ], + "role_ids" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\RoleSelectFieldComponent", + "com_data" => [ + "label" => "{%ROLES}:" + ], + "filter" => function($data): string { + if (!is_array($data)) return "[]"; + $data = array_filter($data, "ctype_digit"); + return json_encode(array_values($data)); + } + ], + "state" => [ + "column" => 1, + "component" => "\\Crispage\\Components\\Fields\\SelectFieldComponent", + "com_data" => [ + "label" => "{%STATE}:", + "options" => [ + 0 => "{%DEACTIVATED}", + 2 => "{%ACTIVATED}" + ] + ], + "filter" => "intval" + ] + ]; + } + + public string $displayname; + public string $email; + public int $last_login; + public string $role_ids; + private array $_role_ids_array; + public int $state; + + public function __construct(array $props) { + parent::__construct($props); + $this->displayname = $props["displayname"] ?? $props["slug"] ?? ""; + $this->email = $props["email"] ?? ""; + $this->last_login = $props["last_login"] ?? 0; + $this->role_ids = $props["role_ids"] ?? "[]"; + $this->_role_ids_array = json_decode($this->role_ids, true) ?? []; + $this->state = $props["state"] ?? 0; + } + + public function getRoles(): array { + return $this->_role_ids_array; + } + + public function hasRole(int $id): bool { + return in_array($id, $this->_role_ids_array); + } + + public function setRoles(int ...$ids): array { + $this->_role_ids_array = $ids; + $this->role_ids = json_encode($this->_role_ids_array); + return $this->_role_ids_array; + } + + public function addRoles(int ...$ids): array { + $this->_role_ids_array = array_unique(array_merge( + $this->_role_ids_array, $ids + )); + $this->role_ids = json_encode($this->_role_ids_array); + return $this->_role_ids_array; + } + + public function removeRoles(int ...$ids): array { + $this->_role_ids_array = array_filter( + $this->_role_ids_array, fn(int $id): bool => !in_array($id, $ids) + ); + $this->role_ids = json_encode($this->_role_ids_array); + return $this->_role_ids_array; + } + + public function getName(): string { + return $this->displayname; + } + + public function getOwnerId(): int { + return $this->id; + } + } +?> diff --git a/patch/core/app/extensions/CoreExtensions.php b/patch/core/app/extensions/CoreExtensions.php new file mode 100644 index 0000000..bc0fb41 --- /dev/null +++ b/patch/core/app/extensions/CoreExtensions.php @@ -0,0 +1,71 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Extensions; + + defined("ROOT") or die(); + + class CoreExtensions { + public static function get(): array { + return [ + /* + new Extension( + id, version, package, + classname, type, data + ), + */ + + new Extension( + "crispage.core", VERSION, "crispage.core", + "crispage.core", "package", [] + ), + + // Actions + + new Extension( + "crispage.actions.default", VERSION, "crispage.core", + "\\Crispage\\Actions\\DefaultAction", + "action", [] + ), + + + // Components + new Extension( + "crispage.components.default", VERSION, "crispage.core", + "\\Crispage\\Components\\DefaultComponent", + "component", ["is_module" => true] + ), + + + // Plugins + new Extension( + "crispage.plugins.default", VERSION, "crispage.core", + "\\Crispage\\Plugins\\DefaultPluginRunnable", + "component", [] + ), + + + // Templates + new Extension( + "crispage.templates.default", VERSION, "crispage.core", + "default", "template", [] + ), + + // Translations + new Extension( + "crispage.translations.en_us.default", VERSION, "crispage.core", + "en-US_default", "translation", [] + ) + ]; + } + } +?> diff --git a/patch/core/components/Crispage/Components/DefaultComponent.php b/patch/core/components/Crispage/Components/DefaultComponent.php new file mode 100755 index 0000000..1c7bde9 --- /dev/null +++ b/patch/core/components/Crispage/Components/DefaultComponent.php @@ -0,0 +1,41 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Components; + + defined("ROOT") or die(); + + class DefaultComponent extends \Crispage\Response\Component + implements \Crispage\Response\ModuleComponent { + public static function getExtensionInfo(): array { + return [ + "id" => "crispage.components.default", + "version" => VERSION, + "name" => "default", + "author" => "crispycat", + "description" => "The Crispage default module." + ]; + } + + public static function getModuleFields(): array { + return []; + } + + public function __construct(\Crispage $app, array $data) { + parent::__construct($app, $data); + } + + public function render(): void { + // do nothing + } + } +?> diff --git a/patch/core/components/Crispage/Components/ErrorInfoComponent.php b/patch/core/components/Crispage/Components/ErrorInfoComponent.php new file mode 100755 index 0000000..881d253 --- /dev/null +++ b/patch/core/components/Crispage/Components/ErrorInfoComponent.php @@ -0,0 +1,64 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Components; + + defined("ROOT") or die(); + + class ErrorInfoComponent extends \Crispage\Response\Component { + public const TITLES = [ + 400 => "BAD_REQUEST", + 401 => "UNAUTHORIZED", + 403 => "FORBIDDEN", + 404 => "PAGE_NOT_FOUND", + 500 => "INTERNAL_SERVER_ERROR" + ]; + + public static function getExtensionInfo(): array { + return [ + "id" => "crispage.components.errorinfo", + "version" => VERSION + ]; + } + + public function __construct(\Crispage $app, array $data) { + parent::__construct($app, $data); + } + + public function render(): void { + $exceptions = $this->data["exceptions"] ?? []; + if (empty($exceptions)) return; + + $code = $exceptions[0]->getCode(); + $message = $exceptions[0]->getMessage(); + $title = $this->app->i18n->getTranslation(self::TITLES[$code] ?? "ERROR"); + + echo "

" + . "$code $title

$message

"; + + ($this->app->i18n)("

{%AN_ERROR_HAS_OCCURRED_}

"); + + + if (!$exceptions[0]->showDebug) return; + + echo "
"; + + ($this->app->i18n)("

{%DEBUG_INFO}

\n
");
+			foreach ($exceptions as $ind => $exception) {
+				if (!$exception->showDebug ?? true) continue;
+				echo "=======[Exception $ind]=======\n$exception\n\n";
+			}
+
+			echo "
"; + } + } +?> diff --git a/patch/core/components/Crispage/Components/HeadTagsComponent.php b/patch/core/components/Crispage/Components/HeadTagsComponent.php new file mode 100755 index 0000000..9abd5c5 --- /dev/null +++ b/patch/core/components/Crispage/Components/HeadTagsComponent.php @@ -0,0 +1,91 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Components; + + defined("ROOT") or die(); + + class HeadTagsComponent extends \Crispage\Response\Component { + public static function getExtensionInfo(): array { + return [ + "id" => "crispage.components.headtags", + "version" => VERSION + ]; + } + + public function __construct(\Crispage $app, array $data) { + parent::__construct($app, $data); + } + + public function render(): void { + $blocks = $this->data["render"] ?? ["title", "metas", "links", "scripts", "styles"]; + + foreach ($blocks as $block) { + switch ($block) { + case "title": { + $title = $this->app->page->data["title"] ?? ""; + + $asset = $this->app->assets->get($this->app->route->data["asset_id"] ?? 0); + if ($asset && !$asset->getOption("show_title_suffix", true)) { + $prefix = ""; + $suffix = ""; + } + else { + $prefix = $this->app->settings->get("crispage.page.title_prefix", ""); + $suffix = $this->app->settings->get("crispage.page.title_suffix", ""); + } + echo "$prefix$title$suffix"; + break; + } + case "metas": { + foreach ($this->app->page->data["metas"] as $meta) { + if (!$meta) continue; + echo " $val) echo " $attr=\"$val\""; + echo ">"; + } + break; + } + case "links": { + foreach ($this->app->page->data["links"] as $link) { + if (!$link) continue; + echo " $val) echo " $attr=\"$val\""; + echo ">"; + } + break; + } + case "scripts": { + foreach ($this->app->page->data["scripts"] as $script) { + if (!$script) continue; + echo " $val) + if ($attr != "_inner") echo " $attr=\"$val\""; + echo ">" . ($script["_inner"] ?? "") . ""; + } + break; + } + case "styles": { + foreach ($this->app->page->data["styles"] as $style) { + if (!$style) continue; + echo " $val) + if ($attr != "_inner") echo " $attr=\"$val\""; + echo ">" . ($style["_inner"] ?? "") . ""; + } + break; + } + } + } + } + } +?> diff --git a/patch/core/components/Crispage/Components/MessagesComponent.php b/patch/core/components/Crispage/Components/MessagesComponent.php new file mode 100755 index 0000000..72ae2d7 --- /dev/null +++ b/patch/core/components/Crispage/Components/MessagesComponent.php @@ -0,0 +1,41 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace Crispage\Components; + + defined("ROOT") or die(); + + class MessagesComponent extends \Crispage\Response\Component { + public static function getExtensionInfo(): array { + return [ + "id" => "crispage.components.messages", + "version" => VERSION + ]; + } + + public function __construct(\Crispage $app, array $data) { + parent::__construct($app, $data); + } + + public function render(): void { +?> +
+ app->page->data["messages"] as $id => $msg) { ?> +
mb-2"> + +
+ +
+ diff --git a/patch/core/templates/default/layouts/api.php b/patch/core/templates/default/layouts/api.php new file mode 100644 index 0000000..5c2bd84 --- /dev/null +++ b/patch/core/templates/default/layouts/api.php @@ -0,0 +1 @@ +app->page->renderMainComponent(); ?> diff --git a/patch/core/templates/default/layouts/default.php b/patch/core/templates/default/layouts/default.php new file mode 100644 index 0000000..07da623 --- /dev/null +++ b/patch/core/templates/default/layouts/default.php @@ -0,0 +1,36 @@ +app->page->createComponent( + "\\Crispage\\Components\\HeadTagsComponent", + ["render" => ["title", "metas", "links", "scripts", "styles"]] + ); +?> + + + + + app->page->renderComponent($com_head); ?> + + + + app->page->createComponent( + "\\Crispage\\Components\\MessagesComponent", [] + ); + $this->app->page->renderComponent($com_msgs); + $this->app->page->renderMainComponent(); + ?> + + diff --git a/patch/user/actions/CrispageLite/DefaultAction.php b/patch/user/actions/CrispageLite/DefaultAction.php new file mode 100755 index 0000000..f36235b --- /dev/null +++ b/patch/user/actions/CrispageLite/DefaultAction.php @@ -0,0 +1,34 @@ + + https://crispy.cat/software/crispage + + Crispage is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + */ + + namespace CrispageLite; + + defined("ROOT") or die(); + + class DefaultAction extends \Crispage\Response\Action { + public static function getExtensionInfo(): array { + return [ + "id" => "crispagelite.defaultaction", + "version" => VERSION + ]; + } + + public function __construct(\Crispage $app, array $data) { + parent::__construct($app, $data); + } + + public function run(): void { + $this->app->page->setPersistentMessage("lite_installed", "Crispage Lite has been installed successfully."); + $this->app->page->actionFinished(); + } + } +?>