Repository navigation
fix(schedules): accept JSON boolean is_active/is_processing (fixes #218) #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,22 @@ class ServerSchedule | |
| */ | ||
| private static string $table = 'featherpanel_server_schedules'; | ||
|
|
||
| /** | ||
| * Whether a value is acceptable for the is_active / is_processing flags. | ||
| * | ||
| * Accepts real PHP booleans (is_numeric(true) === false in PHP, so a JSON | ||
| * `true`/`false` body value - the natural shape a JSON API client sends - | ||
| * was previously rejected) as well as numeric 0/1. | ||
| */ | ||
| public static function isValidActiveFlag(mixed $value): bool | ||
| { | ||
| if (is_bool($value)) { | ||
| return true; | ||
| } | ||
|
|
||
| return is_numeric($value) && in_array((int) $value, [0, 1], true); | ||
| } | ||
|
|
||
| /** | ||
| * Create a new server schedule. | ||
| * | ||
|
|
@@ -79,7 +95,7 @@ public static function createSchedule(array $data): int | false | |
| return false; | ||
| } | ||
| } elseif (in_array($field, ['is_active', 'is_processing'])) { | ||
| if (!is_numeric($data[$field]) || !in_array((int) $data[$field], [0, 1])) { | ||
| if (!self::isValidActiveFlag($data[$field])) { | ||
| $sanitizedData = self::sanitizeDataForLogging($data); | ||
| App::getInstance(true)->getLogger()->error('Invalid ' . $field . ': ' . $data[$field] . ' for schedule: ' . $data['name'] . ' with data: ' . json_encode($sanitizedData)); | ||
|
|
||
|
|
@@ -104,6 +120,12 @@ public static function createSchedule(array $data): int | false | |
| return false; | ||
| } | ||
|
|
||
| // Normalize accepted boolean values (JSON true/false) to 0/1 so the | ||
| // INSERT always binds a plain int; PDO's native bool binding for | ||
| // MySQL is inconsistent across drivers. | ||
| $data['is_active'] = (int) $data['is_active']; | ||
| $data['is_processing'] = (int) $data['is_processing']; | ||
|
|
||
| // Set default values for optional fields | ||
| $data['only_when_online'] = $data['only_when_online'] ?? 0; | ||
| $data['created_at'] = $data['created_at'] ?? date('Y-m-d H:i:s'); | ||
|
|
@@ -337,6 +359,14 @@ public static function updateSchedule(int $id, array $data): bool | |
|
|
||
| return false; | ||
| } | ||
| // Normalize accepted boolean values (JSON true/false) to 0/1, matching | ||
| // createSchedule() - PDO's native bool binding for MySQL is | ||
| // inconsistent across drivers. | ||
| foreach (['is_active', 'is_processing'] as $boolField) { | ||
| if (array_key_exists($boolField, $data)) { | ||
| $data[$boolField] = (int) $data[$boolField]; | ||
| } | ||
|
Comment on lines
+365
to
+368
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '320,390p' backend/app/Chat/ServerSchedule.php
rg -n 'updateSchedule\(|isValidActiveFlag|is_active|is_processing' backend/app backend/testsRepository: MythicalLTD/FeatherPanel Length of output: 15990 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- ServerSchedule flag helper, createSchedule, updateSchedule ---'
sed -n '1,145p' backend/app/Chat/ServerSchedule.php
sed -n '320,405p' backend/app/Chat/ServerSchedule.php
printf '%s\n' '--- HTTP update controller ---'
sed -n '480,560p' backend/app/Controllers/User/Server/ServerScheduleController.php
printf '%s\n' '--- HTTP route ---'
sed -n '90,125p' backend/app/routes/user/server/schedules.php
printf '%s\n' '--- Chatbot update caller ---'
sed -n '135,200p' backend/app/Services/Chatbot/Tools/UpdateScheduleTool.php
printf '%s\n' '--- All direct updateSchedule calls with context ---'
rg -n -C 8 'ServerSchedule::updateSchedule\(' backend/app backend/testsRepository: MythicalLTD/FeatherPanel Length of output: 22566 🏁 Script executed: #!/bin/bash
set -eu
sed -n '1,145p' backend/app/Chat/ServerSchedule.php
sed -n '320,405p' backend/app/Chat/ServerSchedule.php
sed -n '480,560p' backend/app/Controllers/User/Server/ServerScheduleController.php
sed -n '90,125p' backend/app/routes/user/server/schedules.php
sed -n '135,200p' backend/app/Services/Chatbot/Tools/UpdateScheduleTool.php
rg -n -C 8 'ServerSchedule::updateSchedule\(' backend/app backend/testsRepository: MythicalLTD/FeatherPanel Length of output: 22366 🏁 Script executed: #!/bin/bash
set -eu
rg -n -C 8 'featherpanel_server_schedules|CREATE TABLE.*server.schedule|is_processing|is_active' backend database migrations 2>/dev/null | head -240Repository: MythicalLTD/FeatherPanel Length of output: 21714 🏁 Script executed: #!/bin/bash
set -eu
rg -n -C 6 'featherpanel_server_schedules|server_schedules' . --glob '!backend/vendor/**' --glob '!vendor/**' --glob '!node_modules/**' | head -300Repository: MythicalLTD/FeatherPanel Length of output: 22016 🏁 Script executed: #!/bin/bash
set -eu
cat -n backend/storage/migrations/2025-08-21.13.31-schedules.sqlRepository: MythicalLTD/FeatherPanel Length of output: 1321 Validate update flags before casting them.
🤖 Prompt for AI Agents |
||
| } | ||
| $set = implode(', ', array_map(fn ($f) => "$f = :$f", $fields)); | ||
| $sql = 'UPDATE ' . self::$table . ' SET ' . $set . ' WHERE id = :id'; | ||
| $stmt = $pdo->prepare($sql); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| <?php | ||
|
|
||
| /* | ||
| * This file is part of FeatherPanel. | ||
| * | ||
| * Copyright (C) 2025 MythicalSystems Studios | ||
| * Copyright (C) 2025 FeatherPanel Contributors | ||
| * Copyright (C) 2025 Cassian Gherman (aka NaysKutzu) | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License as published | ||
| * by the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * See the LICENSE file or <https://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| use App\Chat\ServerSchedule; | ||
| use PHPUnit\Framework\TestCase; | ||
|
|
||
| /** | ||
| * Regression test for GitHub issue #218: creating a schedule with is_active | ||
| * sent as a JSON boolean (true/false) was rejected with a 500 | ||
| * "Invalid is_active" error because is_numeric(true) === false in PHP. | ||
| * | ||
| * This targets ServerSchedule::isValidActiveFlag() directly (no DB/App boot | ||
| * required) since createSchedule()/updateSchedule() reach the database. | ||
| */ | ||
| class ServerScheduleIsValidActiveFlagTest extends TestCase | ||
| { | ||
| public function testAcceptsJsonBooleans(): void | ||
| { | ||
| $this->assertTrue(ServerSchedule::isValidActiveFlag(true)); | ||
| $this->assertTrue(ServerSchedule::isValidActiveFlag(false)); | ||
| } | ||
|
|
||
| public function testAcceptsNumericZeroOrOne(): void | ||
| { | ||
| $this->assertTrue(ServerSchedule::isValidActiveFlag(0)); | ||
| $this->assertTrue(ServerSchedule::isValidActiveFlag(1)); | ||
| $this->assertTrue(ServerSchedule::isValidActiveFlag('0')); | ||
| $this->assertTrue(ServerSchedule::isValidActiveFlag('1')); | ||
| } | ||
|
|
||
| public function testRejectsOutOfRangeNumbers(): void | ||
| { | ||
| $this->assertFalse(ServerSchedule::isValidActiveFlag(2)); | ||
| $this->assertFalse(ServerSchedule::isValidActiveFlag(-1)); | ||
| } | ||
|
Comment on lines
+45
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Add fractional-value rejection cases. Add cases for 🤖 Prompt for AI Agents |
||
|
|
||
| public function testRejectsNonNumericStrings(): void | ||
| { | ||
| $this->assertFalse(ServerSchedule::isValidActiveFlag('yes')); | ||
| $this->assertFalse(ServerSchedule::isValidActiveFlag('')); | ||
| } | ||
|
|
||
| public function testRejectsNullAndArrays(): void | ||
| { | ||
| $this->assertFalse(ServerSchedule::isValidActiveFlag(null)); | ||
| $this->assertFalse(ServerSchedule::isValidActiveFlag([])); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reject fractional numeric values before normalization.
is_numeric(0.5)is true, and(int) 0.5is0. The helper therefore accepts0.5,1.5, and numeric strings such as"1.9". Creation then silently stores a different flag value. Compare the numeric value itself with0or1before casting it.🤖 Prompt for AI Agents