39424-vm/okr_bootstrap.php
Flatlogic Bot c04a6c2d66 Version 1
2026-04-01 08:42:52 +00:00

177 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
@date_default_timezone_set('UTC');
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
require_once __DIR__ . '/db/config.php';
const OKR_ROLES = ['Super Admin', 'Admin', 'CEO', 'Director', 'Manager', 'Team', 'Staff'];
const OKR_APPROVER_ROLES = ['Super Admin', 'Admin', 'CEO', 'Director', 'Manager'];
function env_value(string $key, string $default = ''): string
{
$serverValue = $_SERVER[$key] ?? null;
if (is_string($serverValue) && $serverValue !== '') {
return $serverValue;
}
$envValue = getenv($key);
return is_string($envValue) && $envValue !== '' ? $envValue : $default;
}
function e(mixed $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function okr_app_name(): string
{
return env_value('PROJECT_NAME', 'Aligned OKR Cloud');
}
function okr_meta_description(): string
{
return env_value('PROJECT_DESCRIPTION', 'Multi-tenant OKR workspace for strategy, approvals, and score tracking.');
}
function okr_roles(): array
{
return OKR_ROLES;
}
function okr_is_approver(string $role): bool
{
return in_array($role, OKR_APPROVER_ROLES, true);
}
function okr_is_super_admin(): bool
{
return (($_SESSION['okr_user']['role'] ?? '') === 'Super Admin');
}
function okr_current_user(): array
{
if (empty($_SESSION['okr_user']) || !is_array($_SESSION['okr_user'])) {
header('Location: login.php');
exit;
}
return $_SESSION['okr_user'];
}
function okr_flash(string $type, string $message): void
{
$_SESSION['okr_flash'] = [
'type' => $type,
'message' => $message,
];
}
function okr_pull_flash(): ?array
{
$flash = $_SESSION['okr_flash'] ?? null;
unset($_SESSION['okr_flash']);
return is_array($flash) ? $flash : null;
}
function okr_csrf_token(): string
{
if (empty($_SESSION['okr_csrf'])) {
$_SESSION['okr_csrf'] = bin2hex(random_bytes(16));
}
return (string) $_SESSION['okr_csrf'];
}
function okr_verify_csrf(): void
{
$sessionToken = $_SESSION['okr_csrf'] ?? '';
$postedToken = $_POST['csrf_token'] ?? '';
if (!is_string($postedToken) || !hash_equals((string) $sessionToken, $postedToken)) {
throw new RuntimeException('Security validation failed. Please refresh and try again.');
}
}
function okr_scope_clause(string $alias = ''): string
{
$prefix = $alias !== '' ? $alias . '.' : '';
return okr_is_super_admin() ? '1=1' : $prefix . 'organization_slug = :organization_slug';
}
function okr_scope_params(array $user): array
{
return okr_is_super_admin() ? [] : [':organization_slug' => $user['organization_slug']];
}
function okr_calculate_score(float $currentValue, float $targetValue): float
{
if ($targetValue <= 0) {
return 0.0;
}
$score = ($currentValue / $targetValue) * 100;
return round(max(0, min(100, $score)), 1);
}
function okr_badge_class(string $state): string
{
return match ($state) {
'approved', 'completed', 'active' => 'badge-soft-success',
'pending_manager', 'submitted' => 'badge-soft-warning',
'rejected', 'needs_revision' => 'badge-soft-danger',
default => 'badge-soft-neutral',
};
}
function okr_notification_count(array $user): int
{
if (!okr_is_approver($user['role'])) {
return 0;
}
$params = okr_scope_params($user);
$stmt = db()->prepare('SELECT COUNT(*) FROM okr_items WHERE ' . okr_scope_clause() . ' AND approval_state = :approval_state');
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value);
}
$stmt->bindValue(':approval_state', 'pending_manager');
$stmt->execute();
return (int) $stmt->fetchColumn();
}
function okr_ensure_schema(): void
{
db()->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS okr_items (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
organization_name VARCHAR(120) NOT NULL,
organization_slug VARCHAR(120) NOT NULL,
owner_name VARCHAR(120) NOT NULL,
owner_email VARCHAR(160) NOT NULL,
owner_role VARCHAR(40) NOT NULL,
department_name VARCHAR(120) NOT NULL,
period_name VARCHAR(120) NOT NULL,
objective_title VARCHAR(255) NOT NULL,
key_result_title VARCHAR(255) NOT NULL,
description TEXT NULL,
target_value DECIMAL(10,2) NOT NULL DEFAULT 100.00,
current_value DECIMAL(10,2) NOT NULL DEFAULT 0.00,
score_percent DECIMAL(5,2) NOT NULL DEFAULT 0.00,
status VARCHAR(40) NOT NULL DEFAULT 'draft',
approval_state VARCHAR(40) NOT NULL DEFAULT 'pending_manager',
manager_comment TEXT NULL,
created_by_email VARCHAR(160) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_scope (organization_slug, department_name, approval_state),
INDEX idx_owner (owner_email, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL);
}