360 lines
9.9 KiB
PHP
360 lines
9.9 KiB
PHP
|
<?php
|
||
|
/*
|
||
|
Crispage CMS
|
||
|
crispycat <the@crispy.cat>
|
||
|
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();
|
||
|
}
|
||
|
}
|
||
|
?>
|