From 8f4405a4ee202e3f3994a955a5d47cf84b71ab00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= <1536036+zoic21@users.noreply.github.com> Date: Mon, 15 Jun 2026 08:27:12 +0000 Subject: [PATCH 1/3] feat: check core::branch validity and alert on health page Add update::getCoreBranchList() and update::isCoreBranchValid() to verify that the configured core branch (or tag) still exists on the repository. Surface the result as a new 'Branche du core' entry on the health page so the user is warned when the branch no longer exists. Reuse the new helper in the administration page to remove the duplicated fetch logic. Closes #3166 --- core/class/jeedom.class.php | 12 ++++++ core/class/update.class.php | 67 ++++++++++++++++++++++++++++++++++ desktop/php/administration.php | 20 +--------- 3 files changed, 80 insertions(+), 19 deletions(-) diff --git a/core/class/jeedom.class.php b/core/class/jeedom.class.php index 4fbe7170e3..c34a156be3 100644 --- a/core/class/jeedom.class.php +++ b/core/class/jeedom.class.php @@ -191,6 +191,18 @@ public static function health() { 'key' => 'uptodate' ); + if (config::byKey('core::repo::provider') == 'default') { + $branch = config::byKey('core::branch', 'core', 'master'); + $state = update::isCoreBranchValid(); + $return[] = array( + 'name' => __('Branche du core', __FILE__), + 'state' => $state, + 'result' => ($state) ? $branch : $branch . ' (' . __('introuvable', __FILE__) . ')', + 'comment' => ($state) ? '' : __("La branche configurée pour le core n'existe plus sur le dépôt. Allez dans Réglages -> Système -> Mises à jour / Réinitialisation pour sélectionner une branche valide.", __FILE__), + 'key' => 'core::branch' + ); + } + $state = (config::byKey('enableCron', 'core', 1, true) != 0) ? true : false; $return[] = array( 'name' => __('Cron actif', __FILE__), diff --git a/core/class/update.class.php b/core/class/update.class.php index ffbde4fb6f..b118cf3314 100644 --- a/core/class/update.class.php +++ b/core/class/update.class.php @@ -493,6 +493,73 @@ public static function getLastAvailableVersion() { return null; } + /** + * Retourne la liste des branches et tags disponibles pour le core sur le dépôt par défaut. + * Le résultat est mis en cache 24h (clé core::branch::default::list). + * @param bool $_refresh force le rafraichissement du cache + * @return array tableau contenant les clés 'branchs' et 'tags' + */ + public static function getCoreBranchList($_refresh = false) { + $lists = ($_refresh) ? array() : cache::byKey('core::branch::default::list')->getValue(array()); + if (!isset($lists['branchs']) || !is_array($lists['branchs'])) { + $request_http = new com_http('https://api.github.com/repos/jeedom/core/branches'); + $request_http->setHeader(array('User-agent: jeedom')); + try { + $lists['branchs'] = json_decode($request_http->exec(10, 1), true); + } catch (\Exception $e) { + } + cache::set('core::branch::default::list', $lists, 86400); + } + if (!isset($lists['tags']) || !is_array($lists['tags'])) { + $request_http = new com_http('https://api.github.com/repos/jeedom/core/tags'); + $request_http->setHeader(array('User-agent: jeedom')); + try { + $lists['tags'] = json_decode($request_http->exec(10, 1), true); + } catch (\Exception $e) { + } + cache::set('core::branch::default::list', $lists, 86400); + } + return $lists; + } + + /** + * Vérifie que la branche (ou le tag) configurée pour le core existe toujours sur le dépôt. + * Pour éviter les faux positifs, retourne true si la validité ne peut pas être déterminée + * (fournisseur personnalisé, branche stable connue, ou liste distante indisponible). + * @return bool true si la branche est valide (ou indéterminable), false si elle est introuvable + */ + public static function isCoreBranchValid() { + if (config::byKey('core::repo::provider') != 'default') { + return true; + } + $branch = config::byKey('core::branch', 'core', 'master'); + if (in_array($branch, array('master', 'release', 'stable'))) { + return true; + } + $lists = self::getCoreBranchList(); + if (strpos($branch, 'tag::') === 0) { + $tagName = substr($branch, strlen('tag::')); + if (!isset($lists['tags']) || !is_array($lists['tags']) || count($lists['tags']) == 0) { + return true; + } + foreach ($lists['tags'] as $tag) { + if (is_array($tag) && isset($tag['name']) && $tag['name'] == $tagName) { + return true; + } + } + return false; + } + if (!isset($lists['branchs']) || !is_array($lists['branchs']) || count($lists['branchs']) == 0) { + return true; + } + foreach ($lists['branchs'] as $remoteBranch) { + if (is_array($remoteBranch) && isset($remoteBranch['name']) && $remoteBranch['name'] == $branch) { + return true; + } + } + return false; + } + public function checkUpdate() { if ($this->getConfiguration('doNotUpdate') == 1 && $this->getType() != 'core') { log::add(__CLASS__, 'alert', __('Vérification des mises à jour, mise à jour et réinstallation désactivées sur', __FILE__) . ' ' . $this->getLogicalId()); diff --git a/desktop/php/administration.php b/desktop/php/administration.php index f226834255..7b16039abb 100644 --- a/desktop/php/administration.php +++ b/desktop/php/administration.php @@ -1822,25 +1822,7 @@ getValue(array()); - if (!isset($lists['branchs']) || !is_array($lists['branchs'])) { - $request_http = new com_http('https://api.github.com/repos/jeedom/core/branches'); - $request_http->setHeader(array('User-agent: jeedom')); - try { - $lists['branchs'] = json_decode($request_http->exec(10, 1), true); - } catch (\Exception $e) { - } - cache::set('core::branch::default::list', $lists, 86400); - } - if (!isset($lists['tags']) || !is_array($lists['tags'])) { - $request_http = new com_http('https://api.github.com/repos/jeedom/core/tags'); - $request_http->setHeader(array('User-agent: jeedom')); - try { - $lists['tags'] = json_decode($request_http->exec(10, 1), true); - } catch (\Exception $e) { - } - cache::set('core::branch::default::list', $lists, 86400); - } + $lists = update::getCoreBranchList(); if (isset($lists['branchs']) && is_array($lists['branchs'])) { echo ''; foreach ($lists['branchs'] as $branch) { From f122d5ba4f8d1420cc0b9ff656a21c911c37f5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= <1536036+zoic21@users.noreply.github.com> Date: Mon, 15 Jun 2026 08:29:04 +0000 Subject: [PATCH 2/3] feat: alert in message center when core branch is invalid During the core update check, raise a message in the message center when the configured core branch no longer exists, and clear it automatically once a valid branch is selected again. Refs #3166 --- core/class/update.class.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/class/update.class.php b/core/class/update.class.php index b118cf3314..0ba022dd9b 100644 --- a/core/class/update.class.php +++ b/core/class/update.class.php @@ -570,6 +570,11 @@ public function checkUpdate() { return; } if (config::byKey('core::repo::provider') == 'default') { + if (self::isCoreBranchValid()) { + message::removeAll('core', 'core::branch::invalid'); + } else { + message::add('core', __('La branche configurée pour le core (', __FILE__) . config::byKey('core::branch', 'core', 'master') . __(") n'existe plus sur le dépôt. Les mises à jour ne fonctionneront pas tant qu'une branche valide n'est pas sélectionnée dans Réglages -> Système -> Mises à jour / Réinitialisation.", __FILE__), '', 'core::branch::invalid'); + } $this->setRemoteVersion(self::getLastAvailableVersion()); } else { $class = 'repo_' . config::byKey('core::repo::provider'); From c25ce830756008625c428a6b2b2300e410c47e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= <1536036+zoic21@users.noreply.github.com> Date: Mon, 15 Jun 2026 08:33:47 +0000 Subject: [PATCH 3/3] test: add coverage for update::isCoreBranchValid Verify stable branches, existing/missing branches and tags, custom provider and unreachable remote list (no false positive). Refs #3166 --- tests/updateBranchTest.php | 96 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/updateBranchTest.php diff --git a/tests/updateBranchTest.php b/tests/updateBranchTest.php new file mode 100644 index 0000000000..07510dd03d --- /dev/null +++ b/tests/updateBranchTest.php @@ -0,0 +1,96 @@ +. +*/ + +use PHPUnit\Framework\TestCase; + + +class updateBranchTest extends TestCase { + + private $providerBackup; + private $branchBackup; + + protected function setUp(): void { + $this->providerBackup = config::byKey('core::repo::provider'); + $this->branchBackup = config::byKey('core::branch'); + config::save('core::repo::provider', 'default'); + // Liste distante simulée (branches + tags) pour éviter tout appel réseau. + cache::set('core::branch::default::list', array( + 'branchs' => array( + array('name' => 'master'), + array('name' => 'beta'), + array('name' => 'alpha'), + ), + 'tags' => array( + array('name' => '4.4.0'), + array('name' => '4.4.1'), + ), + ), 86400); + } + + protected function tearDown(): void { + config::save('core::repo::provider', $this->providerBackup); + config::save('core::branch', $this->branchBackup); + cache::byKey('core::branch::default::list')->remove(); + } + + public function testStableBranchIsAlwaysValid() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + config::save('core::branch', 'master'); + $this->assertTrue(update::isCoreBranchValid()); + } + + public function testExistingBranchIsValid() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + config::save('core::branch', 'beta'); + $this->assertTrue(update::isCoreBranchValid()); + } + + public function testMissingBranchIsInvalid() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + config::save('core::branch', 'branche-supprimee'); + $this->assertFalse(update::isCoreBranchValid()); + } + + public function testExistingTagIsValid() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + config::save('core::branch', 'tag::4.4.0'); + $this->assertTrue(update::isCoreBranchValid()); + } + + public function testMissingTagIsInvalid() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + config::save('core::branch', 'tag::9.9.9'); + $this->assertFalse(update::isCoreBranchValid()); + } + + public function testCustomProviderIsAlwaysValid() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + config::save('core::repo::provider', 'custom'); + config::save('core::branch', 'branche-supprimee'); + $this->assertTrue(update::isCoreBranchValid()); + } + + public function testUnreachableListDoesNotRaiseFalsePositive() { + echo "\n" . __CLASS__ . '::' . __FUNCTION__ . ' : '; + // Liste distante indisponible (réseau KO) : on ne doit pas alerter à tort. + cache::set('core::branch::default::list', array('branchs' => array(), 'tags' => array()), 86400); + config::save('core::branch', 'branche-inconnue'); + $this->assertTrue(update::isCoreBranchValid()); + } +} +?>