diff --git a/public/recent_changes.json b/public/recent_changes.json new file mode 100644 index 0000000..953c842 --- /dev/null +++ b/public/recent_changes.json @@ -0,0 +1,4 @@ +{ + "last_updated": "2025-08-22T18:13:20.641943", + "recent_changes": [] +} \ No newline at end of file diff --git a/src/Controller/WikiController.php b/src/Controller/WikiController.php index a54c25e..f243cce 100644 --- a/src/Controller/WikiController.php +++ b/src/Controller/WikiController.php @@ -8,6 +8,112 @@ use Symfony\Component\Routing\Annotation\Route; class WikiController extends AbstractController { + /** + * Detects incorrect heading hierarchies in a list of sections + * For example, h4 directly under h2 without h3 in between + * + * @param array $sections List of sections with 'level' and 'title' keys + * @return array List of section indices with hierarchy errors + */ + private function detectHeadingHierarchyErrors(array $sections): array + { + $errors = []; + $lastLevel = 0; + + foreach ($sections as $index => $section) { + $currentLevel = isset($section['level']) ? (int)$section['level'] : 0; + + // Skip if level is not set or is 0 + if ($currentLevel === 0) { + continue; + } + + // If this is the first section, just record its level + if ($lastLevel === 0) { + $lastLevel = $currentLevel; + continue; + } + + // Check if the level jump is more than 1 + // For example, h2 -> h4 (skipping h3) + if ($currentLevel > $lastLevel + 1) { + $errors[] = $index; + } + + $lastLevel = $currentLevel; + } + + return $errors; + } + #[Route('/wiki/recent-changes', name: 'app_admin_wiki_recent_changes')] + public function recentChanges(): Response + { + $recentChangesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/recent_changes.json'; + + // Initialize arrays + $recentChanges = []; + $lastUpdated = null; + + // Check if the recent changes file exists and load it + if (file_exists($recentChangesFile)) { + $recentChangesData = json_decode(file_get_contents($recentChangesFile), true); + + if (isset($recentChangesData['recent_changes']) && is_array($recentChangesData['recent_changes'])) { + $recentChanges = $recentChangesData['recent_changes']; + $lastUpdated = isset($recentChangesData['last_updated']) ? $recentChangesData['last_updated'] : null; + } + + // Check if the data is older than 1 hour + if ($lastUpdated) { + $lastUpdatedTime = new \DateTime($lastUpdated); + $now = new \DateTime(); + $diff = $now->diff($lastUpdatedTime); + + // If older than 1 hour, refresh the data + if ($diff->h >= 1 || $diff->days > 0) { + $this->refreshRecentChangesData(); + return $this->redirectToRoute('app_admin_wiki_recent_changes'); + } + } + } else { + // If the file doesn't exist, try to create it by running the script + $this->refreshRecentChangesData(); + + // Check if the file was created + if (file_exists($recentChangesFile)) { + return $this->redirectToRoute('app_admin_wiki_recent_changes'); + } else { + $this->addFlash('error', 'Impossible de générer le fichier des changements récents.'); + } + } + + return $this->render('admin/wiki_recent_changes.html.twig', [ + 'recent_changes' => $recentChanges, + 'last_updated' => $lastUpdated + ]); + } + + /** + * Refresh the recent changes data by running the fetch_recent_changes.py script + */ + private function refreshRecentChangesData(): void + { + try { + $scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/fetch_recent_changes.py'; + if (file_exists($scriptPath)) { + exec('python3 ' . $scriptPath . ' --force 2>&1', $output, $returnCode); + + if ($returnCode !== 0) { + $this->addFlash('warning', 'Impossible de mettre à jour les changements récents. Erreur: ' . implode("\n", $output)); + } + } else { + $this->addFlash('error', 'Le script fetch_recent_changes.py n\'existe pas.'); + } + } catch (\Exception $e) { + $this->addFlash('error', 'Erreur lors de l\'exécution du script: ' . $e->getMessage()); + } + } + #[Route('/wiki/missing-translations', name: 'app_admin_wiki_missing_translations')] public function missingTranslations(): Response { @@ -664,11 +770,149 @@ class WikiController extends AbstractController $mediaComparison['fr_only_count'] = count($frOnlyImages); } + // Get link comparison data + $linkComparison = $page['link_comparison'] ?? null; + + // Sort links alphabetically by URL if link comparison exists + if ($linkComparison) { + // Sort English-only links + if (isset($linkComparison['en_only']) && is_array($linkComparison['en_only'])) { + usort($linkComparison['en_only'], function($a, $b) { + return strcmp($a['href'], $b['href']); + }); + } + + // Sort French-only links + if (isset($linkComparison['fr_only']) && is_array($linkComparison['fr_only'])) { + usort($linkComparison['fr_only'], function($a, $b) { + return strcmp($a['href'], $b['href']); + }); + } + + // Sort common links + if (isset($linkComparison['common']) && is_array($linkComparison['common'])) { + usort($linkComparison['common'], function($a, $b) { + return strcmp($a['en']['href'], $b['en']['href']); + }); + } + } + + // Get section comparison data and filter out "Contents" sections + $sectionComparison = $page['section_comparison'] ?? null; + + // Filter out "Contents" sections if section comparison exists + if ($sectionComparison) { + // Filter common sections + if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { + $sectionComparison['common'] = array_filter($sectionComparison['common'], function($section) { + // Skip if either English or French title is "Contents" + return !($section['en']['title'] === 'Contents' || $section['fr']['title'] === 'Sommaire'); + }); + // Re-index array + $sectionComparison['common'] = array_values($sectionComparison['common']); + } + + // Filter English-only sections + if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) { + $sectionComparison['en_only'] = array_filter($sectionComparison['en_only'], function($section) { + return $section['title'] !== 'Contents'; + }); + // Re-index array + $sectionComparison['en_only'] = array_values($sectionComparison['en_only']); + } + + // Filter French-only sections + if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) { + $sectionComparison['fr_only'] = array_filter($sectionComparison['fr_only'], function($section) { + return $section['title'] !== 'Sommaire'; + }); + // Re-index array + $sectionComparison['fr_only'] = array_values($sectionComparison['fr_only']); + } + } + + // Calculate adjusted section counts (excluding "Contents" sections) + $enSectionCount = $enPage['sections']; + $frSectionCount = $frPage['sections']; + + // Adjust section counts if we have section comparison data + if ($sectionComparison) { + // Count how many "Contents" sections were filtered out + $contentsFilteredCount = 0; + + // Check common sections that were filtered + if (isset($page['section_comparison']['common']) && is_array($page['section_comparison']['common'])) { + foreach ($page['section_comparison']['common'] as $section) { + if ($section['en']['title'] === 'Contents' || $section['fr']['title'] === 'Sommaire') { + $contentsFilteredCount++; + } + } + } + + // Check English-only sections that were filtered + if (isset($page['section_comparison']['en_only']) && is_array($page['section_comparison']['en_only'])) { + foreach ($page['section_comparison']['en_only'] as $section) { + if ($section['title'] === 'Contents') { + $contentsFilteredCount++; + } + } + } + + // Check French-only sections that were filtered + if (isset($page['section_comparison']['fr_only']) && is_array($page['section_comparison']['fr_only'])) { + foreach ($page['section_comparison']['fr_only'] as $section) { + if ($section['title'] === 'Sommaire') { + $contentsFilteredCount++; + } + } + } + + // Adjust section counts + $enSectionCount -= $contentsFilteredCount; + $frSectionCount -= $contentsFilteredCount; + } + + // Check for incorrect heading hierarchies + $enHierarchyErrors = []; + $frHierarchyErrors = []; + + // Check English sections + if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) { + $enHierarchyErrors = $this->detectHeadingHierarchyErrors($sectionComparison['en_only']); + } + + // Also check common sections (English side) + if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { + $commonEnSections = array_map(function($section) { + return $section['en']; + }, $sectionComparison['common']); + + $enHierarchyErrors = array_merge($enHierarchyErrors, $this->detectHeadingHierarchyErrors($commonEnSections)); + } + + // Check French sections + if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) { + $frHierarchyErrors = $this->detectHeadingHierarchyErrors($sectionComparison['fr_only']); + } + + // Also check common sections (French side) + if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { + $commonFrSections = array_map(function($section) { + return $section['fr']; + }, $sectionComparison['common']); + + $frHierarchyErrors = array_merge($frHierarchyErrors, $this->detectHeadingHierarchyErrors($commonFrSections)); + } + $detailedComparison = [ - 'section_comparison' => $page['section_comparison'] ?? null, - 'link_comparison' => $page['link_comparison'] ?? null, + 'section_comparison' => $sectionComparison, + 'link_comparison' => $linkComparison, 'media_comparison' => $mediaComparison, - 'category_comparison' => $page['category_comparison'] ?? null + 'category_comparison' => $page['category_comparison'] ?? null, + 'adjusted_en_section_count' => $enSectionCount, + 'adjusted_fr_section_count' => $frSectionCount, + 'en_hierarchy_errors' => $enHierarchyErrors, + 'fr_hierarchy_errors' => $frHierarchyErrors ]; $mediaDiff = $page['media_diff'] ?? 0; @@ -841,6 +1085,15 @@ class WikiController extends AbstractController } } + // Ensure page URLs are strings to prevent array to string conversion errors + if ($frPage && isset($frPage['url']) && is_array($frPage['url'])) { + $frPage['url'] = json_encode($frPage['url']); + } + + if ($enPage && isset($enPage['url']) && is_array($enPage['url'])) { + $enPage['url'] = json_encode($enPage['url']); + } + return $this->render('admin/wiki_compare.html.twig', [ 'key' => $key, 'en_page' => $enPage, diff --git a/templates/admin/_wiki_navigation.html.twig b/templates/admin/_wiki_navigation.html.twig index 502691b..1d0daeb 100644 --- a/templates/admin/_wiki_navigation.html.twig +++ b/templates/admin/_wiki_navigation.html.twig @@ -41,6 +41,11 @@ Groupes OSM-FR +
Texte | -URL | +Texte EN | +URL EN | +Texte FR | +URL FR | ||||
---|---|---|---|---|---|---|---|---|---|
{{ link.text }} | -{{ link.href|slice(0, 30) }}... | + {% if i < en_links|length %} +{{ en_links[i].text }} | +{{ en_links[i].href|slice(0, 30) }}... | + {% else %} ++ | + {% endif %} + + {% if i < fr_links|length %} + | {{ fr_links[i].text }} | +{{ fr_links[i].href|slice(0, 30) }}... | + {% else %} ++ | + {% endif %} |
Texte | -URL | -
---|---|
{{ link.text }} | -{{ link.href|slice(0, 30) }}... | -
{{ group.description }}
{% endif %} + + {% if source == 'framacalc' and group.contact %} +Contact: {{ group.contact }}
+ {% endif %} + + {% if source == 'framacalc' and group.website %} ++ + Site web + +
+ {% endif %}Aucun groupe local n'a été trouvé.
diff --git a/templates/admin/wiki_random_suggestion.html.twig b/templates/admin/wiki_random_suggestion.html.twig index 3699d7c..d079448 100644 --- a/templates/admin/wiki_random_suggestion.html.twig +++ b/templates/admin/wiki_random_suggestion.html.twig @@ -3,120 +3,122 @@ {% block title %}Suggestion de page Wiki à améliorer{% endblock %} {% block body %} -Voici une page wiki qui a besoin d'être améliorée.
+{{ page.reason }}
+Voici une page wiki qui a besoin d'être améliorée.
+ +{{ page.reason }}
+- Dernière modification: {{ page.en_page.last_modified }} -
++ Dernière modification: {{ page.en_page.last_modified }} +
++ Dernière modification: {{ page.fr_page.last_modified }} +
+ {% else %} ++ Page non existante +
+ {% endif %} +La page wiki pour la clé + "{{ page.key }}" n'existe pas en français.
+Vous pouvez contribuer en créant cette page sur le wiki OpenStreetMap.
+- Dernière modification: {{ page.fr_page.last_modified }} -
- {% else %} -- Page non existante -
- {% endif %} -La page wiki pour la clé "{{ page.key }}" n'existe pas en français.
-Vous pouvez contribuer en créant cette page sur le wiki OpenStreetMap.
-Liste des changements récents dans l'espace de noms français du wiki OpenStreetMap.
+ + {% if last_updated %} +Page | +Date | +Utilisateur | +Commentaire | +Taille | +Actions | +
---|---|---|---|---|---|
+ {{ change.page_name }} + | +{{ change.timestamp }} | +{{ change.user }} | +{{ change.comment }} | +{{ change.change_size }} | ++ + Voir + + | +
Aucun changement récent n'a été trouvé.
+Cette page affiche les changements récents dans l'espace de noms français (FR:) du wiki OpenStreetMap.
+Ces informations sont utiles pour suivre les traductions manquantes et les mises à jour des pages wiki.
+Les données sont mises à jour automatiquement toutes les heures.
+