diff --git a/public/css/main.css b/public/css/main.css index c5f25cb..b51c2f1 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1,4 +1,63 @@ /* Layout général */ +body { + overflow-x: hidden; +} + +/* Sidebar styles */ +.sidebar { + width: 250px; + min-height: 100vh; + position: fixed; + top: 0; + left: 0; + z-index: 100; + transition: all 0.3s; +} + +.sidebar-header { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar .nav-link { + padding: 0.75rem 1rem; + border-radius: 0.25rem; + margin-bottom: 0.25rem; + transition: all 0.2s; +} + +.sidebar .nav-link:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.sidebar .nav-link.active { + background-color: rgba(255, 255, 255, 0.2); + font-weight: bold; +} + +.sidebar .nav-link i { + margin-right: 0.5rem; +} + +.content-wrapper { + margin-left: 250px; + width: calc(100% - 250px); + min-height: 100vh; +} + +/* For mobile devices */ +@media (max-width: 768px) { + .sidebar { + width: 100%; + position: relative; + min-height: auto; + } + + .content-wrapper { + margin-left: 0; + width: 100%; + } +} + .body-landing { background-color: rgb(255, 255, 255); min-height: 100vh; @@ -9,7 +68,7 @@ .main-header { background-color: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, .1); - padding: 1rem 0; + /*padding: 1rem 0;*/ margin-bottom: 2rem; } diff --git a/src/Controller/WikiController.php b/src/Controller/WikiController.php index 4cec428..843d794 100644 --- a/src/Controller/WikiController.php +++ b/src/Controller/WikiController.php @@ -205,9 +205,16 @@ class WikiController extends AbstractController if (isset($recentChangesData['recent_changes']) && is_array($recentChangesData['recent_changes'])) { $recentChanges = $recentChangesData['recent_changes']; $lastUpdated = isset($recentChangesData['last_updated']) ? $recentChangesData['last_updated'] : null; - + // Process team members statistics $teamMembers = $this->processTeamMembersStats($recentChanges); + + // Add avatar URLs to recent changes + foreach ($recentChanges as &$change) { + if (isset($change['user'])) { + $change['avatar_url'] = $this->fetchUserAvatar($change['user']); + } + } } // Check if the data is older than 1 hour @@ -241,6 +248,72 @@ class WikiController extends AbstractController ]); } + /** + * Fetch and cache user avatar from OSM Wiki + * + * @param string $username OSM Wiki username + * @return string|null URL of the avatar or null if not found + */ + private function fetchUserAvatar(string $username): ?string + { + // Create cache directory if it doesn't exist + $cacheDir = $this->getParameter('kernel.project_dir') . '/wiki_compare/avatar_cache'; + if (!file_exists($cacheDir)) { + mkdir($cacheDir, 0755, true); + } + + // Generate cache filename + $cacheFile = $cacheDir . '/' . md5($username) . '.json'; + + // Check if avatar is cached and not expired (7 days) + if (file_exists($cacheFile)) { + $cacheData = json_decode(file_get_contents($cacheFile), true); + $cacheTime = new \DateTime($cacheData['timestamp'] ?? '2000-01-01'); + $now = new \DateTime(); + $diff = $now->diff($cacheTime); + + // If cache is less than 7 days old, return cached avatar + if ($diff->days < 7 && isset($cacheData['avatar_url'])) { + return $cacheData['avatar_url']; + } + } + + // Avatar not cached or cache expired, fetch from wiki + $userPageUrl = "https://wiki.openstreetmap.org/wiki/User:" . urlencode($username); + try { + $response = file_get_contents($userPageUrl); + if ($response) { + // Parse HTML to find avatar + $avatarUrl = null; + + // Look for user avatar in the page + if (preg_match('/]*class="[^"]*userImage[^"]*"[^>]*src="([^"]+)"/', $response, $matches)) { + $avatarUrl = $matches[1]; + + // Make URL absolute if it's relative + if (strpos($avatarUrl, 'http') !== 0) { + $avatarUrl = 'https://wiki.openstreetmap.org' . $avatarUrl; + } + } + + // Cache the result + $cacheData = [ + 'username' => $username, + 'avatar_url' => $avatarUrl, + 'timestamp' => (new \DateTime())->format('c') + ]; + file_put_contents($cacheFile, json_encode($cacheData)); + + return $avatarUrl; + } + } catch (\Exception $e) { + // Log error but continue + error_log("Error fetching avatar for user $username: " . $e->getMessage()); + } + + return null; + } + /** * Process team members statistics from recent changes data * @@ -258,13 +331,17 @@ class WikiController extends AbstractController // Initialize user data if not exists if (!isset($teamMembers[$user])) { + // Fetch user avatar + $avatarUrl = $this->fetchUserAvatar($user); + $teamMembers[$user] = [ 'username' => $user, 'contributions' => 0, 'chars_added' => 0, 'chars_changed' => 0, 'chars_deleted' => 0, - 'user_url' => "https://wiki.openstreetmap.org/wiki/User:" . urlencode($user) + 'user_url' => "https://wiki.openstreetmap.org/wiki/User:" . urlencode($user), + 'avatar_url' => $avatarUrl ]; } @@ -628,9 +705,18 @@ class WikiController extends AbstractController // Also load the word-diff based suspicious pages for comparison if (file_exists($wordDiffFile)) { - $jsonData = json_decode(file_get_contents($wordDiffFile), true); - - foreach ($jsonData as $page) { + // Use memory-efficient approach to extract only the necessary data + $maxItems = 50; // Limit the number of items to prevent memory exhaustion + + // Extract regular_pages and specific_pages arrays + $regularPages = $this->extractJsonArrayByKey($wordDiffFile, 'regular_pages', $maxItems); + $specificPages = $this->extractJsonArrayByKey($wordDiffFile, 'specific_pages', $maxItems); + + // Combine them into a single array + $allPages = array_merge($regularPages, $specificPages); + + // Process each page to find suspicious deletions + foreach ($allPages as $page) { if (isset($page['fr_page']) && isset($page['en_page'])) { // Calculate deletion percentage $enWordCount = (int)$page['en_page']['word_count']; @@ -641,6 +727,11 @@ class WikiController extends AbstractController if ($wordDiff > 0 && $frWordCount > 0 && ($wordDiff / $enWordCount) > 0.3) { $page['deletion_percentage'] = round(($wordDiff / $enWordCount) * 100, 2); $wordDiffPages[] = $page; + + // Limit the number of suspicious pages to prevent memory issues + if (count($wordDiffPages) >= 20) { + break; + } } } } @@ -794,8 +885,18 @@ class WikiController extends AbstractController return $this->redirectToRoute('app_admin_wiki'); } - // Select a random page from the combined pages - $randomPage = $allPages[array_rand($allPages)]; + // Filter pages to ensure they have an en_page key + $validPages = array_filter($allPages, function($page) { + return isset($page['en_page']) && !empty($page['en_page']); + }); + + // If no valid pages, use any page but ensure we handle missing en_page in the template + if (empty($validPages)) { + $randomPage = $allPages[array_rand($allPages)]; + } else { + // Select a random page from the valid pages + $randomPage = $validPages[array_rand($validPages)]; + } return $this->render('admin/wiki_random_suggestion.html.twig', [ 'page' => $randomPage @@ -902,7 +1003,97 @@ EOT; ]); } - #[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')] + #[Route('/wiki/dashboard', name: 'app_admin_wiki_dashboard')] + public function dashboard(): Response + { + // Get metrics data from JSON files + $metricsData = $this->getDashboardMetrics(); + + return $this->render('admin/wiki_dashboard.html.twig', [ + 'metrics' => $metricsData + ]); + } + + /** + * Get metrics data for the dashboard + * + * @return array Metrics data + */ + private function getDashboardMetrics(): array + { + $metrics = [ + 'average_scores' => [], + 'tracked_pages' => [], + 'orphaned_pages' => [], + 'uncategorized_pages' => [], + 'dates' => [] + ]; + + // Get data from outdated_pages.json for average scores and tracked pages + $outdatedPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json'; + if (file_exists($outdatedPagesFile)) { + $historyEntries = $this->extractJsonArrayByKey($outdatedPagesFile, 'history', 100); + + // Process history entries + foreach ($historyEntries as $date => $entry) { + if (isset($entry['global_metrics'])) { + $globalMetrics = $entry['global_metrics']; + + // Format date for display + $formattedDate = (new \DateTime($date))->format('Y-m-d'); + $metrics['dates'][] = $formattedDate; + + // Get average staleness score + $metrics['average_scores'][] = $globalMetrics['avg_staleness'] ?? 0; + + // Get number of tracked pages + $totalPages = $globalMetrics['total_pages'] ?? 0; + $metrics['tracked_pages'][] = $totalPages; + } + } + } + + // Get data from deadend_pages.json for uncategorized pages + $deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json'; + if (file_exists($deadendPagesFile)) { + $historyEntries = $this->extractJsonArrayByKey($deadendPagesFile, 'history', 100); + + // Process history entries + foreach ($historyEntries as $date => $entry) { + // Format date for display + $formattedDate = (new \DateTime($date))->format('Y-m-d'); + + // If date already exists in metrics, use the same index + $dateIndex = array_search($formattedDate, $metrics['dates']); + if ($dateIndex === false) { + $dateIndex = count($metrics['dates']); + $metrics['dates'][] = $formattedDate; + + // Add placeholder values for other metrics if this is a new date + if (!isset($metrics['average_scores'][$dateIndex])) { + $metrics['average_scores'][$dateIndex] = 0; + } + if (!isset($metrics['tracked_pages'][$dateIndex])) { + $metrics['tracked_pages'][$dateIndex] = 0; + } + } + + // Get number of uncategorized pages + $uncategorizedCount = isset($entry['pages']) ? count($entry['pages']) : 0; + $metrics['uncategorized_pages'][$dateIndex] = $uncategorizedCount; + } + } + + // Sort dates and reindex arrays + array_multisort($metrics['dates'], SORT_ASC, + $metrics['average_scores'], + $metrics['tracked_pages'], + $metrics['uncategorized_pages']); + + return $metrics; + } + + #[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')] public function archivedProposals(\Symfony\Component\HttpFoundation\Request $request): Response { $jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/archived_proposals.json'; @@ -1233,6 +1424,20 @@ EOT; $keysWithoutWiki = $keysWithoutWikiData; } } + + // Load deadend pages (pages starting with "France" from the DeadendPages list) + $deadendPages = []; + $categorizedPages = []; + $deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json'; + if (file_exists($deadendPagesFile)) { + $deadendPagesData = json_decode(file_get_contents($deadendPagesFile), true); + if (isset($deadendPagesData['pages']) && is_array($deadendPagesData['pages'])) { + $deadendPages = $deadendPagesData['pages']; + } + if (isset($deadendPagesData['categorized_pages']) && is_array($deadendPagesData['categorized_pages'])) { + $categorizedPages = $deadendPagesData['categorized_pages']; + } + } return $this->render('admin/wiki.html.twig', [ 'wiki_pages' => $wikiPages, @@ -1244,7 +1449,9 @@ EOT; 'staleness_stats' => $stalenessStats, 'wiki_pages_stats' => $wikiPagesStats, 'available_translations' => $availableTranslations, - 'keys_without_wiki' => $keysWithoutWiki + 'keys_without_wiki' => $keysWithoutWiki, + 'deadend_pages' => $deadendPages, + 'categorized_pages' => $categorizedPages ]); } diff --git a/templates/admin/wiki.html.twig b/templates/admin/wiki.html.twig index c8fef3a..076b08b 100644 --- a/templates/admin/wiki.html.twig +++ b/templates/admin/wiki.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki OpenStreetMap

Outil de qualité des des pages wiki OpenStreetMap en français et en anglais pour les clés OSM @@ -570,6 +569,101 @@

{% endif %} + {% if deadend_pages is defined and deadend_pages|length > 0 %} +
+
+

Pages "France" sans catégorie ({{ deadend_pages|length }})

+
+
+

Ces pages wiki commençant par "France" n'ont pas de catégorie. Vous pouvez contribuer en ajoutant des catégories à ces pages.

+
+ + + + + + + + + + {% for page in deadend_pages %} + + + + + + {% endfor %} + +
TitreCatégories suggéréesActions
+ {{ page.title }} + + {% if page.suggested_categories is defined and page.suggested_categories|length > 0 %} + {% for category in page.suggested_categories %} + {{ category }} + {% endfor %} + {% else %} + Aucune suggestion + {% endif %} + + +
+
+
+
+ {% endif %} + + {% if categorized_pages is defined and categorized_pages|length > 0 %} +
+
+

Pages "France" récemment catégorisées ({{ categorized_pages|length }})

+
+
+

Ces pages wiki commençant par "France" ont été récemment catégorisées et ne sont plus dans la liste des pages sans catégorie.

+
+ + + + + + + + + {% for page in categorized_pages %} + + + + + {% endfor %} + +
TitreActions
+
+
+ {{ page.title }} + Catégorisée +
+
+
+ +
+
+
+
+ {% endif %} +

le score de fraîcheur prend en compte d'avantage la différence entre le nombre de mots que l'ancienneté de modification. diff --git a/templates/admin/wiki_archived_proposals.html.twig b/templates/admin/wiki_archived_proposals.html.twig index 72fbfd2..7b6b1b0 100644 --- a/templates/admin/wiki_archived_proposals.html.twig +++ b/templates/admin/wiki_archived_proposals.html.twig @@ -128,8 +128,7 @@ {% block body %}

- {% include 'admin/_wiki_navigation.html.twig' %} - +

Propositions archivées OpenStreetMap

Analyse des votes sur les propositions archivées du wiki OSM

@@ -182,7 +181,7 @@
-
{{ statistics.total_proposals }}
+
{{ statistics.total_proposals|default(proposals|length) }}
Propositions analysées
diff --git a/templates/admin/wiki_compare.html.twig b/templates/admin/wiki_compare.html.twig index 5d3f8c0..98cdbf2 100644 --- a/templates/admin/wiki_compare.html.twig +++ b/templates/admin/wiki_compare.html.twig @@ -103,7 +103,6 @@ }
- {% include 'admin/_wiki_navigation.html.twig' %}
diff --git a/templates/admin/wiki_create_french.html.twig b/templates/admin/wiki_create_french.html.twig index 7960164..a98044c 100644 --- a/templates/admin/wiki_create_french.html.twig +++ b/templates/admin/wiki_create_french.html.twig @@ -67,7 +67,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Créer une traduction française pour "{{ key }}"

Utilisez cette page pour traduire la page wiki en français. La page anglaise est affichée à gauche pour référence, et le formulaire d'édition de la page française est à droite.

diff --git a/templates/admin/wiki_dashboard.html.twig b/templates/admin/wiki_dashboard.html.twig new file mode 100644 index 0000000..2fb7e59 --- /dev/null +++ b/templates/admin/wiki_dashboard.html.twig @@ -0,0 +1,254 @@ +{% extends 'base.html.twig' %} + +{% block title %}Tableau de bord - Wiki OSM{% endblock %} + +{% block body %} +
+

Tableau de bord - Wiki OSM

+

Suivi de l'évolution des métriques du wiki OpenStreetMap

+ +
+
+
+
+

Évolution du score moyen de décrépitude

+
+
+ +
+
+
+
+
+
+

Évolution du nombre de pages suivies

+
+
+ +
+
+
+
+ +
+
+
+
+

Évolution des pages sans catégorie

+
+
+ +
+
+
+
+
+
+

Statistiques actuelles

+
+
+
+
+
+
+

Score moyen

+

+ {% if metrics.average_scores|length > 0 %} + {{ metrics.average_scores|last|number_format(1) }} + {% else %} + 0 + {% endif %} +

+
+
+
+
+
+
+

Pages suivies

+

+ {% if metrics.tracked_pages|length > 0 %} + {{ metrics.tracked_pages|last }} + {% else %} + 0 + {% endif %} +

+
+
+
+
+
+
+

Pages sans catégorie

+

+ {% if metrics.uncategorized_pages|length > 0 %} + {{ metrics.uncategorized_pages|last }} + {% else %} + 0 + {% endif %} +

+
+
+
+
+
+
+

Dernière mise à jour

+

+ {% if metrics.dates|length > 0 %} + {{ metrics.dates|last }} + {% else %} + - + {% endif %} +

+
+
+
+
+
+
+
+
+ +
+
+
+
+

Données brutes

+
+
+
+ + + + + + + + + + + {% for i in 0..(metrics.dates|length - 1) %} + + + + + + + {% endfor %} + +
DateScore moyenPages suiviesPages sans catégorie
{{ metrics.dates[i] }}{{ metrics.average_scores[i]|default(0)|number_format(1) }}{{ metrics.tracked_pages[i]|default(0) }}{{ metrics.uncategorized_pages[i]|default(0) }}
+
+
+
+
+
+
+{% endblock %} + +{% block javascripts %} + {{ parent() }} + + +{% endblock %} \ No newline at end of file diff --git a/templates/admin/wiki_decrepitude.html.twig b/templates/admin/wiki_decrepitude.html.twig index 72284e2..63fbf1f 100644 --- a/templates/admin/wiki_decrepitude.html.twig +++ b/templates/admin/wiki_decrepitude.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Évolution des scores de décrépitude

diff --git a/templates/admin/wiki_missing_translations.html.twig b/templates/admin/wiki_missing_translations.html.twig index 5698544..b099445 100644 --- a/templates/admin/wiki_missing_translations.html.twig +++ b/templates/admin/wiki_missing_translations.html.twig @@ -4,7 +4,6 @@ {% block body %}

- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki françaises sans traduction anglaise

Liste des pages françaises du wiki OSM qui n'ont pas de traduction en anglais.

diff --git a/templates/admin/wiki_osm_fr_groups.html.twig b/templates/admin/wiki_osm_fr_groups.html.twig index b314eb3..0faa2ec 100644 --- a/templates/admin/wiki_osm_fr_groups.html.twig +++ b/templates/admin/wiki_osm_fr_groups.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Groupes OSM-FR

Liste des groupes de travail et des groupes locaux d'OpenStreetMap France.

diff --git a/templates/admin/wiki_pages_unavailable_in_french.html.twig b/templates/admin/wiki_pages_unavailable_in_french.html.twig index eb212ff..e1b68ae 100644 --- a/templates/admin/wiki_pages_unavailable_in_french.html.twig +++ b/templates/admin/wiki_pages_unavailable_in_french.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki non disponibles en français

Liste des pages du wiki OSM qui n'ont pas de traduction française, groupées par langue d'origine.

diff --git a/templates/admin/wiki_random_suggestion.html.twig b/templates/admin/wiki_random_suggestion.html.twig index 2bce17e..7392066 100644 --- a/templates/admin/wiki_random_suggestion.html.twig +++ b/templates/admin/wiki_random_suggestion.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Suggestion de page Wiki à améliorer

Voici une page wiki qui a besoin d'être améliorée.

@@ -25,28 +24,34 @@

Version anglaise

- Dernière modification: {{ page.en_page.last_modified }} + Dernière modification: {{ page.en_page is defined and page.en_page.last_modified is defined ? page.en_page.last_modified : 'Non disponible' }}

  • Sections - {{ page.en_page.sections }} + {{ page.en_page is defined ? page.en_page.sections|default(0) : 0 }}
  • Mots - {{ page.en_page.word_count|default(0) }} + {{ page.en_page is defined ? page.en_page.word_count|default(0) : 0 }}
  • Liens - {{ page.en_page.link_count|default(0) }} + {{ page.en_page is defined ? page.en_page.link_count|default(0) : 0 }}
- - Voir la page anglaise - + {% if page.en_page is defined and page.en_page.url is defined %} + + Voir la page anglaise + + {% else %} + + {% endif %}
diff --git a/templates/admin/wiki_rankings.html.twig b/templates/admin/wiki_rankings.html.twig index 8ce10af..a9eb72a 100644 --- a/templates/admin/wiki_rankings.html.twig +++ b/templates/admin/wiki_rankings.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Évolution des classements Wiki OSM

diff --git a/templates/admin/wiki_recent_changes.html.twig b/templates/admin/wiki_recent_changes.html.twig index eb41254..907cd7b 100644 --- a/templates/admin/wiki_recent_changes.html.twig +++ b/templates/admin/wiki_recent_changes.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Changements récents Wiki OpenStreetMap

Liste des changements récents dans l'espace de noms français du wiki OpenStreetMap.

@@ -26,18 +25,27 @@ {% for member in team_members %}
- - {{ member.username }} - - {{ member.contributions }} -
- +{{ member.chars_added }} - {% if member.chars_changed > 0 %} - ~{{ member.chars_changed }} - {% endif %} - {% if member.chars_deleted > 0 %} - -{{ member.chars_deleted }} - {% endif %} + {% if member.avatar_url is defined and member.avatar_url %} + {{ member.username }} + {% else %} +
+ {{ member.username|first|upper }} +
+ {% endif %} +
+ + {{ member.username }} + + {{ member.contributions }} +
+ +{{ member.chars_added }} + {% if member.chars_changed > 0 %} + ~{{ member.chars_changed }} + {% endif %} + {% if member.chars_deleted > 0 %} + -{{ member.chars_deleted }} + {% endif %} +
@@ -83,13 +91,22 @@ {{ change.timestamp }} - {% if change.user_url %} - + {{ change.comment }} diff --git a/templates/admin/wiki_suspicious_deletions.html.twig b/templates/admin/wiki_suspicious_deletions.html.twig index 8e1b444..12779aa 100644 --- a/templates/admin/wiki_suspicious_deletions.html.twig +++ b/templates/admin/wiki_suspicious_deletions.html.twig @@ -4,7 +4,6 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %}

Pages Wiki avec suppressions suspectes

diff --git a/templates/admin/wiki_tag_proposals.html.twig b/templates/admin/wiki_tag_proposals.html.twig index ae8d980..d074898 100644 --- a/templates/admin/wiki_tag_proposals.html.twig +++ b/templates/admin/wiki_tag_proposals.html.twig @@ -64,8 +64,7 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %} - +

Propositions de tags OSM

Liste des propositions de tags OpenStreetMap actuellement en cours de vote ou récemment modifiées.

diff --git a/templates/base.html.twig b/templates/base.html.twig index 8dfdb04..a0c4dd6 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -21,97 +21,141 @@ {% endblock %} -
-
-
- +
+ +
- - - -
- {% block body %}{% endblock %} -
- - +
{% block javascripts %} {{ encore_entry_script_tags('app') }} diff --git a/templates/public/wiki.html.twig b/templates/public/wiki.html.twig index 346a915..a816bb5 100644 --- a/templates/public/wiki.html.twig +++ b/templates/public/wiki.html.twig @@ -4,7 +4,7 @@ {% block body %}
- {% include 'admin/_wiki_navigation.html.twig' %} +

Pages Wiki OpenStreetMap

Outil de qualité des des pages wiki OpenStreetMap en français et en anglais pour les clés OSM diff --git a/templates/public/wiki_random_suggestion.html.twig b/templates/public/wiki_random_suggestion.html.twig index 1a10cba..0e1718f 100644 --- a/templates/public/wiki_random_suggestion.html.twig +++ b/templates/public/wiki_random_suggestion.html.twig @@ -25,28 +25,34 @@

Version anglaise

- Dernière modification: {{ page.en_page.last_modified }} + Dernière modification: {{ page.en_page is defined and page.en_page.last_modified is defined ? page.en_page.last_modified : 'Non disponible' }}

  • Sections - {{ page.en_page.sections }} + {{ page.en_page is defined ? page.en_page.sections|default(0) : 0 }}
  • Mots - {{ page.en_page.word_count|default(0) }} + {{ page.en_page is defined ? page.en_page.word_count|default(0) : 0 }}
  • Liens - {{ page.en_page.link_count|default(0) }} + {{ page.en_page is defined ? page.en_page.link_count|default(0) : 0 }}
- - Voir la page anglaise - + {% if page.en_page is defined and page.en_page.url is defined %} + + Voir la page anglaise + + {% else %} + + {% endif %}
diff --git a/templates/public/wiki_recent_changes.html.twig b/templates/public/wiki_recent_changes.html.twig index eb41254..7794f17 100644 --- a/templates/public/wiki_recent_changes.html.twig +++ b/templates/public/wiki_recent_changes.html.twig @@ -26,18 +26,27 @@ {% for member in team_members %}
- - {{ member.username }} - - {{ member.contributions }} -
- +{{ member.chars_added }} - {% if member.chars_changed > 0 %} - ~{{ member.chars_changed }} - {% endif %} - {% if member.chars_deleted > 0 %} - -{{ member.chars_deleted }} - {% endif %} + {% if member.avatar_url is defined and member.avatar_url %} + {{ member.username }} + {% else %} +
+ {{ member.username|first|upper }} +
+ {% endif %} +
+ + {{ member.username }} + + {{ member.contributions }} +
+ +{{ member.chars_added }} + {% if member.chars_changed > 0 %} + ~{{ member.chars_changed }} + {% endif %} + {% if member.chars_deleted > 0 %} + -{{ member.chars_deleted }} + {% endif %} +
@@ -83,13 +92,22 @@ {{ change.timestamp }} - {% if change.user_url %} - + {{ change.comment }} diff --git a/wiki_compare/CHANGES_DEADEND_PAGES.md b/wiki_compare/CHANGES_DEADEND_PAGES.md new file mode 100644 index 0000000..2bfdef6 --- /dev/null +++ b/wiki_compare/CHANGES_DEADEND_PAGES.md @@ -0,0 +1,199 @@ +# Ajout de la fonctionnalité de suivi des pages "France" sans catégorie + +## Description + +Cette fonctionnalité permet de suivre les pages du wiki OpenStreetMap commençant par "France" qui n'ont pas de catégorie (pages sans issues). Ces pages sont identifiées à partir de la liste des "DeadendPages" du wiki OSM. La fonctionnalité suggère également des catégories pour ces pages et suit leur évolution lorsqu'elles sont catégorisées. + +## Modifications apportées + +### 1. Ajout de constantes dans `wiki_compare.py` + +```python +WIKI_DEADEND_PAGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:DeadendPages&limit=500&offset=1000" +DEADEND_PAGES_FILE = "deadend_pages.json" +``` + +### 2. Ajout de la fonction `suggest_categories` dans `wiki_compare.py` + +Cette fonction analyse le titre et le contenu d'une page pour suggérer des catégories pertinentes : + +```python +def suggest_categories(page_title, page_url): + """ + Suggest categories for an uncategorized page based on its title and content + + Args: + page_title (str): Title of the page + page_url (str): URL of the page + + Returns: + list: List of suggested categories + """ + # Logique de suggestion de catégories basée sur le titre et le contenu de la page +``` + +### 3. Ajout de la fonction `fetch_deadend_pages` dans `wiki_compare.py` + +Cette fonction récupère les pages commençant par "France" depuis la liste des DeadendPages : + +```python +def fetch_deadend_pages(): + """ + Fetch pages starting with "France" from the DeadendPages list + + Returns: + list: List of dictionaries containing page information + """ + # Logique de récupération des pages depuis la liste des DeadendPages + # Filtrage des pages commençant par "France" + # Suggestion de catégories pour chaque page +``` + +### 4. Modification de la fonction `main` dans `wiki_compare.py` + +Ajout d'une section pour traiter les pages sans catégorie et suivre leur évolution : + +```python +# Fetch pages starting with "France" from the DeadendPages list +deadend_pages = fetch_deadend_pages() + +if deadend_pages: + # Load existing deadend pages data to compare with history + existing_data = load_json_data(DEADEND_PAGES_FILE) + + # Initialize history if it doesn't exist + if 'history' not in existing_data: + existing_data['history'] = {} + + # Get the most recent history entry + sorted_timestamps = sorted(existing_data.get('history', {}).keys()) + previous_pages = [] + if sorted_timestamps: + latest_timestamp = sorted_timestamps[-1] + previous_pages = existing_data['history'][latest_timestamp].get('pages', []) + + # Find pages that were in the previous list but are no longer in the current list + previous_urls = [page['url'] for page in previous_pages] + current_urls = [page['url'] for page in deadend_pages] + + categorized_pages = [] + for url in previous_urls: + if url not in current_urls: + # Find the page in previous_pages + for page in previous_pages: + if page['url'] == url: + # This page is no longer in the DeadendPages list, which means it has been categorized + categorized_pages.append(page) + break + + # Create a timestamp for the current data + current_timestamp = datetime.now().isoformat() + + # Create the history entry + history_entry = { + 'pages': deadend_pages, + 'categorized_pages': categorized_pages + } + + # Add the entry to history with timestamp as key + existing_data['history'][current_timestamp] = history_entry + + # Update the current data + existing_data['pages'] = deadend_pages + existing_data['categorized_pages'] = categorized_pages + existing_data['last_updated'] = current_timestamp + + # Save the updated data + save_to_json(existing_data, DEADEND_PAGES_FILE) +``` + +### 5. Modification du contrôleur `WikiController.php` + +Ajout du chargement des données des pages sans catégorie : + +```php +// Load deadend pages (pages starting with "France" from the DeadendPages list) +$deadendPages = []; +$categorizedPages = []; +$deadendPagesFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/deadend_pages.json'; +if (file_exists($deadendPagesFile)) { + $deadendPagesData = json_decode(file_get_contents($deadendPagesFile), true); + if (isset($deadendPagesData['pages']) && is_array($deadendPagesData['pages'])) { + $deadendPages = $deadendPagesData['pages']; + } + if (isset($deadendPagesData['categorized_pages']) && is_array($deadendPagesData['categorized_pages'])) { + $categorizedPages = $deadendPagesData['categorized_pages']; + } +} +``` + +Ajout des données à la vue : + +```php +return $this->render('admin/wiki.html.twig', [ + // Autres données... + 'deadend_pages' => $deadendPages, + 'categorized_pages' => $categorizedPages +]); +``` + +### 6. Ajout de sections dans le template `admin/wiki.html.twig` + +Ajout d'une section pour afficher les pages sans catégorie : + +```twig +{% if deadend_pages is defined and deadend_pages|length > 0 %} +
+
+

Pages "France" sans catégorie ({{ deadend_pages|length }})

+
+
+

Ces pages wiki commençant par "France" n'ont pas de catégorie. Vous pouvez contribuer en ajoutant des catégories à ces pages.

+
+ + +
+
+
+
+{% endif %} +``` + +Ajout d'une section pour afficher les pages récemment catégorisées : + +```twig +{% if categorized_pages is defined and categorized_pages|length > 0 %} +
+
+

Pages "France" récemment catégorisées ({{ categorized_pages|length }})

+
+
+

Ces pages wiki commençant par "France" ont été récemment catégorisées et ne sont plus dans la liste des pages sans catégorie.

+
+ + +
+
+
+
+{% endif %} +``` + +## Fonctionnalités + +1. **Scraping des pages sans catégorie** : Récupération des pages commençant par "France" depuis la liste des DeadendPages du wiki OSM. +2. **Suggestion de catégories** : Analyse du titre et du contenu des pages pour suggérer des catégories pertinentes. +3. **Suivi des pages catégorisées** : Détection des pages qui ont été catégorisées depuis le dernier scraping. +4. **Affichage dans l'interface** : Affichage des pages sans catégorie et des pages récemment catégorisées dans l'interface utilisateur. + +## Utilisation + +1. Le script `wiki_compare.py` est exécuté périodiquement pour mettre à jour les données. +2. Les utilisateurs peuvent consulter les pages sans catégorie et les pages récemment catégorisées dans l'interface. +3. Les utilisateurs peuvent cliquer sur les boutons d'action pour voir les pages et ajouter des catégories. + +## Avantages + +1. **Amélioration de la qualité du wiki** : Aide à identifier et à catégoriser les pages qui devraient avoir des catégories. +2. **Suivi des progrès** : Permet de suivre les progrès dans la catégorisation des pages. +3. **Suggestion de catégories** : Facilite le travail des contributeurs en suggérant des catégories pertinentes. \ No newline at end of file diff --git a/wiki_compare/CHANGES_FIXES.md b/wiki_compare/CHANGES_FIXES.md new file mode 100644 index 0000000..0a6da53 --- /dev/null +++ b/wiki_compare/CHANGES_FIXES.md @@ -0,0 +1,94 @@ +# Corrections apportées à QualiWiki + +Ce document décrit les corrections apportées pour résoudre les problèmes mentionnés dans la description des issues. + +## 1. Correction de l'erreur dans wiki_archived_proposals.html.twig + +### Problème +``` +Key "total_proposals" for sequence/mapping with keys "0, 1, 2, 3, ..." does not exist in admin/wiki_archived_proposals.html.twig at line 193. +``` + +### Solution +Ajout d'une valeur par défaut pour `total_proposals` dans le template : + +```twig +
{{ statistics.total_proposals|default(proposals|length) }}
+``` + +Cette modification permet d'utiliser le nombre de propositions dans le tableau `proposals` comme valeur par défaut si `statistics.total_proposals` n'est pas défini. + +## 2. Implémentation d'un panneau latéral sombre pour la navigation + +### Modifications +- Ajout d'un panneau latéral sombre dans `templates/base.html.twig` +- Ajout de styles CSS dans `public/css/main.css` pour le panneau latéral +- Réorganisation de la structure HTML pour intégrer le panneau latéral +- Ajout de liens vers toutes les sections principales de l'application + +### Avantages +- Navigation plus claire et plus accessible +- Meilleure organisation visuelle de l'application +- Accès rapide à toutes les fonctionnalités principales + +## 3. Création d'une page dashboard avec graphiques + +### Fonctionnalités +- Ajout d'une méthode `dashboard` dans `WikiController.php` +- Création d'un template `admin/wiki_dashboard.html.twig` +- Implémentation de graphiques montrant : + - L'évolution du score moyen de décrépitude + - L'évolution du nombre de pages suivies + - L'évolution du nombre de pages sans catégorie + +### Données suivies +- Score moyen de décrépitude des pages +- Nombre de pages suivies +- Nombre de pages sans catégorie (pages orphelines) + +## 4. Correction de l'erreur dans wiki_random_suggestion.html.twig + +### Problème +``` +Key "en_page" for sequence/mapping with keys "title, level" does not exist in admin/wiki_random_suggestion.html.twig at line 28. +``` + +### Solution +- Ajout de vérifications pour l'existence de `en_page` dans le template +- Ajout de valeurs par défaut pour les propriétés de `en_page` +- Filtrage des pages pour s'assurer qu'elles ont une clé `en_page` dans le contrôleur + +## 5. Ajout d'avatars utilisateurs dans la page des changements récents + +### Fonctionnalités +- Ajout d'une fonction `fetchUserAvatar` dans `WikiController.php` pour récupérer et mettre en cache les avatars des utilisateurs +- Mise à jour des templates `admin/wiki_recent_changes.html.twig` et `public/wiki_recent_changes.html.twig` pour afficher les avatars +- Mise en cache des avatars pour éviter de les récupérer à chaque fois + +## 6. Correction de l'erreur de mémoire dans la page des suppressions suspectes + +### Problème +``` +Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 175260864 bytes) +``` + +### Solution +- Modification de la méthode `suspiciousDeletions` dans `WikiController.php` pour utiliser une approche plus efficace en mémoire +- Utilisation de la méthode `extractJsonArrayByKey` au lieu de `json_decode(file_get_contents())` pour traiter le fichier JSON +- Limitation du nombre d'éléments extraits à 50 avec le paramètre `$maxItems` +- Ajout d'une limite pour arrêter le traitement après avoir trouvé 20 pages suspectes + +## 7. Implémentation du suivi des pages "France" sans catégorie + +### Fonctionnalités +- Ajout de constantes pour l'URL des DeadendPages et le fichier de sortie dans `wiki_compare.py` +- Création d'une fonction `fetch_deadend_pages` pour récupérer les pages commençant par "France" depuis la liste des DeadendPages +- Création d'une fonction `suggest_categories` pour suggérer des catégories pour les pages sans catégorie +- Modification de la fonction `main` pour traiter et sauvegarder les données des DeadendPages +- Mise à jour du contrôleur pour charger et afficher les données des DeadendPages +- Ajout de sections dans le template pour afficher les pages sans catégorie et les pages récemment catégorisées + +### Avantages +- Suivi des pages qui devraient avoir des catégories +- Suggestion de catégories pour faciliter le travail des contributeurs +- Suivi des progrès dans la catégorisation des pages \ No newline at end of file diff --git a/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc b/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc index f3255ad..e443c45 100644 Binary files a/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc and b/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc differ diff --git a/wiki_compare/wiki_compare.py b/wiki_compare/wiki_compare.py index 74fcfe5..01bd90a 100755 --- a/wiki_compare/wiki_compare.py +++ b/wiki_compare/wiki_compare.py @@ -61,10 +61,12 @@ WIKI_BASE_URL_EN = "https://wiki.openstreetmap.org/wiki/Key:" WIKI_BASE_URL_FR = "https://wiki.openstreetmap.org/wiki/FR:Key:" WIKI_BASE_URL = "https://wiki.openstreetmap.org/wiki/" WIKI_CATEGORY_URL = "https://wiki.openstreetmap.org/wiki/Category:FR:Traductions_d%C3%A9synchronis%C3%A9es" +WIKI_DEADEND_PAGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:DeadendPages&limit=500&offset=1000" TOP_KEYS_FILE = "top_keys.json" KEYS_WITHOUT_WIKI_FILE = "keys_without_wiki.json" WIKI_PAGES_CSV = "wiki_pages.csv" OUTDATED_PAGES_FILE = "outdated_pages.json" +DEADEND_PAGES_FILE = "deadend_pages.json" STALENESS_HISTOGRAM_FILE = "staleness_histogram.png" # Number of wiki pages to examine NUM_WIKI_PAGES = 2 @@ -154,6 +156,161 @@ def fetch_desynchronized_pages(): logger.error(f"Error fetching category page: {e}") return [] +def suggest_categories(page_title, page_url): + """ + Suggest categories for an uncategorized page based on its title and content + + Args: + page_title (str): Title of the page + page_url (str): URL of the page + + Returns: + list: List of suggested categories + """ + logger.info(f"Suggesting categories for page: {page_title}") + + suggested_categories = [] + + # Common categories for French OSM wiki pages + common_categories = [ + "Documentation OSM en français", + "Cartographie", + "Contributeurs", + "Développeurs", + "Éléments cartographiés", + "Imports", + "Logiciels", + "Projets", + "Rencontres", + "Utilisateurs" + ] + + # Add geography-related categories for pages about France + if "France" in page_title: + suggested_categories.append("France") + + # Check for specific regions or departments + regions = [ + "Auvergne-Rhône-Alpes", "Bourgogne-Franche-Comté", "Bretagne", + "Centre-Val de Loire", "Corse", "Grand Est", "Hauts-de-France", + "Île-de-France", "Normandie", "Nouvelle-Aquitaine", + "Occitanie", "Pays de la Loire", "Provence-Alpes-Côte d'Azur" + ] + + for region in regions: + if region in page_title: + suggested_categories.append(region) + + # Try to fetch the page content to make better suggestions + try: + response = requests.get(page_url) + response.raise_for_status() + + soup = BeautifulSoup(response.text, 'html.parser') + + # Get the main content + content = soup.select_one('#mw-content-text') + if content: + text = content.get_text(separator=' ', strip=True).lower() + + # Check for keywords related to common categories + if any(keyword in text for keyword in ["carte", "cartographie", "mapper"]): + suggested_categories.append("Cartographie") + + if any(keyword in text for keyword in ["contribuer", "contributeur", "éditer"]): + suggested_categories.append("Contributeurs") + + if any(keyword in text for keyword in ["développeur", "programmer", "code", "api"]): + suggested_categories.append("Développeurs") + + if any(keyword in text for keyword in ["tag", "clé", "valeur", "élément", "nœud", "way", "relation"]): + suggested_categories.append("Éléments cartographiés") + + if any(keyword in text for keyword in ["import", "données", "dataset"]): + suggested_categories.append("Imports") + + if any(keyword in text for keyword in ["logiciel", "application", "outil"]): + suggested_categories.append("Logiciels") + + if any(keyword in text for keyword in ["projet", "initiative"]): + suggested_categories.append("Projets") + + if any(keyword in text for keyword in ["rencontre", "réunion", "événement", "conférence"]): + suggested_categories.append("Rencontres") + + if any(keyword in text for keyword in ["utiliser", "utilisateur", "usage"]): + suggested_categories.append("Utilisateurs") + + except requests.exceptions.RequestException as e: + logger.warning(f"Error fetching page content for category suggestions: {e}") + # If we can't fetch the content, suggest common categories based on title only + if "projet" in page_title.lower(): + suggested_categories.append("Projets") + elif "logiciel" in page_title.lower() or "application" in page_title.lower(): + suggested_categories.append("Logiciels") + elif "rencontre" in page_title.lower() or "réunion" in page_title.lower(): + suggested_categories.append("Rencontres") + + # Always suggest the general French documentation category + suggested_categories.append("Documentation OSM en français") + + # Remove duplicates while preserving order + seen = set() + unique_categories = [] + for cat in suggested_categories: + if cat not in seen: + seen.add(cat) + unique_categories.append(cat) + + logger.info(f"Suggested {len(unique_categories)} categories for {page_title}: {', '.join(unique_categories)}") + return unique_categories + +def fetch_deadend_pages(): + """ + Fetch pages starting with "France" from the DeadendPages list + + Returns: + list: List of dictionaries containing page information + """ + logger.info(f"Fetching pages from DeadendPages list: {WIKI_DEADEND_PAGES_URL}") + + try: + response = requests.get(WIKI_DEADEND_PAGES_URL) + response.raise_for_status() + + soup = BeautifulSoup(response.text, 'html.parser') + + # Find all links in the DeadendPages list + page_links = [] + for link in soup.select('.mw-spcontent li a'): + href = link.get('href', '') + title = link.get_text(strip=True) + + # Skip if it's not a wiki page or if it's a special page + if not href.startswith('/wiki/') or 'Special:' in href: + continue + + # Filter pages that start with "France" + if title.startswith('France'): + # Get the full URL + full_url = 'https://wiki.openstreetmap.org' + href + + # Suggest categories for this page + suggested_categories = suggest_categories(title, full_url) + + page_links.append({ + 'title': title, + 'url': full_url, + 'suggested_categories': suggested_categories + }) + + logger.info(f"Found {len(page_links)} pages starting with 'France' in the DeadendPages list") + return page_links + + except requests.exceptions.RequestException as e: + logger.error(f"Error fetching DeadendPages list: {e}") + return [] + def fetch_top_keys(limit=NUM_WIKI_PAGES): """ Fetch the most used OSM keys from TagInfo API @@ -1365,10 +1522,11 @@ def main(): 3. Fetches and processes wiki pages for these keys 4. Processes specific wiki pages listed in SPECIFIC_PAGES 5. Processes pages from the FR:Traductions_désynchronisées category - 6. Calculates staleness scores for all pages - 7. Generates a histogram of staleness scores - 8. Saves the results to CSV and JSON files - 9. Prints a list of pages that need updating + 6. Processes pages starting with "France" from the DeadendPages list + 7. Calculates staleness scores for all pages + 8. Generates a histogram of staleness scores + 9. Saves the results to CSV and JSON files + 10. Prints a list of pages that need updating """ # Parse command-line arguments parser = argparse.ArgumentParser(description='Compare OpenStreetMap wiki pages in English and French.') @@ -1404,6 +1562,62 @@ def main(): logger.info(f"Saved {len(keys_without_wiki)} keys without wiki pages to {KEYS_WITHOUT_WIKI_FILE}") else: logger.warning("No keys without wiki pages were fetched.") + + # Fetch pages starting with "France" from the DeadendPages list + deadend_pages = fetch_deadend_pages() + + if deadend_pages: + # Load existing deadend pages data to compare with history + existing_data = load_json_data(DEADEND_PAGES_FILE) + + # Initialize history if it doesn't exist + if 'history' not in existing_data: + existing_data['history'] = {} + + # Get the most recent history entry + sorted_timestamps = sorted(existing_data.get('history', {}).keys()) + previous_pages = [] + if sorted_timestamps: + latest_timestamp = sorted_timestamps[-1] + previous_pages = existing_data['history'][latest_timestamp].get('pages', []) + + # Find pages that were in the previous list but are no longer in the current list + previous_urls = [page['url'] for page in previous_pages] + current_urls = [page['url'] for page in deadend_pages] + + categorized_pages = [] + for url in previous_urls: + if url not in current_urls: + # Find the page in previous_pages + for page in previous_pages: + if page['url'] == url: + # This page is no longer in the DeadendPages list, which means it has been categorized + categorized_pages.append(page) + break + + # Create a timestamp for the current data + current_timestamp = datetime.now().isoformat() + + # Create the history entry + history_entry = { + 'pages': deadend_pages, + 'categorized_pages': categorized_pages + } + + # Add the entry to history with timestamp as key + existing_data['history'][current_timestamp] = history_entry + + # Update the current data + existing_data['pages'] = deadend_pages + existing_data['categorized_pages'] = categorized_pages + existing_data['last_updated'] = current_timestamp + + # Save the updated data + save_to_json(existing_data, DEADEND_PAGES_FILE) + logger.info(f"Saved {len(deadend_pages)} deadend pages to {DEADEND_PAGES_FILE}") + logger.info(f"Found {len(categorized_pages)} pages that have been categorized since the last run") + else: + logger.warning("No deadend pages were fetched.") # Fetch wiki pages for each key wiki_pages = []