fix(devices): deleting an unpaired MAG or Enigma2 device failed

MagService::getById() and EnigmaService::getById() look up the paired line
with UserRepository::getLineById($rRow['user']['pair_id']), and pair_id is
NULL for a device without a pair. The `int` type the cs-fix pass (758a9cab)
put on getLineById() made that a TypeError, so loading such a device —
deleting it, among others — answered an empty page and changed nothing.
Found by the new E2E device test.

getLineById() is also fed activation codes' nullable subscriber_id and raw
request values, so the guard lives there: anything that is not a positive id
finds nothing, as the untyped version did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WDhDajBPziJwjWZcXnh6R
This commit is contained in:
root
2026-09-16 14:18:09 +00:00
co-authored by Claude Opus 5
parent f7bba5cbcd
commit fd13c94032
2 changed files with 21 additions and 3 deletions
+9 -3
View File
@@ -425,12 +425,18 @@ class UserRepository {
/**
* Fetch a line by id.
*
* @param int $rID Line id.
* @param int|string|null $rID Line id. Callers hand over nullable columns (a
* device's `pair_id`) and request values; anything
* that is not a positive id finds nothing.
* @return array|null The line row, or null if not found.
*/
public static function getLineById(int $rID) {
public static function getLineById($rID) {
if (!is_numeric($rID) || (int) $rID <= 0) {
return null;
}
$db = self::db();
$db->query('SELECT * FROM `lines` WHERE `id` = ?;', $rID);
$db->query('SELECT * FROM `lines` WHERE `id` = ?;', (int) $rID);
if ($db->num_rows() == 1) {
return $db->get_row();
+12
View File
@@ -111,4 +111,16 @@ final class UserRepositoryTest extends TestCase {
// An id-based lookup carries no username/password to re-verify.
$this->assertTrue($this->call('verifyCachedCredentials', array(), 5, null, null));
}
/**
* getLineById() is handed nullable columns (a MAG / Enigma2 device's
* `pair_id`, an activation code's `subscriber_id`) and raw request values.
* None of those is a line, and none may end the request: loading an
* unpaired MAG device used to throw, so deleting one answered an empty page.
*/
public function testGetLineByIdFindsNothingForANonId(): void {
foreach (array(null, '', 0, '0', -3, 'abc') as $rID) {
$this->assertNull(UserRepository::getLineById($rID), var_export($rID, true));
}
}
}