diff --git a/.deploy.example.env b/.deploy.example.env index 4194ff2..24d31b2 100644 --- a/.deploy.example.env +++ b/.deploy.example.env @@ -3,11 +3,28 @@ # .deploy.env wird NICHT ins Git-Repository übernommen. # SSH-Verbindung -DEPLOY_HOST=deine-subdomain.bitpalast.de -DEPLOY_USER=dein-ssh-user +DEPLOY_HOST=deine-server-ip-oder-domain +DEPLOY_USER=root # Absoluter Pfad zu mediawiki/ auf dem Server -DEPLOY_MW_PATH=/var/www/html/gplx/mediawiki +DEPLOY_MW_PATH=/var/www/gplx/mediawiki # Absoluter Pfad zum Projektverzeichnis (eine Ebene über mediawiki/) -DEPLOY_PROJECT_PATH=/var/www/html/gplx +DEPLOY_PROJECT_PATH=/var/www/gplx + +# PHP auf dem Server (Standard: php) +# DEPLOY_PHP=php + +# MediaWiki-Admin-Benutzername für Seiten-Edits (Standard: Gxplex admin) +# DEPLOY_MW_USER=Gxplex admin + +# ── Datenbank (nur für -Mode db) ───────────────────────────────────────────── + +# Datenbank auf dem Server +DEPLOY_DB_NAME=gplx +DEPLOY_DB_USER=gplx +DEPLOY_DB_PASS=dein-db-passwort + +# Lokale Datenbank (Standard: gflex_test / root ohne Passwort) +# LOCAL_DB_NAME=gflex_test +# LOCAL_MYSQLDUMP=D:\xampp\mysql\bin\mysqldump.exe diff --git a/mediawiki/resources/assets/GxPlex-Logo.png b/mediawiki/resources/assets/GxPlex-Logo.png new file mode 100644 index 0000000..8764106 Binary files /dev/null and b/mediawiki/resources/assets/GxPlex-Logo.png differ diff --git a/scripts/deploy.ps1 b/scripts/deploy.ps1 index bce9c11..ce8d4b8 100644 --- a/scripts/deploy.ps1 +++ b/scripts/deploy.ps1 @@ -1,27 +1,57 @@ <# .SYNOPSIS - Deployt GPLX MediaWiki auf den Bitpalast-Server. + Deployt GPLX MediaWiki auf den IONOS-Server. .PARAMETER Mode - skin - Cavendish-Skin synchronisieren (Standard, schnell) - templates - Wiki-Seiten-Vorlagen synchronisieren - all - Skin + Templates - initial - Erstinstallation: gesamten MediaWiki-Core übertragen + skin – Cavendish-Skin synchronisieren (schnell) + pages – Alle Wiki-Seiten aus scripts/templates/ auf Server pushen + db – Lokalen DB-Dump erstellen und auf Server importieren + all – Skin + Pages + initial – Erstinstallation: gesamten MediaWiki-Core übertragen .EXAMPLE .\deploy.ps1 # Skin deployen - .\deploy.ps1 -Mode all # Skin + Templates + .\deploy.ps1 -Mode pages # Alle Wiki-Seiten pushen + .\deploy.ps1 -Mode db # DB synchronisieren + .\deploy.ps1 -Mode all # Skin + Pages .\deploy.ps1 -Mode initial #> param( - [ValidateSet('skin', 'templates', 'all', 'initial')] + [ValidateSet('skin', 'pages', 'db', 'all', 'initial')] [string]$Mode = 'skin' ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' -# === Config laden === +# ── Mapping: Dateiname in scripts/templates/ → Wiki-Seitentitel ────────────── +# Reihenfolge ist relevant: MediaWiki-Systemseiten zuerst, dann Templates, +# dann Inhaltsseiten. +$PAGE_MAP = [ordered]@{ + 'Sidebar.wiki' = 'MediaWiki:Sidebar' + 'Common_js.wiki' = 'MediaWiki:Common.js' + 'Editnotice_Advertisement.wiki' = 'MediaWiki:Editnotice-MediaWiki:Advertisement' + 'Template_DocumentBox.wiki' = 'Template:DocumentBox' + 'Template_ReferenceBox.wiki' = 'Template:ReferenceBox' + 'Cat_Clinical.wiki' = 'Category:Clinical' + 'Cat_EU_Regulation.wiki' = 'Category:EU Regulation' + 'Cat_IVD.wiki' = 'Category:In Vitro Diagnostics' + 'Cat_Labelling.wiki' = 'Category:Labelling' + 'Cat_MedicalDevices.wiki' = 'Category:Medical Devices' + 'Cat_PerformanceEvaluation.wiki' = 'Category:Performance Evaluation' + 'Cat_PostMarket.wiki' = 'Category:Post-Market Surveillance' + 'Cat_Traceability.wiki' = 'Category:Traceability' + 'About_GxPlex.wiki' = 'About GxPlex' + 'Contribute_to_GxPlex.wiki' = 'Contribute to GxPlex' + 'Create_new_page.wiki' = 'Create new page' + 'GxPlex_Search.wiki' = 'GxPlex Search' + 'Help_Adding_a_topic.wiki' = 'Help:Adding a topic' + 'Roadmap.wiki' = 'Development Roadmap' + 'The_Project.wiki' = 'The Project' + 'Themes_and_topics.wiki' = 'Themes and topics' +} + +# ── .deploy.env laden ───────────────────────────────────────────────────────── $envFile = Join-Path $PSScriptRoot '..' '.deploy.env' if (-not (Test-Path $envFile)) { Write-Error @" @@ -37,56 +67,58 @@ Get-Content $envFile | ForEach-Object { } } -$deployHost = $env:DEPLOY_HOST -$deployUser = $env:DEPLOY_USER -$mwPath = $env:DEPLOY_MW_PATH -$projectPath = $env:DEPLOY_PROJECT_PATH -$target = "${deployUser}@${deployHost}" - -foreach ($var in @('DEPLOY_HOST','DEPLOY_USER','DEPLOY_MW_PATH')) { +# Pflichtfelder prüfen +foreach ($var in @('DEPLOY_HOST', 'DEPLOY_USER', 'DEPLOY_MW_PATH', 'DEPLOY_PROJECT_PATH')) { if (-not [System.Environment]::GetEnvironmentVariable($var, 'Process')) { - Write-Error "$var ist nicht in .deploy.env gesetzt." + Write-Error "$var fehlt in .deploy.env." exit 1 } } -# === WSL rsync oder scp === +$deployHost = $env:DEPLOY_HOST +$deployUser = $env:DEPLOY_USER +$mwPath = $env:DEPLOY_MW_PATH +$projectPath = $env:DEPLOY_PROJECT_PATH +$serverPhp = if ($env:DEPLOY_PHP) { $env:DEPLOY_PHP } else { 'php' } +$mwUser = if ($env:DEPLOY_MW_USER) { $env:DEPLOY_MW_USER } else { 'Gxplex admin' } +$target = "${deployUser}@${deployHost}" +$repoRoot = Split-Path $PSScriptRoot + +# ── Hilfsfunktionen ─────────────────────────────────────────────────────────── $hasWsl = $null -ne (Get-Command 'wsl' -ErrorAction SilentlyContinue) function ConvertTo-WslPath([string]$winPath) { - $abs = [System.IO.Path]::GetFullPath($winPath) + $abs = [System.IO.Path]::GetFullPath($winPath) $drive = $abs.Substring(0, 1).ToLower() $rest = $abs.Substring(2).Replace('\', '/') return "/mnt/$drive$rest" } function Sync-Dir([string]$localDir, [string]$remoteDir, [bool]$delete = $false) { - Write-Host " $localDir" -ForegroundColor DarkGray - Write-Host " -> ${target}:${remoteDir}" -ForegroundColor DarkGray - + Write-Host " $localDir → ${target}:${remoteDir}" -ForegroundColor DarkGray if ($hasWsl) { - $wslSrc = (ConvertTo-WslPath $localDir) + '/' + $wslSrc = (ConvertTo-WslPath $localDir) + '/' $rsyncArgs = @('-az', '--progress') if ($delete) { $rsyncArgs += '--delete' } - $rsyncArgs += $wslSrc - $rsyncArgs += "${target}:${remoteDir}/" + $rsyncArgs += @($wslSrc, "${target}:${remoteDir}/") wsl rsync @rsyncArgs } else { - # Fallback: scp (kein automatisches Loeschen von Remotedateien) - Write-Warning "WSL nicht gefunden – nutze scp. Geloeschte lokale Dateien bleiben auf dem Server." + Write-Warning "WSL nicht gefunden – nutze scp." ssh $target "mkdir -p '$remoteDir'" - scp -r "${localDir}\." "${target}:${remoteDir}/" - } - - if ($LASTEXITCODE -ne 0) { - Write-Error "Uebertragung fehlgeschlagen (Exit $LASTEXITCODE)." - exit $LASTEXITCODE + scp -r "${localDir}\*" "${target}:${remoteDir}/" } + if ($LASTEXITCODE -ne 0) { Write-Error "Übertragung fehlgeschlagen (Exit $LASTEXITCODE)."; exit $LASTEXITCODE } } -$repoRoot = Split-Path $PSScriptRoot +function Push-WikiPage([string]$remoteFile, [string]$title) { + # Titel für einfache Shell-Quoting escapen: einfache Anführungszeichen verdoppeln + $safeTitle = $title -replace "'", "'\"'\"'" + $cmd = "cat '$remoteFile' | $serverPhp $mwPath/maintenance/run.php edit --user '$mwUser' --summary 'Deploy' '$safeTitle' 2>&1" + $out = ssh $target $cmd + return $LASTEXITCODE -eq 0 +} -# === Deploy-Modi === +# ── Deploy-Modi ─────────────────────────────────────────────────────────────── switch ($Mode) { 'skin' { @@ -95,27 +127,102 @@ switch ($Mode) { Write-Host "Fertig." -ForegroundColor Green } - 'templates' { - if (-not $projectPath) { - Write-Error "DEPLOY_PROJECT_PATH muss in .deploy.env gesetzt sein." + 'pages' { + Write-Host "Deploye Wiki-Seiten ($($PAGE_MAP.Count) Seiten)..." -ForegroundColor Cyan + + # 1. Template-Dateien auf Server synchronisieren + Write-Host "`n[1/2] Dateien übertragen..." -ForegroundColor DarkGray + Sync-Dir "$repoRoot\scripts\templates" "$projectPath/scripts/templates" + + # 2. Jede Seite per Maintenance-Script einspielen + Write-Host "`n[2/2] Seiten pushen..." -ForegroundColor DarkGray + $ok = 0; $fail = 0; $skip = 0 + + foreach ($entry in $PAGE_MAP.GetEnumerator()) { + $localFile = Join-Path "$repoRoot\scripts\templates" $entry.Key + $remoteFile = "$projectPath/scripts/templates/$($entry.Key)" + $title = $entry.Value + + if (-not (Test-Path $localFile)) { + Write-Host " SKIP $($entry.Key) (Datei nicht gefunden)" -ForegroundColor DarkYellow + $skip++ + continue + } + + Write-Host -NoNewline " → $title ... " + if (Push-WikiPage $remoteFile $title) { + Write-Host "OK" -ForegroundColor Green + $ok++ + } else { + Write-Host "FEHLER" -ForegroundColor Red + $fail++ + } + } + + Write-Host "" + $color = if ($fail -eq 0) { 'Green' } else { 'Yellow' } + Write-Host "Ergebnis: $ok OK | $fail Fehler | $skip übersprungen" -ForegroundColor $color + } + + 'db' { + Write-Host "Synchronisiere Datenbank lokal → Server..." -ForegroundColor Cyan + + # Konfiguration aus .deploy.env + $localDbName = if ($env:LOCAL_DB_NAME) { $env:LOCAL_DB_NAME } else { 'gflex_test' } + $localDump = if ($env:LOCAL_MYSQLDUMP) { $env:LOCAL_MYSQLDUMP } else { 'D:\xampp\mysql\bin\mysqldump.exe' } + $serverDbName = if ($env:DEPLOY_DB_NAME) { $env:DEPLOY_DB_NAME } else { 'gplx' } + $serverDbUser = if ($env:DEPLOY_DB_USER) { $env:DEPLOY_DB_USER } else { 'gplx' } + $serverDbPass = $env:DEPLOY_DB_PASS + + if (-not $serverDbPass) { + Write-Error "DEPLOY_DB_PASS fehlt in .deploy.env." exit 1 } - Write-Host "Deploye Wiki-Templates..." -ForegroundColor Cyan - Sync-Dir "$repoRoot\scripts\templates" "$projectPath/scripts/templates" - Write-Host "Fertig." -ForegroundColor Green + + $dumpFile = Join-Path $env:TEMP "gplx_deploy_$(Get-Date -Format 'yyyyMMdd_HHmmss').sql" + + # 1. Lokalen Dump erstellen + Write-Host "`n[1/3] Erstelle lokalen DB-Dump ($localDbName)..." -ForegroundColor DarkGray + & $localDump -u root $localDbName | Set-Content $dumpFile -Encoding UTF8 + if ($LASTEXITCODE -ne 0) { Write-Error "Dump fehlgeschlagen."; exit 1 } + $sizeMb = [math]::Round((Get-Item $dumpFile).Length / 1MB, 1) + Write-Host " Dump erstellt: $dumpFile ($sizeMb MB)" + + # 2. Dump auf Server übertragen + Write-Host "`n[2/3] Übertrage Dump auf Server..." -ForegroundColor DarkGray + scp $dumpFile "${target}:/tmp/gplx_deploy.sql" + if ($LASTEXITCODE -ne 0) { Write-Error "SCP fehlgeschlagen."; exit 1 } + + # 3. Auf Server importieren (DB droppen + neu anlegen) + Write-Host "`n[3/3] Importiere auf Server (DB wird überschrieben)..." -ForegroundColor DarkGray + $importCmd = @" +mysql -u root <<'SQL' +DROP DATABASE IF EXISTS $serverDbName; +CREATE DATABASE $serverDbName CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +GRANT ALL PRIVILEGES ON $serverDbName.* TO '${serverDbUser}'@'localhost'; +FLUSH PRIVILEGES; +SQL +mysql -u$serverDbUser -p$serverDbPass $serverDbName < /tmp/gplx_deploy.sql +cd $mwPath && $serverPhp maintenance/run.php update --quick +rm /tmp/gplx_deploy.sql +"@ + ssh $target $importCmd + if ($LASTEXITCODE -ne 0) { Write-Error "Import fehlgeschlagen."; exit 1 } + + # Lokale Dump-Datei aufräumen + Remove-Item $dumpFile -Force + Write-Host "`nFertig. Datenbank wurde auf dem Server überschrieben." -ForegroundColor Green + Write-Host "Hinweis: LocalSettings.php auf dem Server muss ggf. angepasst werden." -ForegroundColor DarkYellow } 'all' { - Write-Host "Deploye Skin + Templates..." -ForegroundColor Cyan - Sync-Dir "$repoRoot\mediawiki\skins\Cavendish" "$mwPath/skins/Cavendish" -delete $true - if ($projectPath) { - Sync-Dir "$repoRoot\scripts\templates" "$projectPath/scripts/templates" - } - Write-Host "Fertig." -ForegroundColor Green + Write-Host "Deploye Skin + Wiki-Seiten..." -ForegroundColor Cyan + & $PSCommandPath -Mode skin + & $PSCommandPath -Mode pages } 'initial' { - Write-Host "Erstinstallation – uebertraege MediaWiki-Core..." -ForegroundColor Yellow + Write-Host "Erstinstallation – übertrage MediaWiki-Core..." -ForegroundColor Yellow Write-Host "Das kann einige Minuten dauern." -ForegroundColor DarkGray $excludes = @( @@ -127,27 +234,24 @@ switch ($Mode) { ) if ($hasWsl) { - $wslSrc = (ConvertTo-WslPath "$repoRoot\mediawiki") + '/' + $wslSrc = (ConvertTo-WslPath "$repoRoot\mediawiki") + '/' $rsyncArgs = @('-az', '--progress') + $excludes + @($wslSrc, "${target}:${mwPath}/") wsl rsync @rsyncArgs } else { - Write-Warning "Fuer die Erstinstallation wird WSL empfohlen (grosse Datenmenge)." - Write-Warning "Alternative: WinSCP GUI verwenden oder WSL installieren." + Write-Error "WSL ist für die Erstinstallation erforderlich (große Datenmenge). Bitte WSL installieren." exit 1 } + if ($LASTEXITCODE -ne 0) { Write-Error "Übertragung fehlgeschlagen."; exit 1 } + Write-Host "" Write-Host "Erstinstallation abgeschlossen." -ForegroundColor Green Write-Host "" - Write-Host "Naechste Schritte auf dem Server:" -ForegroundColor Yellow - Write-Host " 1. LocalSettings.php anlegen:" - Write-Host " Vorlage: mediawiki/LocalSettings.production.example.php" - Write-Host " 2. Composer-Abhaengigkeiten installieren:" - Write-Host " cd $mwPath && composer install --no-dev" - Write-Host " 3. Datenbank einrichten:" - Write-Host " php maintenance/run.php install ..." - Write-Host " oder: Dump importieren mit mysql -u user -p dbname < dump.sql" - Write-Host " 4. SMW initialisieren:" - Write-Host " php maintenance/run.php setupStore" + Write-Host "Nächste Schritte auf dem Server:" -ForegroundColor Yellow + Write-Host " 1. LocalSettings.php hochladen: scp mediawiki/LocalSettings.php ${target}:${mwPath}/" + Write-Host " 2. Composer: ssh $target 'cd $mwPath && composer install --no-dev'" + Write-Host " 3. DB-Setup: php maintenance/run.php install ... (siehe Runbook)" + Write-Host " 4. SMW-Setup: php extensions/SemanticMediaWiki/maintenance/setupStore.php --nochecks" + Write-Host " 5. Seiten pushen: .\deploy.ps1 -Mode pages" } } diff --git a/scripts/templates/About_GxPlex.wiki b/scripts/templates/About_GxPlex.wiki new file mode 100644 index 0000000..7c85616 --- /dev/null +++ b/scripts/templates/About_GxPlex.wiki @@ -0,0 +1,25 @@ +== About GxPlex == + +GxPlex is a structured document database for regulatory affairs in the medical device industry. + +It helps teams manage, review, and approve regulatory documents in compliance with MDR and IVDR requirements. + +=== Purpose === + +* Central repository for regulatory documents +* Structured metadata via [[Special:Properties|semantic properties]] +* Approval workflow for controlled document management + +=== How to use GxPlex === + +* [[Create_new_page|Create a new page]] — open a pre-filled document form +* [[Contribute_to_GxPlex|How to contribute]] — step-by-step guide for authors +* [[Help_Adding_a_topic|How to add a topic]] — create a new category that appears on the topics overview + +=== Development === + +* [[Development Roadmap]] — planned features and open tasks for future versions + +=== Contact === + +For questions or access requests, contact the wiki administrator. diff --git a/scripts/templates/Cat_Clinical.wiki b/scripts/templates/Cat_Clinical.wiki index c9bd48d..57a215a 100644 --- a/scripts/templates/Cat_Clinical.wiki +++ b/scripts/templates/Cat_Clinical.wiki @@ -1,3 +1,4 @@ +{{#set: Description=Clinical evaluation, clinical performance studies, PMCF, and PMPF documentation.}} Pages in this category cover clinical evaluation, clinical performance, post-market clinical follow-up (PMCF), and post-market performance follow-up (PMPF). == Pages in this category == @@ -9,3 +10,5 @@ Pages in this category cover clinical evaluation, clinical performance, post-mar | headers=show | limit=50 }} + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_EU_Regulation.wiki b/scripts/templates/Cat_EU_Regulation.wiki new file mode 100644 index 0000000..1a24053 --- /dev/null +++ b/scripts/templates/Cat_EU_Regulation.wiki @@ -0,0 +1,17 @@ +{{#set: Description=Core EU regulatory frameworks: MDR (EU 2017/745) and IVDR (EU 2017/746).}} +Pages in this category relate to EU regulatory frameworks governing medical devices and in vitro diagnostic devices, including MDR (EU 2017/745) and IVDR (EU 2017/746). + +== Documents == + +{{#ask: [[Category:EU Regulation]] [[Status::+]] +| ?Document name = Name +| ?Status = Status +| ?Area of validity = Region +| ?Version = Version +| format=table +| headers=show +| limit=50 +| sort=Status +}} + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_IVD.wiki b/scripts/templates/Cat_IVD.wiki index f8922ae..4d321b3 100644 --- a/scripts/templates/Cat_IVD.wiki +++ b/scripts/templates/Cat_IVD.wiki @@ -1,3 +1,4 @@ +{{#set: Description=Regulatory documents and references under IVDR (EU 2017/746).}} Pages in this category cover regulatory documents and references in the In Vitro Diagnostics (IVD) scope. == Documents == @@ -20,3 +21,5 @@ Pages in this category cover regulatory documents and references in the In Vitro * [[:Category:Labelling|Labelling]] * [[:Category:Traceability|Traceability]] * [[:Category:Performance Evaluation|Performance Evaluation]] + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_Labelling.wiki b/scripts/templates/Cat_Labelling.wiki index 2ca4dfe..722d58d 100644 --- a/scripts/templates/Cat_Labelling.wiki +++ b/scripts/templates/Cat_Labelling.wiki @@ -1,3 +1,4 @@ +{{#set: Description=Labelling requirements, UDI carriers, and packaging obligations.}} Pages in this category cover labelling requirements, UDI carriers, and packaging obligations. == Pages in this category == @@ -9,3 +10,5 @@ Pages in this category cover labelling requirements, UDI carriers, and packaging | headers=show | limit=50 }} + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_MedicalDevices.wiki b/scripts/templates/Cat_MedicalDevices.wiki index df24770..922a3b3 100644 --- a/scripts/templates/Cat_MedicalDevices.wiki +++ b/scripts/templates/Cat_MedicalDevices.wiki @@ -1,3 +1,4 @@ +{{#set: Description=Regulatory documents and references under MDR (EU 2017/745).}} Pages in this category cover regulatory documents and references in the Medical Devices (MD) scope. == Documents == @@ -19,3 +20,5 @@ Pages in this category cover regulatory documents and references in the Medical * [[:Category:Post-Market Surveillance|Post-Market Surveillance]] * [[:Category:Labelling|Labelling]] * [[:Category:Traceability|Traceability]] + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_PerformanceEvaluation.wiki b/scripts/templates/Cat_PerformanceEvaluation.wiki new file mode 100644 index 0000000..49ce899 --- /dev/null +++ b/scripts/templates/Cat_PerformanceEvaluation.wiki @@ -0,0 +1,17 @@ +{{#set: Description=Performance evaluation plans, reports, and PMPF documentation for IVD devices (IVDR).}} +Pages in this category cover performance evaluation documentation for in vitro diagnostic devices, including performance evaluation plans, reports, and post-market performance follow-up (PMPF) under IVDR. + +== Documents == + +{{#ask: [[Category:Performance Evaluation]] [[Status::+]] +| ?Document name = Name +| ?Status = Status +| ?Area of validity = Region +| ?Version = Version +| format=table +| headers=show +| limit=50 +| sort=Status +}} + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_PostMarket.wiki b/scripts/templates/Cat_PostMarket.wiki index 86db301..2f1f6eb 100644 --- a/scripts/templates/Cat_PostMarket.wiki +++ b/scripts/templates/Cat_PostMarket.wiki @@ -1,3 +1,4 @@ +{{#set: Description=Post-market surveillance plans, PMCF, PMPF, and periodic safety update reports (PSUR/PSSR).}} Pages in this category cover post-market surveillance (PMS), post-market clinical follow-up (PMCF), post-market performance follow-up (PMPF), and periodic safety update reports (PSUR). == Pages in this category == @@ -9,3 +10,5 @@ Pages in this category cover post-market surveillance (PMS), post-market clinica | headers=show | limit=50 }} + +[[Category:Themes and topics]] diff --git a/scripts/templates/Cat_Traceability.wiki b/scripts/templates/Cat_Traceability.wiki new file mode 100644 index 0000000..e9abb1c --- /dev/null +++ b/scripts/templates/Cat_Traceability.wiki @@ -0,0 +1,17 @@ +{{#set: Description=UDI systems and device identification obligations under MDR and IVDR.}} +Pages in this category cover traceability requirements including UDI (Unique Device Identification) systems and device identification obligations under MDR and IVDR. + +== Documents == + +{{#ask: [[Category:Traceability]] [[Status::+]] +| ?Document name = Name +| ?Status = Status +| ?Area of validity = Region +| ?Version = Version +| format=table +| headers=show +| limit=50 +| sort=Status +}} + +[[Category:Themes and topics]] diff --git a/scripts/templates/Common_js.wiki b/scripts/templates/Common_js.wiki new file mode 100644 index 0000000..f7f3d28 --- /dev/null +++ b/scripts/templates/Common_js.wiki @@ -0,0 +1,504 @@ +/* site-wide JS — VoteNY toggle is handled in Vote.js */ + +/* GxPlex Search — chainable filter buttons with pagination */ +( function () { + if ( mw.config.get( 'wgPageName' ) !== 'GxPlex_Search' ) { + return; + } + + var PAGE_SIZE = 50; + + var FILTER_GROUPS = [ + { key: 'type', label: 'Type' }, + { key: 'validityArea', label: 'Validity area' }, + { key: 'scopes', label: 'Scope(s)' }, + { key: 'status', label: 'Status' }, + { key: 'docType', label: 'Document type' }, + { key: 'language', label: 'Language' }, + { key: 'restricted', label: 'Restricted access' } + ]; + + var DOC_QUERY = '[[Document:+]]' + + '|?DocReference=reference' + + '|?DocValidityArea=validityArea' + + '|?DocScopes=scopes' + + '|?DocName=docName' + + '|?DocVersion=version' + + '|?DocStatus=status' + + '|?DocType=docType' + + '|?DocLanguage=language' + + '|?DocDescription=description' + + '|?DocRestricted=restricted' + + '|?DocSubmittedBy=submittedBy' + + '|?DocContributors=contributors' + + '|limit=500' + + '|sort=DocName' + + '|order=asc'; + + var REF_QUERY = '[[Reference:+]]' + + '|?HasDocument=linkedDoc' + + '|?HasDocument.DocReference=reference' + + '|?HasDocument.DocValidityArea=validityArea' + + '|?HasDocument.DocScopes=scopes' + + '|?HasDocument.DocName=docName' + + '|?HasDocument.DocVersion=version' + + '|?HasDocument.DocStatus=status' + + '|?HasDocument.DocType=docType' + + '|?HasDocument.DocLanguage=language' + + '|?HasDocument.DocDescription=description' + + '|?HasDocument.DocRestricted=restricted' + + '|?HasDocument.DocSubmittedBy=submittedBy' + + '|?HasDocument.DocContributors=contributors' + + '|?RefScope=refScope' + + '|?RefLanguage=refLanguage' + + '|?RefTagsEN=tagsEN' + + '|limit=500'; + + var allResults = []; + var filteredResults = []; + var currentPage = 1; + var activeFilters = {}; + + /* ---- helpers ---- */ + + function esc( str ) { + return String( str || '' ) + .replace( /&/g, '&' ) + .replace( //g, '>' ) + .replace( /"/g, '"' ); + } + + function getProp( printouts, key ) { + var arr = printouts[ key ]; + if ( !arr || !arr.length ) { return ''; } + return arr.map( function ( v ) { + if ( typeof v === 'string' ) { return v; } + return v.item !== undefined ? v.item : ( v.fulltext || '' ); + } ).filter( Boolean ).join( ', ' ); + } + + function splitValues( str ) { + return String( str || '' ).split( ',' ).map( function ( v ) { return v.trim(); } ).filter( Boolean ); + } + + /* ---- parse SMW responses ---- */ + + function parseResults( data, type ) { + var raw = ( data.query && data.query.results ) || {}; + return Object.keys( raw ).map( function ( key ) { + var page = raw[ key ]; + var p = page.printouts || {}; + var docName = getProp( p, 'docName' ); + return { + _type: type, + fulltext: page.fulltext, + url: page.fullurl, + displayName: type === 'Document' + ? ( docName || page.fulltext.replace( /^Document:/, '' ) ) + : page.fulltext.replace( /^Reference:/, '' ), + reference: getProp( p, 'reference' ), + validityArea: getProp( p, 'validityArea' ), + scopes: getProp( p, 'scopes' ), + docName: docName, + version: getProp( p, 'version' ), + status: getProp( p, 'status' ), + docType: getProp( p, 'docType' ), + language: getProp( p, 'language' ), + description: getProp( p, 'description' ), + restricted: getProp( p, 'restricted' ), + submittedBy: getProp( p, 'submittedBy' ), + contributors: getProp( p, 'contributors' ), + preview: '' + }; + } ); + } + + /* ---- text extracts (250 chars, batches of 50) ---- */ + + function fetchExtracts( results, callback ) { + if ( !results.length ) { callback(); return; } + var byTitle = {}; + results.forEach( function ( r ) { byTitle[ r.fulltext ] = r; } ); + var titles = results.map( function ( r ) { return r.fulltext; } ); + var batches = []; + for ( var i = 0; i < titles.length; i += 50 ) { + batches.push( titles.slice( i, i + 50 ) ); + } + var done = 0; + var api = new mw.Api(); + batches.forEach( function ( batch ) { + api.get( { + action: 'query', prop: 'extracts', + exchars: 250, exintro: true, explaintext: true, + titles: batch.join( '|' ), format: 'json' + } ).always( function ( data ) { + if ( data && data.query && data.query.pages ) { + Object.keys( data.query.pages ).forEach( function ( id ) { + var pg = data.query.pages[ id ]; + if ( byTitle[ pg.title ] ) { + byTitle[ pg.title ].preview = ( pg.extract || '' ) + .replace( /\s+/g, ' ' ).trim().slice( 0, 250 ); + } + } ); + } + if ( ++done === batches.length ) { callback(); } + } ); + } ); + } + + /* ---- filter button data ---- */ + + function collectFieldValues( results ) { + var values = {}; + FILTER_GROUPS.forEach( function ( g ) { values[ g.key ] = {}; } ); + results.forEach( function ( r ) { + FILTER_GROUPS.forEach( function ( g ) { + var raw = g.key === 'type' ? r._type : r[ g.key ]; + splitValues( raw ).forEach( function ( v ) { values[ g.key ][ v ] = true; } ); + } ); + } ); + return values; + } + + /* ---- render filter buttons ---- */ + + function renderFilterButtons( filterSection, values ) { + filterSection.innerHTML = ''; + activeFilters = {}; + + var hasAnyValue = FILTER_GROUPS.some( function ( g ) { + return Object.keys( values[ g.key ] || {} ).length > 0; + } ); + + if ( !hasAnyValue ) { + var msg = document.createElement( 'p' ); + msg.className = 'gplx-no-results'; + msg.textContent = 'No documents or references indexed yet. Import data to enable filters.'; + filterSection.appendChild( msg ); + return; + } + + FILTER_GROUPS.forEach( function ( g ) { + activeFilters[ g.key ] = {}; + var vals = Object.keys( values[ g.key ] || {} ).sort(); + if ( !vals.length ) { return; } + + var group = document.createElement( 'div' ); + group.className = 'gplx-filter-group'; + + var label = document.createElement( 'span' ); + label.className = 'gplx-filter-label'; + label.textContent = g.label; + + var row = document.createElement( 'div' ); + row.className = 'gplx-filter-btn-row'; + + vals.forEach( function ( v ) { + var btn = document.createElement( 'button' ); + btn.type = 'button'; + btn.className = 'gplx-filter-btn'; + btn.textContent = v; + btn.setAttribute( 'aria-pressed', 'false' ); + btn.addEventListener( 'click', function () { + if ( activeFilters[ g.key ][ v ] ) { + delete activeFilters[ g.key ][ v ]; + btn.classList.remove( 'is-active' ); + btn.setAttribute( 'aria-pressed', 'false' ); + } else { + activeFilters[ g.key ][ v ] = true; + btn.classList.add( 'is-active' ); + btn.setAttribute( 'aria-pressed', 'true' ); + } + refresh(); + } ); + row.appendChild( btn ); + } ); + + group.appendChild( label ); + group.appendChild( row ); + filterSection.appendChild( group ); + } ); + } + + /* ---- filter logic: AND across groups, OR within group ---- */ + + function matchesFilters( result ) { + return FILTER_GROUPS.every( function ( g ) { + var active = Object.keys( activeFilters[ g.key ] || {} ); + if ( !active.length ) { return true; } + var fieldVal = g.key === 'type' ? result._type : ( result[ g.key ] || '' ); + var parts = splitValues( fieldVal ); + return active.some( function ( av ) { + return parts.some( function ( p ) { + return p.toLowerCase() === av.toLowerCase(); + } ); + } ); + } ); + } + + function applyFilters( results ) { + var textEl = document.getElementById( 'gplx-filter-text' ); + var text = textEl ? textEl.value.trim().toLowerCase() : ''; + return results.filter( function ( r ) { + if ( !matchesFilters( r ) ) { return false; } + if ( text ) { + var hay = [ + r.displayName, r.reference, r.validityArea, r.scopes, + r.docName, r.version, r.status, r.docType, r.language, + r.description, r.restricted, r.submittedBy, r.contributors, + r.preview + ].join( ' ' ).toLowerCase(); + if ( hay.indexOf( text ) === -1 ) { return false; } + } + return true; + } ); + } + + /* ---- pagination ---- */ + + function getPageRange( current, total ) { + if ( total <= 7 ) { + var pages = []; + for ( var i = 1; i <= total; i++ ) { pages.push( i ); } + return pages; + } + var result = [ 1 ]; + var left = current - 2; + var right = current + 2; + if ( left > 2 ) { result.push( '...' ); } + for ( var j = Math.max( 2, left ); j <= Math.min( total - 1, right ); j++ ) { + result.push( j ); + } + if ( right < total - 1 ) { result.push( '...' ); } + result.push( total ); + return result; + } + + function renderPagination( total ) { + var el = document.getElementById( 'gplx-pagination' ); + if ( !el ) { return; } + var totalPages = Math.ceil( total / PAGE_SIZE ); + if ( totalPages <= 1 ) { el.innerHTML = ''; return; } + + var frag = document.createDocumentFragment(); + + function pageBtn( label, page, isCurrent, isDisabled ) { + var btn = document.createElement( 'button' ); + btn.type = 'button'; + btn.className = 'gplx-page-btn' + ( isCurrent ? ' gplx-page-btn--current' : '' ); + btn.textContent = label; + btn.disabled = isDisabled || isCurrent; + if ( isCurrent ) { btn.setAttribute( 'aria-current', 'page' ); } + if ( !isDisabled && !isCurrent ) { + btn.addEventListener( 'click', function () { + currentPage = page; + renderPage(); + var s = document.getElementById( 'gplx-search-status' ); + if ( s ) { s.scrollIntoView( { behavior: 'smooth', block: 'nearest' } ); } + } ); + } + return btn; + } + + frag.appendChild( pageBtn( '‹ Previous', currentPage - 1, false, currentPage === 1 ) ); + + getPageRange( currentPage, totalPages ).forEach( function ( p ) { + if ( p === '...' ) { + var span = document.createElement( 'span' ); + span.className = 'gplx-page-ellipsis'; + span.textContent = '…'; + frag.appendChild( span ); + } else { + frag.appendChild( pageBtn( String( p ), p, p === currentPage, false ) ); + } + } ); + + frag.appendChild( pageBtn( 'Next ›', currentPage + 1, false, currentPage === totalPages ) ); + + el.innerHTML = ''; + el.appendChild( frag ); + } + + /* ---- render results table ---- */ + + function renderTable( pageResults ) { + var container = document.getElementById( 'gplx-search-results' ); + if ( !pageResults.length ) { + container.innerHTML = '
No results match the selected filters.
'; + return; + } + var rows = pageResults.map( function ( r ) { + var typeBadge = r._type === 'Document' + ? 'Document' + : 'Reference'; + var restricted = r.restricted && r.restricted.toLowerCase() === 'yes' + ? ' Restricted' : ''; + var preview = r.preview + ? '| Type | Title | ' + + 'Validity area | Status | Language | Document type | ' + + '
|---|
-) or leave the page blank.
+
+; Adding an image
+: 1. Upload the image via [[Special:Upload]].
+: 2. Insert it with:
+:: [[File:Filename.jpg|center|link=https://example.com|alt=Short description]]
+: The link= parameter makes the image clickable. alt= is required for accessibility (WCAG).
+
+; Recommended image dimensions
+: The sidebar is approximately '''200 px''' wide. Recommended image width: '''180–200 px''' (or 360–400 px for HiDPI displays). Wider images are scaled down automatically to fit — there is no hard pixel limit. Keep the height below 150 px; landscape or square formats work best in the sidebar.
+
+; Caption
+: An optional short caption appears below the image in grey text. Add it as a plain paragraph after the image syntax.
+http://<wiki-url>/index.php/Category:Your_Topic_Name in your browser. The page will show "There is currently no text in this page." Click '''Create''' to start editing.
+
+=== Step 2 – Add the required content ===
+
+Paste the following into the editor and adjust the highlighted parts:
+
+
+{{#set: Description=A one-sentence description of what this topic covers.}}
+Pages in this category cover …
+
+== Documents ==
+
+{{#ask: [[Category:Your Topic Name]] [[Status::+]]
+| ?Document name = Name
+| ?Status = Status
+| ?Area of validity = Region
+| ?Version = Version
+| format=table
+| headers=show
+| limit=50
+| sort=Status
+}}
+
+[[Category:Themes and topics]]
+
+
+{| class="wikitable"
+! Part !! What to change
+|-
+| Description=… || One sentence describing the topic — shown in the [[Themes_and_topics|Themes and topics]] table.
+|-
+| Pages in this category cover … || A short introductory sentence visible on the category page itself.
+|-
+| [[Category:Your Topic Name]] in the ask query || Replace with the exact name of your new category.
+|-
+| [[Category:Themes and topics]] at the bottom || '''Do not remove.''' This line makes the topic appear in the overview table.
+|}
+
+=== Step 3 – Save ===
+
+Click '''Save page'''. The new topic will appear on [[Themes_and_topics|Themes and topics]] immediately — no further steps needed.
+
+== Removing a topic ==
+
+Edit the category page and remove the line [[Category:Themes and topics]] , then save. The topic disappears from the overview on the next page load.
+
+== Need help? ==
+
+Contact the wiki administrator or see [[About_GxPlex|About GxPlex]].
diff --git a/scripts/templates/MainPage.wiki b/scripts/templates/MainPage.wiki
index feae9f4..7c826f5 100644
--- a/scripts/templates/MainPage.wiki
+++ b/scripts/templates/MainPage.wiki
@@ -1,8 +1,8 @@
-This wiki is a structured reference database for regulatory documents in the medical devices, in vitro diagnostics, and pharmaceutical sectors. Each document is stored with standardized metadata (scope, validity status, region, official source) and linked to individual pages for its key articles, annexes, and provisions, organized by topic.
+This wiki is a structured reference database for regulatory documents in the medical devices, in vitro diagnostics, and pharmaceutical sectors. Each document is stored with standardized metadata (scope, validity status, region, official source) and linked to individual reference pages for key articles, annexes, and provisions.
-The database currently covers regulations, directives, and guidelines in the MD, IVD, GMP, PV, and NIS scopes. Cross-references between equivalent provisions in different regulations are maintained explicitly, for example between MDR and IVDR requirements on post-market surveillance or UDI.
+The database currently covers regulations, directives, and guidelines in the MD, IVD, HM, GMP, PV, and NIS scopes. Cross-references between equivalent provisions in different regulations are maintained explicitly.
-Structured queries are available on the [[Document_Index|Document Index]] and on each category page. Use [[Special:Ask|Special:Ask]] for custom queries across any combination of scope, status, region, or topic.
+Structured queries are available on the [[Document_Index|Document Index]]. Use [[GxPlex_Search|GxPlex Search]] for filter-based browsing, or [[Special:Ask|Special:Ask]] for custom SMW queries.
All registered users can contribute. New pages require reviewer approval before becoming publicly visible.
@@ -11,34 +11,41 @@ All registered users can contribute. New pages require reviewer approval before
{| style="width:100%; border-collapse:collapse;"
|-
| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
-=== By document ===
-* [[Document_Index|All documents]]
+=== Browse ===
+* [[Document_Index|Document index]]
+* [[GxPlex_Search|Search & filter]]
+* [[Special:Ask|Custom SMW query]]
* [[Special:RecentChanges|Recent changes]]
-* [[Special:Ask|Custom query]]
| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
-=== By scope ===
-* [[:Category:Medical Devices|Medical Devices (MD)]]
-* [[:Category:In Vitro Diagnostics|In Vitro Diagnostics (IVD)]]
-
-| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
-=== By topic ===
+=== By category ===
+* [[:Category:Medical Devices|Medical Devices]]
+* [[:Category:In Vitro Diagnostics|In Vitro Diagnostics]]
* [[:Category:Clinical|Clinical]]
* [[:Category:Post-Market Surveillance|Post-Market Surveillance]]
* [[:Category:Labelling|Labelling]]
* [[:Category:Traceability|Traceability]]
+
+| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
+=== Contribute ===
+* [[Contribute_to_GxPlex|How to contribute]]
+* [[Help:Adding_a_topic|Add a topic category]]
+* [[Create_new_page|Create a new page]]
+* [[GxPlex_Search|Find existing pages]]
|}
-== Current documents ==
+== Recent documents ==
-{{#ask: [[Status::Current]]
-| ?Document name = Name
-| ?Scope = Scope
-| ?Area of validity = Region
+{{#ask: [[Document:+]]
+| ?DocName = Document name
+| ?DocScopes = Scope
+| ?DocValidityArea = Region
+| ?DocStatus = Status
| format=table
| headers=show
| limit=10
-| sort=Scope
+| sort=DocName
+| order=asc
}}
[[Document_Index|View all documents →]]
diff --git a/scripts/templates/Roadmap.wiki b/scripts/templates/Roadmap.wiki
new file mode 100644
index 0000000..985a40d
--- /dev/null
+++ b/scripts/templates/Roadmap.wiki
@@ -0,0 +1,82 @@
+== Development Roadmap ==
+
+This page collects planned features, open tasks, and ideas for future development of GxPlex.
+Items are grouped by theme. Priority is indicated in the Status column.
+
+=== Rating and feedback ===
+
+{| class="wikitable"
+! Feature !! Status !! Notes
+|-
+| Loading indicator during vote submission
+| Planned
+| After clicking a star, the UI gives no visual feedback until the API response arrives. A spinner or disabled state on the stars during the request would improve perceived responsiveness. Relevant file: extensions/VoteNY/resources/js/Vote.js — disable stars on click, re-enable in .done().
+|-
+| Link to top-rated pages
+| Planned
+| VoteNY ships with [[Special:TopRatings]] — a ranked list of the highest-rated pages. Link it from [[GxPlex_Search|GxPlex Search]] and the sidebar.
+|-
+| Show vote count on document index
+| Idea
+| The {{NUMBEROFVOTESPAGE:Page name}} magic word from VoteNY can display the number of votes per page. Could be added as a column to [[Document_Index|Document Index]] or the category pages.
+|}
+
+=== Content and structure ===
+
+{| class="wikitable"
+! Feature !! Status !! Notes
+|-
+| Document part as filterable property
+| Deferred
+| Adding document_part (chapter, article, section) as an SMW property to [[Template:RegDocument]] was explicitly deferred. Would require updating all existing pages after the template change. Reopen when the document count makes this worthwhile.
+|-
+| Additional content templates
+| Planned
+| Beyond {{RegDocument}}, dedicated templates for recurring document types (e.g. PSUR, CEP, PMCF report) would reduce setup time per page and improve metadata consistency.
+|-
+| Structured change history
+| Idea
+| A property or subpage convention for recording revision history of a regulatory document (who updated what, and when) separate from the wiki edit history.
+|}
+
+=== Search and navigation ===
+
+{| class="wikitable"
+! Feature !! Status !! Notes
+|-
+| Dynamic filter UI for GxPlex Search
+| Planned
+| The current [[GxPlex_Search|GxPlex Search]] uses a sortable table. Installing the [https://www.mediawiki.org/wiki/Extension:PageForms PageForms extension] would enable dropdown filters that query SMW properties dynamically without a full page reload.
+|-
+| Topic property on RegDocument
+| Idea
+| Adding a Topic SMW property would allow filtering documents by topic in Special:Ask without navigating to category pages. Currently topics are inferred from page categories only.
+|}
+
+=== Communication ===
+
+{| class="wikitable"
+! Feature !! Status !! Notes
+|-
+| Email notifications for approval workflow
+| Planned
+| Authors currently have no automatic notification when their page is approved or rejected. Requires SMTP configuration ($wgSMTP in LocalSettings.php) and a hook on ApprovedRevs' approval/rejection events.
+|-
+| Discussion threads on document pages
+| Planned
+| The [[Special:Version|DiscussionTools extension]] is already installed but not surfaced in the UI. Enabling it would allow threaded comments on each document page without a separate talk page workflow.
+|}
+
+=== Infrastructure ===
+
+{| class="wikitable"
+! Feature !! Status !! Notes
+|-
+| Sponsor or partner slot
+| Idea
+| A designated area in the sidebar or footer for a sponsor link or logo. Requires a hook in LocalSettings.php and a configurable wikitext snippet so non-technical admins can update the content without a deployment.
+|-
+| Automated accessibility regression test
+| Idea
+| The Pa11y and Lighthouse checks (npm run a11y:all) currently run manually. Adding them to a pre-commit hook or a scheduled task would catch regressions earlier.
+|}
diff --git a/scripts/templates/Sidebar.wiki b/scripts/templates/Sidebar.wiki
index 98cb41e..db94c8f 100644
--- a/scripts/templates/Sidebar.wiki
+++ b/scripts/templates/Sidebar.wiki
@@ -1,9 +1,16 @@
-* Document Database
-** Document_Index|All documents
-** Special:Ask|Custom query
-** Special:Categories|Browse categories
+* About the Database
+** mainpage|Main Page
+** About_GxPlex|About GxPlex
+** The_Project|The Project
+** Contribute_to_GxPlex|Contribute to GxPlex
+
+* Navigation
+** GxPlex_Search|Search in GxPlex
+
+* Structure
+** Document_Index|All documents
+** Special:Categories|Keywords / Tags
+** Themes_and_topics|Themes and topics
+
+* TOOLBOX
-* navigation
-** mainpage|mainpage-description
-** Special:RecentChanges|recentchanges
-** Special:Random|randompage
diff --git a/scripts/templates/Template_DocumentBox.wiki b/scripts/templates/Template_DocumentBox.wiki
new file mode 100644
index 0000000..2e5d19a
--- /dev/null
+++ b/scripts/templates/Template_DocumentBox.wiki
@@ -0,0 +1,96 @@
+{{#set:}} directly on the Document page.
+
+== Parameters ==
+
+{| class="wikitable"
+! Parameter !! Description
+|-
+| reference || Composite reference identifier (e.g. AUT AMG – Arzneimittelgesetz 04-Mar-2023)
+|-
+| validity_area || Country / region code (e.g. AUT, USA, EU)
+|-
+| scopes || Scope code(s) (e.g. HM, G)
+|-
+| doc_name || Full document name (regulation, law, guideline, …)
+|-
+| version || Version or revision date
+|-
+| status || Current, Draft, Superseded, …
+|-
+| doc_type || Legal act, Regulation, Guideline, …
+|-
+| language || ISO 639 language code(s) (e.g. EN, DE)
+|-
+| description || Short description of the document
+|-
+| official_link || URL to the official source (omit the surrounding brackets)
+|-
+| restricted || Yes / No
+|-
+| submitted_by || Name of the person who submitted this entry
+|-
+| contributors || Additional contributors (optional)
+|}
+
+== Usage ==
+
+
+{{DocumentBox
+|reference=
+|validity_area=
+|scopes=
+|doc_name=
+|version=
+|status=
+|doc_type=
+|language=
+|description=
+|official_link=
+|restricted=No
+|submitted_by=
+|contributors=
+}}
+
+{{#set:}} directly on the Reference page.
+
+== Parameters ==
+
+{| class="wikitable"
+! Parameter !! Description
+|-
+| scope || Applicable scope of this reference (e.g. HM, G, IVD)
+|-
+| doc_part || Article, section, annex, or chapter (e.g. Art. 12, Annex I)
+|-
+| language || Language of this reference (ISO 639, e.g. EN, DE)
+|-
+| tags_en || English tags / keywords (comma-separated)
+|-
+| tags_original || Tags in original document language
+|-
+| tags_fr || French tags (optional)
+|}
+
+== Usage ==
+
+
+{{ReferenceBox
+|scope=
+|doc_part=
+|language=
+|tags_en=
+|tags_original=
+|tags_fr=
+}}
+
+