]'; protected $options = [ 'task' => 'Task name (or pass as first positional arg)', 't' => 'Short form of --task', 'dry' => 'Dry run (no DB writes)', 'force' => 'Ignore lock and run anyway', 'tz' => 'Timezone (default: configured school timezone)', ]; /** @var ConfigurationModel */ protected $configModel; /** Map of available task names -> handler methods. */ protected array $tasks = [ 'update_date_age_reference' => 'taskUpdateDateAgeReference', 'enable_attendance_on' => 'taskEnableAttendanceOn', 'enable_attendance_off' => 'taskEnableAttendanceOff', 'set_semester_spring' => 'taskSetSemesterSpring', 'set_semester_fall' => 'taskSetSemesterFall', ]; /** * Set enable_attendance = 1 */ protected function taskEnableAttendanceOn(bool $dry, DateTimeZone $tz): bool { return $this->setConfig('enable_attendance', '1', $dry); } /** * Set enable_attendance = 0 */ protected function taskEnableAttendanceOff(bool $dry, DateTimeZone $tz): bool { return $this->setConfig('enable_attendance', '0', $dry); } protected function taskUpdateDateAgeReference(bool $dry, DateTimeZone $tz): bool { $now = new DateTime('now', $tz); $isJune1 = ($now->format('n') === '6' && $now->format('j') === '1'); $forced = (CLI::getOption('force') !== null); if (!$isJune1 && !$forced) { CLI::write( "Today is {$now->format('Y-m-d')} (not June 1) — skipping. Use --force to override.", 'yellow' ); return true; // no-op, not an error } $year = (int) $now->format('Y'); $value = sprintf('%04d-12-31', $year); CLI::write("Set date_age_reference = {$value}" . ($dry ? ' [DRY]' : ''), 'light_gray'); if ($dry) return true; // Use your model method $ok = (bool) $this->configModel->setConfigValueByKey('date_age_reference', $value); if ($ok) { CLI::write("date_age_reference updated to {$value}", 'green'); } else { CLI::error("Failed to update date_age_reference"); } return $ok; } protected function taskSetSemesterSpring(bool $dry, DateTimeZone $tz): bool { $now = new DateTime('now', $tz); $isFeb1 = ($now->format('n') === '2' && $now->format('j') === '1'); $forced = (CLI::getOption('force') !== null); if (!$isFeb1 && !$forced) { CLI::write( "Today is {$now->format('Y-m-d')} (not Feb 1) — skipping. Use --force to override.", 'yellow' ); return true; // no-op is success } CLI::write("Set semester = Spring" . ($dry ? ' [DRY]' : ''), 'light_gray'); if ($dry) return true; return (bool) $this->configModel->setConfigValueByKey('semester', 'Spring'); } protected function taskSetSemesterFall(bool $dry, DateTimeZone $tz): bool { $now = new DateTime('now', $tz); $isJun1 = ($now->format('n') === '6' && $now->format('j') === '1'); $forced = (CLI::getOption('force') !== null); if (!$isJun1 && !$forced) { CLI::write( "Today is {$now->format('Y-m-d')} (not Jun 1) — skipping. Use --force to override.", 'yellow' ); return true; } CLI::write("Set semester = Fall" . ($dry ? ' [DRY]' : ''), 'light_gray'); if ($dry) return true; return (bool) $this->configModel->setConfigValueByKey('semester', 'Fall'); } public function run(array $params) { $this->configModel = model(ConfigurationModel::class); $tz = $this->getOptionString('tz', $params) ?? ((string)(config('School')->attendance['timezone'] ?? 'UTC')); $task = $this->getOptionString('task', $params, 't'); // Fallback: first positional arg (php spark config:update enable_attendance_on) if (!$task && !empty($params) && strpos($params[0], '-') !== 0) { $task = trim($params[0]); } $dry = $this->hasFlag('dry', $params); $force = $this->hasFlag('force', $params); $dtz = new DateTimeZone($tz); if ($task === '' || !isset($this->tasks[$task])) { CLI::error('Invalid or missing --task. Available: ' . implode(', ', array_keys($this->tasks))); return; } // Per-task lock $lockFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "ci4_config_update_{$task}.lock"; $fp = @fopen($lockFile, 'c+'); if (!$fp) { CLI::error("Unable to open lock file: $lockFile"); return; } if (!$force && !flock($fp, LOCK_EX | LOCK_NB)) { CLI::error("Task '$task' is already running (lock held). Use --force to override."); fclose($fp); return; } try { $method = $this->tasks[$task]; CLI::write("Running task: {$task}" . ($dry ? ' [DRY RUN]' : ''), 'yellow'); $ok = $this->{$method}($dry, $dtz); if ($ok === true) { CLI::write("Task '{$task}' finished successfully.", 'green'); } else { CLI::error("Task '{$task}' finished with warnings or no changes."); } } catch (\Throwable $e) { CLI::error("Task '{$task}' failed: " . $e->getMessage()); } finally { try { flock($fp, LOCK_UN); fclose($fp); @unlink($lockFile); } catch (\Throwable $e) { } } } /** Accepts --name=value, --name value, -s value, or scans $params. */ private function getOptionString(string $name, array $params, ?string $short = null): ?string { $v = CLI::getOption($name); if (is_string($v) && $v !== '') return trim($v); if ($short) { $v = CLI::getOption($short); if (is_string($v) && $v !== '') return trim($v); } foreach ($params as $i => $p) { if (strpos($p, "--{$name}=") === 0) return trim(substr($p, strlen($name) + 3)); if ($short && strpos($p, "-{$short}=") === 0) return trim(substr($p, strlen($short) + 2)); if ($p === "--{$name}" || ($short && $p === "-{$short}")) { return $params[$i + 1] ?? null; } } return null; } /** True if flag present as --name or -s (no value needed). */ private function hasFlag(string $name, array $params, ?string $short = null): bool { if (CLI::getOption($name) !== null) return true; if ($short && CLI::getOption($short) !== null) return true; foreach ($params as $p) { if ($p === "--{$name}" || ($short && $p === "-{$short}")) return true; } return false; } /* ------------------------- Helpers ------------------------- */ protected function setConfig(string $key, string $value, bool $dry): bool { // show current value $current = $this->configModel->where('config_key', $key)->first()['config_value'] ?? ''; CLI::write("{$key}: current={$current}", 'light_gray'); CLI::write("Set {$key} = {$value}" . ($dry ? ' [DRY]' : ''), 'light_gray'); if ($dry) return true; // ✅ use your model function $ok = (bool) $this->configModel->setConfigValueByKey($key, $value); // read-back to verify what’s persisted $after = $this->configModel->where('config_key', $key)->first()['config_value'] ?? ''; CLI::write("{$key}: after={$after}", $ok ? 'green' : 'red'); return $ok && ($after === $value); } }