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..0ba022dd9b 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()); @@ -503,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'); 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) { 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()); + } +} +?>