241 lines
9.1 KiB
PHP
241 lines
9.1 KiB
PHP
<?php
|
|
|
|
/**
|
|
* RolesAndPermissionsSeed.php
|
|
*
|
|
* Author: Funky Waddle
|
|
* Date: 2026-09-06
|
|
*
|
|
* Purpose: Seeds the default roles (Admin, Editor, Author, User), the
|
|
* baseline permission set, and their `role_permission` assignments, adapted
|
|
* from `data_model.example_seed_data_for_roles` in APP.yaml. Since `roles`/
|
|
* `permissions`/`users` all carry a NOT NULL `created_by` FK back to `users`,
|
|
* this also bootstraps the very first (primary) Admin user account if the
|
|
* `users` table is empty - the "primary Admin account" protected from edit/
|
|
* delete per `auth_requirements`. Its temporary password should be changed
|
|
* immediately after first login.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Phinx\Seed\AbstractSeed;
|
|
|
|
final class RolesAndPermissionsSeed extends AbstractSeed
|
|
{
|
|
public function getDependencies(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function run(): void
|
|
{
|
|
$adminUserId = $this->ensurePrimaryAdminUser();
|
|
|
|
$roleIds = $this->ensureRoles($adminUserId);
|
|
$permissionIds = $this->ensurePermissions($adminUserId);
|
|
|
|
$this->ensureUserHasRole($adminUserId, $roleIds['Admin'], $adminUserId);
|
|
|
|
$this->assignPermissions($adminUserId, $roleIds, $permissionIds, 'Admin', array_keys($permissionIds));
|
|
$this->assignPermissions($adminUserId, $roleIds, $permissionIds, 'Editor', [
|
|
'publish:page', 'unpublish:page', 'edit:page', 'delete:page',
|
|
'view:draft', 'edit:draft',
|
|
'view:blog_post', 'edit:blog_post', 'delete:blog_post', 'publish:blog_post',
|
|
'view:media', 'edit:media', 'delete:media',
|
|
]);
|
|
$this->assignPermissions($adminUserId, $roleIds, $permissionIds, 'Author', [
|
|
'publish:page', 'edit:page', 'edit:draft', 'publish:draft',
|
|
'view:blog_post', 'edit:blog_post', 'publish:blog_post',
|
|
'view:media', 'edit:media',
|
|
]);
|
|
// 'User' (public commenter role) intentionally gets no backend permissions.
|
|
}
|
|
|
|
private function ensurePrimaryAdminUser(): int
|
|
{
|
|
$existing = $this->fetchRow('SELECT id FROM users ORDER BY id ASC LIMIT 1');
|
|
|
|
if ($existing !== false) {
|
|
return (int) $existing['id'];
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
// `created_by` is a NOT NULL self-referencing FK; the very first user
|
|
// in the system has no other creator, so the row is inserted with a
|
|
// placeholder and immediately corrected to reference itself.
|
|
$this->execute(
|
|
'INSERT INTO users (username, password, f_name, l_name, email, failed_login_attempts, created_at, created_by)
|
|
VALUES (?, ?, ?, ?, ?, 0, ?, 0)',
|
|
[
|
|
'admin',
|
|
password_hash(bin2hex(random_bytes(8)), PASSWORD_DEFAULT),
|
|
'Site',
|
|
'Admin',
|
|
'admin@siteweaver.local',
|
|
$now,
|
|
],
|
|
);
|
|
|
|
$adminUserId = (int) $this->getAdapter()->getConnection()->lastInsertId();
|
|
|
|
$this->execute('UPDATE users SET created_by = ? WHERE id = ?', [$adminUserId, $adminUserId]);
|
|
|
|
return $adminUserId;
|
|
}
|
|
|
|
private function ensureUserHasRole(int $userId, int $roleId, int $adminUserId): void
|
|
{
|
|
$exists = $this->fetchRow("SELECT 1 FROM user_role WHERE user_id = $userId AND role_id = $roleId");
|
|
|
|
if ($exists !== false) {
|
|
return;
|
|
}
|
|
|
|
$this->execute(
|
|
'INSERT INTO user_role (user_id, role_id, created_at, created_by) VALUES (?, ?, ?, ?)',
|
|
[$userId, $roleId, date('Y-m-d H:i:s'), $adminUserId],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, int> role name => id
|
|
*/
|
|
private function ensureRoles(int $adminUserId): array
|
|
{
|
|
$definitions = [
|
|
'Admin' => 'Full access to all CMS features and settings.',
|
|
'Editor' => 'Can create, edit, and delete content, but cannot change site settings.',
|
|
'Author' => 'Can create and edit their own content; can view drafts.',
|
|
'User' => 'Basic frontend read-only access to published content. No backend admin access. Can comment on Blog Posts.',
|
|
];
|
|
|
|
$ids = [];
|
|
|
|
foreach ($definitions as $name => $description) {
|
|
$row = $this->fetchRow("SELECT id FROM roles WHERE name = '" . $name . "'");
|
|
|
|
if ($row !== false) {
|
|
$ids[$name] = (int) $row['id'];
|
|
|
|
continue;
|
|
}
|
|
|
|
$this->execute(
|
|
'INSERT INTO roles (name, description, created_at, created_by) VALUES (?, ?, ?, ?)',
|
|
[$name, $description, date('Y-m-d H:i:s'), $adminUserId],
|
|
);
|
|
|
|
$ids[$name] = (int) $this->getAdapter()->getConnection()->lastInsertId();
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, int> permission name => id
|
|
*/
|
|
private function ensurePermissions(int $adminUserId): array
|
|
{
|
|
// Common CMS permissions (per `data_model.example_seed_data_for_roles`)
|
|
// plus the granular View/Edit/Delete-per-resource permissions called
|
|
// for by `auth_requirements`'s "Granular Permissions" note, for every
|
|
// resource named there: Page, Plugin, Theme, Blog Post, User.
|
|
$definitions = [
|
|
['manage:site', null, 'configure', 'Change site-wide settings (e.g., logo, navigation).'],
|
|
['publish:page', 'page', 'publish', 'Publish a page to the public site.'],
|
|
['unpublish:page', 'page', 'unpublish', 'Remove a page from public view.'],
|
|
['edit:page', 'page', 'edit', 'Modify an existing page.'],
|
|
['delete:page', 'page', 'delete', 'Permanently delete a page.'],
|
|
['view:draft', 'draft', 'view', 'Read unpublished drafts.'],
|
|
['edit:draft', 'draft', 'edit', 'Edit an unpublished draft.'],
|
|
['publish:draft', 'draft', 'publish', 'Publish a draft to the public site.'],
|
|
|
|
['manage:user', null, 'admin', 'Create, edit, disable, or delete user accounts.'],
|
|
['view:user', 'user', 'view', 'View user accounts.'],
|
|
['edit:user', 'user', 'edit', 'Edit user accounts.'],
|
|
['delete:user', 'user', 'delete', 'Delete user accounts.'],
|
|
|
|
['manage:role', null, 'admin', 'Create, edit, or delete roles and their permissions.'],
|
|
|
|
['view:plugin', 'plugin', 'view', 'View installed plugins.'],
|
|
['edit:plugin', 'plugin', 'edit', 'Configure/activate/deactivate plugins.'],
|
|
['delete:plugin', 'plugin', 'delete', 'Uninstall/delete plugins.'],
|
|
|
|
['view:theme', 'theme', 'view', 'View installed themes.'],
|
|
['edit:theme', 'theme', 'edit', 'Edit/activate theme files and settings.'],
|
|
['delete:theme', 'theme', 'delete', 'Delete themes.'],
|
|
|
|
['view:blog_post', 'blog_post', 'view', 'View blog posts (including drafts by others, if granted).'],
|
|
['edit:blog_post', 'blog_post', 'edit', 'Create/edit blog posts.'],
|
|
['delete:blog_post', 'blog_post', 'delete', "Delete blog posts, including the author's own published posts."],
|
|
['publish:blog_post', 'blog_post', 'publish', 'Publish a blog post to the public site.'],
|
|
|
|
['view:media', 'media', 'view', 'View media library items.'],
|
|
['edit:media', 'media', 'edit', 'Upload/edit media library items.'],
|
|
['delete:media', 'media', 'delete', 'Delete media library items.'],
|
|
];
|
|
|
|
$ids = [];
|
|
|
|
foreach ($definitions as [$name, $type, $action, $description]) {
|
|
$row = $this->fetchRow("SELECT id FROM permissions WHERE name = '" . $name . "'");
|
|
|
|
if ($row !== false) {
|
|
$ids[$name] = (int) $row['id'];
|
|
|
|
continue;
|
|
}
|
|
|
|
$this->execute(
|
|
'INSERT INTO permissions (name, type, action, description, created_at, created_by) VALUES (?, ?, ?, ?, ?, ?)',
|
|
[$name, $type, $action, $description, date('Y-m-d H:i:s'), $adminUserId],
|
|
);
|
|
|
|
$ids[$name] = (int) $this->getAdapter()->getConnection()->lastInsertId();
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, int> $roleIds
|
|
* @param array<string, int> $permissionIds
|
|
* @param string[] $permissionNames
|
|
*/
|
|
private function assignPermissions(
|
|
int $adminUserId,
|
|
array $roleIds,
|
|
array $permissionIds,
|
|
string $roleName,
|
|
array $permissionNames,
|
|
): void {
|
|
if (!isset($roleIds[$roleName])) {
|
|
return;
|
|
}
|
|
|
|
$roleId = $roleIds[$roleName];
|
|
|
|
foreach ($permissionNames as $permissionName) {
|
|
if (!isset($permissionIds[$permissionName])) {
|
|
continue;
|
|
}
|
|
|
|
$permissionId = $permissionIds[$permissionName];
|
|
|
|
$exists = $this->fetchRow(
|
|
"SELECT 1 FROM role_permission WHERE role_id = $roleId AND permission_id = $permissionId",
|
|
);
|
|
|
|
if ($exists !== false) {
|
|
continue;
|
|
}
|
|
|
|
$this->execute(
|
|
'INSERT INTO role_permission (role_id, permission_id, created_at, created_by) VALUES (?, ?, ?, ?)',
|
|
[$roleId, $permissionId, date('Y-m-d H:i:s'), $adminUserId],
|
|
);
|
|
}
|
|
}
|
|
}
|