Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Composer
/vendor/

# iManager 2.0 runtime artefacts
/data/imanager.db
/data/imanager.db-wal
/data/imanager.db-shm
/data/uploads-2.0/
/data/cache/sections/

# Phase 14 migration backups
/data.bak.*
60 changes: 33 additions & 27 deletions boot.php
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
<?php

use Scriptor\Core\Scriptor;
declare(strict_types=1);

require __DIR__.'/imanager.php';
require __DIR__.'/data/settings/scriptor-config.php';
if (file_exists(__DIR__.'/data/settings/custom.scriptor-config.php')) {
$config = array_replace_recursive(
$config, include __DIR__.'/data/settings/custom.scriptor-config.php'
);
}
use Scriptor\Boot\App;
use Scriptor\Boot\ImanagerBootstrap;

require __DIR__ . '/vendor/autoload.php';

$corePath = __DIR__."/$config[admin_path]core/";
require $corePath.'scriptor.php';
require $corePath.'moduleInterface.php';
require $corePath.'module.php';
require $corePath.'page.php';
require $corePath.'pages.php';
require $corePath.'user.php';
require $corePath.'users.php';
require $corePath.'site.php';
require $corePath.'csrf.php';
App::set(ImanagerBootstrap::create(__DIR__));

spl_autoload_register(function ($pClassName) {
$basePath = 'Scriptor\Modules\\';
$pClassName = str_replace('\\', '/', str_replace($basePath, '', $pClassName));
$inclClass = __DIR__ . "/site/modules/$pClassName.php";
if (file_exists($inclClass)) {
include_once $inclClass;
}
});
/*
* Phase 14b-1 boot path:
* - vendor/autoload.php (Composer + iManager 2.0)
* - iManager container set on App::container()
* - $config loaded from data/settings/scriptor-config.php (legacy file
* format kept verbatim — themes still read it from $site->config)
*
* The legacy 1.x bootstrap (Scriptor/imanager/, editor/core/) is intentionally
* NOT loaded — it shares the Imanager\ namespace with the new vendor package
* and would collide. Sub-phases reintroduce functionality piece by piece:
* - 14b-2: BasicTheme back on the new container
* - 14c: editor modules (auth, pages, users, settings, install, profile)
* - 14f: delete Scriptor/imanager/ entirely
*/

Scriptor::build($config);
// Legacy config files guard their first line with `defined('IS_IM')`.
\defined('IS_IM') || \define('IS_IM', true);
// Legacy themes use IM_DATAPATH for theme-config files (BasicTheme in 14b-2).
\defined('IM_DATAPATH') || \define('IM_DATAPATH', __DIR__ . '/data');

/** @var array<string, mixed> $config */
require __DIR__ . '/data/settings/scriptor-config.php';
if (file_exists(__DIR__ . '/data/settings/custom.scriptor-config.php')) {
$config = array_replace_recursive(
$config,
include __DIR__ . '/data/settings/custom.scriptor-config.php',
);
}
45 changes: 45 additions & 0 deletions boot/App.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Scriptor\Boot;

use League\Container\Container;

/**
* Process-wide service-locator handle for the iManager 2.0 container.
*
* Phase 14a needs a way for legacy Scriptor code to reach the new
* container without threading it through every constructor in one PR.
* Subsequent sub-phases (14b–14c) replace `App::container()->get(...)`
* call-sites with explicit dependency injection module by module, and
* this locator becomes empty at the end of Phase 14.
*/
final class App
{
private static ?Container $container = null;

public static function set(Container $container): void
{
self::$container = $container;
}

public static function container(): Container
{
if (self::$container === null) {
throw new \RuntimeException(
'iManager container has not been booted yet. Did boot.php run?',
);
}
return self::$container;
}

public static function reset(): void
{
self::$container = null;
}

private function __construct()
{
}
}
102 changes: 102 additions & 0 deletions boot/Frontend/Page.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Scriptor\Boot\Frontend;

use Imanager\Domain\Item;

/**
* Read-only page DTO exposed to themes as `$site->page`.
*
* Adapts an `Imanager\Domain\Item` (Pages category) to the property surface
* the legacy Scriptor themes know: `name`, `slug`, `template`, `parent`,
* `pagetype`, `content`, `menu_title`, `images`, plus structural columns.
*
* Field values that aren't promoted to a top-level column live in the item's
* `data` bag — `__get()` reaches in lazily so theme code that asks for an
* uncommon field still works without us declaring every possible property.
*/
final readonly class Page
{
public string $name;
public string $slug;
public string $template;
public string $pagetype;
public string $menu_title;
public string $content;
public int $parent;
/** @var list<array<string, mixed>> */
public array $images;

public function __construct(
public Item $item,
) {
$data = $item->data;
$this->name = $item->name ?? '';
$this->slug = self::str($data->get('slug'));
$this->template = self::str($data->get('template'));
$this->pagetype = self::str($data->get('pagetype'));
$this->menu_title = self::str($data->get('menu_title'));
$this->content = self::str($data->get('content'));
$this->parent = (int) ($data->get('parent') ?? 0);

$rawImages = $data->get('images');
$this->images = \is_array($rawImages) ? array_values($rawImages) : [];
}

public function id(): ?int
{
return $this->item->id;
}

public function active(): bool
{
return $this->item->active;
}

public function created(): int
{
return $this->item->created;
}

public function updated(): int
{
return $this->item->updated;
}

/**
* Lazy access to any field not promoted to a typed property, e.g.
* theme-specific custom fields. Returns `null` for unknown keys instead
* of raising — themes that probe optional fields stay quiet.
*/
public function __get(string $name): mixed
{
return match ($name) {
'id' => $this->item->id,
'active' => $this->item->active,
'created' => $this->item->created,
'updated' => $this->item->updated,
default => $this->item->data->get($name),
};
}

public function __isset(string $name): bool
{
return match ($name) {
'id', 'active', 'created', 'updated' => true,
default => $this->item->data->has($name),
};
}

private static function str(mixed $value): string
{
if (\is_string($value)) {
return $value;
}
if (\is_int($value) || \is_float($value)) {
return (string) $value;
}
return '';
}
}
Loading