diff --git a/bridges/EdfPricesBridge.php b/bridges/EdfPricesBridge.php index 684801d6afe..a1619a4c63b 100644 --- a/bridges/EdfPricesBridge.php +++ b/bridges/EdfPricesBridge.php @@ -1,5 +1,7 @@ [ 'name' => 'Choisir un contrat', 'type' => 'list', - // we can add later more option prices + 'defaultValue' => 'base', + // Stable, short values (first entry of each group): they are part of + // every subscriber's saved feed URL and must never change, even if the + // site we scrape (see CONTRACTS below) moves again. The other entries + // in each group are values this bridge used in the past (see + // LEGACY_CONTRACTS) kept here only so RSS-Bridge's own parameter + // validation still accepts them instead of silently discarding them. 'values' => [ - 'Base' => '/energie/edf/tarifs/tarif-bleu#base', - 'HPHC' => '/energie/edf/tarifs/tarif-bleu#hphc', - 'EJP' => '/energie/edf/tarifs/tarif-bleu#ejp', - 'Tempo' => '/energie/edf/tarifs/tempo' + 'Base' => [ + 'Base' => 'base', + 'Base (lien historique)' => '/energie/edf/tarifs/tarif-bleu#base', + ], + 'HPHC' => [ + 'HPHC' => 'hphc', + 'HPHC (lien historique)' => '/energie/edf/tarifs/tarif-bleu#hphc', + ], + 'EJP' => [ + 'EJP' => 'ejp', + 'EJP (lien historique)' => '/energie/edf/tarifs/tarif-bleu#ejp', + ], + 'Tempo' => [ + 'Tempo' => 'tempo', + 'Tempo (lien historique)' => '/energie/edf/tarifs/tempo', + ], ], ], 'power' => [ @@ -39,246 +59,163 @@ class EdfPricesBridge extends BridgeAbstract ]; const CACHE_TIMEOUT = 7200; // 2h - private function removeEmojisAndSpecialSpaces(string $text): string - { - // This regex covers most common emoji ranges in Unicode - $regex = '/[\x{1F600}-\x{1F64F}' . // Emoticons - '\x{1F300}-\x{1F5FF}' . // Misc Symbols and Pictographs - '\x{1F680}-\x{1F6FF}' . // Transport and Map - '\x{1F700}-\x{1F77F}' . // Alchemical Symbols - '\x{1F780}-\x{1F7FF}' . // Geometric Shapes Extended - '\x{1F800}-\x{1F8FF}' . // Supplemental Arrows-C - '\x{1F900}-\x{1F9FF}' . // Supplemental Symbols and Pictographs - '\x{1FA00}-\x{1FA6F}' . // Chess Symbols, Symbols and Pictographs Extended-A - '\x{1FA70}-\x{1FAFF}' . // Symbols and Pictographs Extended-B - '\x{2600}-\x{26FF}' . // Misc symbols - '\x{2700}-\x{27BF}' . // Dingbats - ']+/u'; - - return preg_replace($regex, '', str_replace(' ', '', $text)); - } - - /** - * @param simple_html_dom $html - * @param string $contractUri - * @return void - */ - private function tempo(simple_html_dom $html, string $contractUri, int $power): void - { - // colors - $ulDom = $html->find('#les-tarifs-du-kwh-tempo-pour-les-differentes-couleurs-et-heures-de-la-journee', 0)->nextSibling(); - $elementsDom = $ulDom->children; - - if ($elementsDom && count($elementsDom) === 3) { - // price per kWh is same for all powers - foreach ($elementsDom as $elementDom) { - $item = []; - - $matches = []; - preg_match_all( - '/Jour (.*) :.*?Heures (.*) : (.*).*?€.*?Heures (.*) : (.*).*?€/um', - $this->removeEmojisAndSpecialSpaces($elementDom->plaintext), - $matches, - PREG_SET_ORDER, - 0 - ); - - // for tempo contract we have 2x3 colors - if ($matches && count($matches[0]) === 6) { - for ($i = 0; $i < 2; $i++) { - $text = 'Jour ' . $matches[0][1] . ' - Heures ' . $matches[0][2 + 2 * $i] . ' : ' . $matches[0][3 + 2 * $i] . '€'; - $item['uri'] = self::URI . $contractUri; - $item['title'] = $text; - $item['author'] = self::MAINTAINER; - $item['content'] = $text; - $item['uid'] = hash('sha256', $item['title']); + // Where to find each contract's pricing table right now. Only this map needs + // updating if jechange.fr reshuffles its pages/anchors again; the "contract" + // values above (and thus everyone's saved feed URLs) stay untouched. + const CONTRACTS = [ + 'base' => [ + 'label' => 'Base', + 'path' => '/energie/edf/tarifs/tarifs-reglementes', + 'heading' => 'option-base', + ], + 'hphc' => [ + 'label' => 'HPHC', + 'path' => '/energie/edf/tarifs/tarifs-reglementes', + 'heading' => 'option-heures-pleines-heures-creuses', + ], + 'ejp' => [ + 'label' => 'EJP', + 'path' => '/energie/edf/tarifs/tarifs-reglementes', + 'heading' => 'option-ejp', + ], + 'tempo' => [ + 'label' => 'Tempo', + 'path' => '/energie/edf/tarifs/tempo', + 'heading' => 'option-tempo', + ], + ]; - $this->items[] = $item; - } - } - } - } + // Raw "contract" values used by this bridge in the past, kept working so that + // feed URLs generated before a site restructuring don't break. + const LEGACY_CONTRACTS = [ + '/energie/edf/tarifs/tarif-bleu#base' => 'base', + '/energie/edf/tarifs/tarif-bleu#hphc' => 'hphc', + '/energie/edf/tarifs/tarif-bleu#ejp' => 'ejp', + '/energie/edf/tarifs/tempo' => 'tempo', + ]; - // add subscription power info - $tablePrices = $ulDom->nextSibling()->nextSibling(); - $this->addSubscriptionPowerInfo($tablePrices, $contractUri, $power, 7); - } + // The header label used for the subscription (yearly) column, as opposed to + // the per-kWh rate columns (whose count and labels vary per contract). + const SUBSCRIPTION_LABEL = 'Abonnement'; - /** - * @param simple_html_dom $html - * @param string $contractUri - * @return void - */ - private function base(simple_html_dom $html, string $contractUri, int $power): void + public function collectData() { - $tablePrices = $html - ->find('#grille-tarifaire-et-prix-du-kwh-du-tarif-reglemente-edf-en-option-base', 0) - ->nextSibling() - ->nextSibling(); + $rawContract = $this->getInput('contract'); + $power = (int) $this->getInput('power'); - $prices = $tablePrices->find('.table tbody tr'); + if (isset(self::CONTRACTS[$rawContract])) { + $contractKey = $rawContract; + } else { + $contractKey = self::LEGACY_CONTRACTS[$rawContract] ?? null; + } + $contract = $contractKey ? self::CONTRACTS[$contractKey] : null; - // price per kWh is same for all powers - if ($prices && count($prices) === 9) { - $item = []; + if (!$contract) { + throwServerException('Contrat EDF inconnu: "' . $rawContract . '".'); + } - $text = 'Base : ' . $prices[0]->children(2); - $item['uri'] = self::URI . $contractUri; - $item['title'] = $text; - $item['author'] = self::MAINTAINER; - $item['content'] = $text; - $item['uid'] = hash('sha256', $item['title']); + $contractUri = $contract['path'] . '#' . $contract['heading']; + $html = getSimpleHTMLDOM(self::URI . $contract['path']); + + $headingDom = $html->find('#' . $contract['heading'], 0); + if (!$headingDom) { + throwServerException( + sprintf( + 'Impossible de trouver la section "%s" sur la page EDF. Le site a probablement encore changé de structure.', + $contract['label'] + ) + ); + } - $this->items[] = $item; + $tableDom = $this->findPriceTableAfter($html, $headingDom); + if (!$tableDom) { + throwServerException( + sprintf( + 'Impossible de trouver le tableau de tarifs pour "%s" sur la page EDF. Le site a probablement encore changé de structure.', + $contract['label'] + ) + ); } - $this->addSubscriptionPowerInfo($tablePrices, $contractUri, $power, 9); + $this->addItemsFromTable($tableDom, $contractUri, $power); } /** - * @param simple_html_dom $html - * @param string $contractUri - * @return void + * Finds the first