From 355da16a5befc54b9fb50cd4532d30eb4ae66fd2 Mon Sep 17 00:00:00 2001 From: Sascha Date: Sat, 27 Jun 2026 21:21:06 +0200 Subject: [PATCH] feat: Document/Reference namespace, infobox sidebar, deploy script, SMW property types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Document: (NS 3000) and Reference: (NS 3002) namespaces with SMW enabled - Fix SMW namespace config: set smwgNamespacesWithSemanticLinks before wfLoadExtension - Declare all Doc*/Ref* SMW properties as Has type::Text for correct query results - Template:DocumentBox — right-floating infobox sidebar with header row styling - Template:ReferenceBox — new sidebar template for Reference: pages - MediaWiki:Common.js — filter buttons (Type, Validity area, Scope, Status, Doc type, Language, Restricted access), 250-char preview, pagination; removed unused groups - Document_Index — rewritten with [[Document:+]] SMW queries, grouped by status + scope - Main_Page — updated Quick navigation, replaced old property-name queries - scripts/deploy.ps1 — add pages mode (rsync + SSH wiki push), db mode (mysqldump sync), updated all mode; 21-page mapping incl. Template:DocumentBox/ReferenceBox - .deploy.example.env — document new vars (DEPLOY_PHP, DEPLOY_MW_USER, DEPLOY_DB_*) - scripts/templates/*.wiki — new and updated content pages + category pages - mediawiki/resources/assets/GxPlex-Logo.png — project logo asset Co-Authored-By: Claude Sonnet 4.6 --- .deploy.example.env | 25 +- mediawiki/resources/assets/GxPlex-Logo.png | Bin 0 -> 44021 bytes scripts/deploy.ps1 | 224 +++++--- scripts/templates/About_GxPlex.wiki | 25 + scripts/templates/Cat_Clinical.wiki | 3 + scripts/templates/Cat_EU_Regulation.wiki | 17 + scripts/templates/Cat_IVD.wiki | 3 + scripts/templates/Cat_Labelling.wiki | 3 + scripts/templates/Cat_MedicalDevices.wiki | 3 + .../templates/Cat_PerformanceEvaluation.wiki | 17 + scripts/templates/Cat_PostMarket.wiki | 3 + scripts/templates/Cat_Traceability.wiki | 17 + scripts/templates/Common_js.wiki | 504 ++++++++++++++++++ scripts/templates/Contribute_to_GxPlex.wiki | 26 + scripts/templates/Create_new_page.wiki | 13 + scripts/templates/DocumentIndex.wiki | 128 +++-- .../templates/Editnotice_Advertisement.wiki | 20 + scripts/templates/GxPlex_Search.wiki | 16 + scripts/templates/Help_Adding_a_topic.wiki | 59 ++ scripts/templates/MainPage.wiki | 43 +- scripts/templates/Roadmap.wiki | 82 +++ scripts/templates/Sidebar.wiki | 23 +- scripts/templates/Template_DocumentBox.wiki | 96 ++++ scripts/templates/Template_ReferenceBox.wiki | 57 ++ scripts/templates/The_Project.wiki | 17 + scripts/templates/Themes_and_topics.wiki | 11 + 26 files changed, 1305 insertions(+), 130 deletions(-) create mode 100644 mediawiki/resources/assets/GxPlex-Logo.png create mode 100644 scripts/templates/About_GxPlex.wiki create mode 100644 scripts/templates/Cat_EU_Regulation.wiki create mode 100644 scripts/templates/Cat_PerformanceEvaluation.wiki create mode 100644 scripts/templates/Cat_Traceability.wiki create mode 100644 scripts/templates/Common_js.wiki create mode 100644 scripts/templates/Contribute_to_GxPlex.wiki create mode 100644 scripts/templates/Create_new_page.wiki create mode 100644 scripts/templates/Editnotice_Advertisement.wiki create mode 100644 scripts/templates/GxPlex_Search.wiki create mode 100644 scripts/templates/Help_Adding_a_topic.wiki create mode 100644 scripts/templates/Roadmap.wiki create mode 100644 scripts/templates/Template_DocumentBox.wiki create mode 100644 scripts/templates/Template_ReferenceBox.wiki create mode 100644 scripts/templates/The_Project.wiki create mode 100644 scripts/templates/Themes_and_topics.wiki 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 0000000000000000000000000000000000000000..87641068788600e1a3d2c9dd147f9f01a28dc297 GIT binary patch literal 44021 zcmaHTc{r49`|x-uB3trEAtGy$eUB*nmPo3RsF7Wku_dyk5?QiGWX6&;J7aA@WvH5SVNB2Q=MGJo9 z^*wjV_kt(J*Z;crO_aaCzqGT5tB>P#uba}I-cFe`jl(EZ@lC_CXRHG3s1*Na*1xiL zf2@`Ev7A~RKSQ_Y;Q97(4o7nZ*?0XEDkjOaSCn8|MHoqG$@HjfJsA72d2KKD%oz^c zS`Vu4V`-~a%i}g1OqzF3^KAA?&}o4++4T*+6DmGSfzLfz=EUp95@X{I<+5X{{>0-( z688#F7y(7pSK?hm&q$p=eR2Ga(4t(j-SM(a|H6Za0=&?ApD=CoBxs6t;X>6L zVZHAWk!r{<;s)}1k3TWKiB>lH3-(}++r^{IN5{7hAWtpQFwrBY5PqVV$zc_^3iRL4 z2OpoxW&8UhU;F?6@73>vSE_6Y!{~G8uzOHpWAAU^tUhe1{hOpCwE2k3Sd=&+Ca^?V zVnnq`EG}a4n?d|kBO@H}vluv`7t*}?J#S{^R(trkmYazvzEdFS@DY3z3x3{Z6I8={ z`u8Y)@FIb%Y4VN%b&#(>CyVXL>d8xj)>Z~jG>#1njM$)1lOYW1DD5-1<@)^u$LLVZ z{D85=sWB!$ekOlZn2ufrJ?bEXvM!qOH9soHf|H6uWv9ki4Bhi$M2Vk;tziY<{BlZ9wVk)8{&WsS%t&);{~;Sh%2F55IE(`te+M+5SWM9R%)Gp2+_I>S6>yZPsW0 zb7LCa){=nfr2tWD|MD2w8e+f(hheZHljt!Gvh(2sn z`MXV4C(8%Kg%U%Utf^DzgDm`H1Ms!$IOoT7Vd3f?An()3NVUK4o6i9v&p2|W{ShgT zBd0Zu_=K&09dqAQINfU?To+@4?r z2p2;3__@LrX#|KALfhFwDJH;Q+X*0|bkKA{uqiGYHqq%9N@*aJ#Ufwy3Z<^Xm$uKa zjKd$hDTe>YQZuTeEuiWV)hz*W>Ny|7#MPUb^!y9s|@f9 zoZ|(u7EA+RlRlA+lW?f^GVrCXlgY3BTs?i5Js=up&N&Z451WKj!q_?IKOy?uj;P0T zq5gJ69=jmouqPbzpLl_s^&@cTop6h2E`Sym3hLR$$mI7Op>!Ql8)m*-Hcfzy+6CC! zbi)0)b%4;M3?L+K5vjJyJ;jFFLb$c(fdyj$px1U3(6g{-^7A?jkC=}lFqm@LJP>5B z!^ztB1pC|FfkJVFC~;Jz+E->|A##{ToJI6iVOThY5E9KfA1{L}JOK-r#dY|9#sVo( z$YBC!3nOgcu$)r}`HoC}%ZCBs0OT-@NDIbt(8#blgci`jWi{jxB3_L!i)fuQK#S)w zz$2S;-r^QQiza~Y64v2oMrdh5v@TGmP&XIw2=fQ;QPd zXm}(AkG>+{z2W3m1p6Ii7jTh@8F|DAkFM$!>V86Gw}!~>m=1sVBOs*#QPotAc@AHo zvWq{QA5|Y_!FUMy!U|th;3&QrSVuzy7Qy7#_5%#44Grje0Y|?h{2kK;5R4r2VKhX1 z>Hz(dexzEC6GE2;z*wb&h5UeqN(|7|e72DO&wYT{g9uZGV;<#%NQ@UCeizc=pF9Bz zR}nWEl#_eUg0UV^oe(%7%){l@DdlI8YHj?0-1p{BrFEDp>?`&EIqXUE$i^{)0{q!8 z&E-}fRuc^+l4NU4I=1}umu`)e%<-F_5RKC596ZJ;aagh0>(Zl6;?O0kfsBCGZ)>X? zW%pJR4d-5U;yU!4r*w)M$u0k&w&wol-vKohIvw;9-qO-IW+UFkHF&9fl-#}W9p$g_ zxp3bl{`TVOa`hH6ozwx@@@d0@i6ad1>5dVOa)Baz`F-f!U_XZDFAgh`Cl#+58;x%+ z-0I&^&yjx^xw<|Lq_iWR93-HY`rg3Q)Y7Lakbh}taj|nJJ78?f78{xK#o0>V$Q}rKESW(Vr56$U7oalkJ}f_FO2{7 zYi8h+y&l68wmaLK#`JsBY|ce(`%Ah3BhMfXCVE|>|7MkFH2?`8yGK8hY>;-!!Zz^6 z+u83Dp}*r2mhpIh_ua0kA&oakGCTX_E5wNKGZEd@#OrUGxL3h$x9%gBI$2|3bImh+tS5_9 zhQ^%5mvoO=`A4d@F8VTgy52e^RPNa5=~FfG^yYU9=XI}@moFWx=;yb~9%Gbo235JYMpc#LuWuS-XyO#^9ts%SLDRr(EwdnaYeQBO z(p>GHSsg-XP)+G>tQXSlE*uM^8&99v>x*}!f36waz0gK?;i%l%!-^l8yf^G28E9ii zJdbGu8^ZRH;aXzT`~p#{%CVWWJcX%Z&iQow0EYQ?r&PkJDVMJXTtD}+V|R1+OtER_ zA`wCPt7JdE55wR%s^r`!y0Vudi*{f4k{=)~9=R&`ywcVOYuBkT9l=QK9qmdW)!&|( z6+G}NG*up$#mo!jnzbz=_Ar-xr8Z3)J~$`m>801`M`4*1Cc&$4E!Vos>r1_zo^f*Q zKD4qjwaSUOv8YGDXSA18zh7j7X=eA3FZpptET6N-MPuW|3A!BoyR9bb@O3QtG1$V_ z*u&q(3?TK;7D7UY*fTc2krG?2>gy4_S;qKo45zSuhRwCz9qst)n)I-tSLqffFnT{a zo+!Io=n8H6ukJ@k1)tk?TZMO?jbHA%2Ssh={uS%vvHY%jypLiQi|JOaj$O}p@fdgO z>5-C@({khzO^X(5hRADS0KB!oeXL(m*?3_n^VHBM``2UNKV#0`Q}hW7hOD^s3b8Pt zvv(Y4DEc)ydBo=VA>=d&|83|09GE9L`{*`!WQ|aT1eEjkI(8au{~58N_Zn@Fz#R1a z_%GKdy~pvQW(w@KL6rheyclyhAb3qyjNfl0U*?)o@_3qP=+kqxvzE$diu!s@)Nydy zoh-6A?k>=1l4h@CXBr#w3yDerf1?H`oK}@TyI4mQ`eJR3v1z$I9i&WQ(e|jW%}DrV znu%*vhzKkEkf*%VF9JW`!SbbgO0gkIRm}w7Hm3usPk4E`dfdUkN7gwvSe{Gt}nl znzkL0pIPrK+UJjIg0xz&!tCHYnT)G(B{HFnV-x}^6ap)4)fwBK|E9{)d3Lg`WS??Q z^!@qX(s}OH5JC{7{Mv$k-1BS#3a+zC9ty4=AWmif-?99tT^DEB%wv8ggzQu83{5-R zPp(25I(ZyC&pn?ei&UZ!0c%)%G2QgMTK*NRlkOhW&cLhlr1=5MkY?V7=SoDAzWq_4 zyG@YE2wG4rcw#e%To+4JDr+$x4mx*nZ2JGh)TkMZQ*`at&3P&5{KvOy%1F($o<2Sl zIpVf&Az@jk%J%iBXQmjl1gc?Bc?Maw-Sy>h!-9<+P|!Z!yK ze{NZ!!C_UBhF|ZE?w*tj1(kQ%w0mo6(m8j5BfhjB(|GdbqK0&cS!;g|P~)oTPN4$K zbt6_kfs?fgX&&L2!X@Ec%MviXqWyH=v%WaU^RK)t?(UGhQfm;+mOlQ5J<~^q?yvF$ zjMFHjkhOHNg;#0&ylw29_2z~T?g`^^kX@vA=L1SGOq-O6BAM+03G=x1$vN%XmKrZ{41IFGh5uLoG`V4%00^ z>(HAkt?Tyv_{E{BsJ;HSWt#qZl=q3u)qH|(;<@-N&C^N8d@muchGZ^PzE-T}5j`tOU zs#R4Ep_)W874=0*t~w7+X89)ievrV_*`dBmfl7uyH9`b#!NZ(Rv=BANZ1bU(Kt)wE zq%NjWg3Q$n)qYu32^>NNK@ygeb3+c}>#^azvJj`;$(D*@HkEVkX}eO@q46|B^+JYR zl*Ct5P=QQ)Tpd43wI;C2ca1_K;W67|$u=2Pc}#vN=JO{oN&$^OjpU=YehuM+X|l6Z zblTF8kA8i-vprKW;gp^5vKaqSM!7HL%k51gl(@!^`Vo_FkEC$-=!wCU2zI03HL}e= zf78XQ17jyNcbit*=rN|0zFWl-Iw& zL#aeV`Qvjv>iCdNoKKHzZdrau%K_9@L1^gQYZm;*3Z9;^d$k*HOr)1ac|p<~Hm&3q zPiy?>CVI*zKOL*;5E;UaTA%1jn9=m=*k9$`kM=h_qD;1-)1Ck#RA$=fs^UC8A9MaB zI2W=cAH80w&KD-g5=}Hq2W=AY26O|q}8|pze$r9Dla7>8=xG|F&`|EUHGo#p4g+&H&#&*$A(fHYHbZf zVLNz9ar`q(X?_dTViY9NSX9urPeN6rqaS6A!O)yj9Xp0?=um3%9%Bh>Jv`SG`s5ep zk)^@NQjz4u@jHDcLPnxQv(zfg?KTzEs@@6AxULMLyQy_NxICTQLrX^mLEOn1MKTH+ zILfInsdg^;2caa+ZzP-aYAh z%5nzO>P2x8(=_{W$IV|!0-^ZSAW3g)iB`Dw}L=4}sZyHm=31t9>)hKOnM(?;%xO|80#z)$Kp(p|{i# zE5G#4Tti4vdDMn3>`VpyvOuPC=6MJVb1!0Wsy^NSgrc-P&gEfDSI+Sm{`!Sk^&lSJ zlte+~Q86-(S?#|-s_M-+-}q4`buVir;P92)b`LY#y^jl*YA3g_l#v0fV}KA4CQ`*R ziuO;Je=6NF{Frn*k`5Kn`8m%&Hg@J#&fA$N@8$EZRsQ9+sjt8s zdsJsiZ~M(Z0+qq-Eyrs=mH)N^r@#Cb8*-VQ3 zM1XG4>Up21tbdF4({(-t=|Ll(1kv6PeMyFv;x}ONa+1V_?%=yVRU7+FnsA-Nfpj-q1#E~>-bdkx%V9-~n(omCxrDhc@X3{mtG`1=s(e`@-W3`q^M8Cl6k?+%-MX0W)#gw>`SAq=@)pp1Lu+p!(>xJHN4 zUfO>I6Zd{j<-qkd55U(BT4T&_)t8_%=`g5s6`MX%3*BZhYpXQIXhlU#-M1RoMJZGO zsQ7Zd+r^-bn=2VgrWsn#^Y)`qw1;{`Ct`mQiABbsyv5~o3@QK`Hp>+^EQ97HR^G05 z%bP;s)#jcXKVornT%(G9h0EOFA>v}0I{l^igU+EO@SEs3bE%;l{{+!;J*Nn9twcPoX#hyJsKC7 zSCXK!Tx4O8p>C6*F~T=ng)Ie4l)ZxY)w_K&a)tm(R`AJ@Vthi63b&szSs6im=cwl> z8f6Amm-OY9t3iv3T;KCes|YC6`snH$sl=94TyEsqeAAy!<)J-e{nyeu9NyTpFjL_@ z?H?4!{tNjAJ~L^(Z;u;OrBMaoXLy=^+}}WDs;62&A3t)R=ve6`=&y}|U*)UyElLkp z^)FNKct+U+O-nT;jw=nM6BkgEC&KwBb8N0DnhW=8TsW*gh=6u;>_}sL4mnh>$=~J_ zTi`EiNWbPvs=66@P^f@%S;uatZ}S6@>TQOgvUJ8NX$4PwW;1kcK^JHBViS8#XL>Or zNUwe-;q~*c$yqX@wAUW<1>X1?r|6F)7Gb9YR!n_Lpdkh}e?-wpZF;{?*hb||ERVK1 z$ldO81BrpqXD-%Hhx&8{qP^GJOeV(08PyCKbWpBi@eD+~5e*Q{T1RY>nL6==XTYXA zfh7l;ZrbdnwvIX!>IgDfc_w%Jg@S9!h{;Wg8i8f1 zu)E3$q==ftrOOjLZkTJGTpV%qr{iO9XH&Ts!H5^jj^E3!#26o~12%$cb9Q^1cL;8RsYXWKPTnx>p;paQ9d3KYgbn+a zY}5Vw0~}#(H4H??vvYILR0R%6&wWc>ltzW~dp*7R5eKzjObsJjHE6EjbFg9z#&ys@ z>f$h3aF_|(cKwKwfoGFY7B)k*9HvwOy~|UGZZDJ@XDW@L1?%Ay9OOqlcJcXU$)aWm zls02g^OSs(gnwmHzOqB>CeZ}{JXtU%}! zv;yy0Ty>kF{+HJ=YHX)uB;{s%L=XJt)~?{7a$Ewc$9ybZi$`w8RKX}t{KTs?)xAOq zlZP0>?4#R)zHwLG{HZ!?fX8D&(GP|v9$2w$aeA^!cE$2Av4!*hGneHb$LD&=9z+Lj z1{WIYOuISQVcM(5RKBZUg;Ur@NBi1a^-N z3!SnHGJILC%u+tc9QP)sj=OcO)ezr3?_%%ki<1{p1hX2xJ_$tf9PFknXUo2i>h zO2{rO%JC9d4uZeGbMyTHAHlh%Krj`5@JsQ2C3VHc@@xCUo^wzqkBrtCd$zg{4%}u^ zMWIx(KN!h-*LmY@li1f`fYtJ8%x!Q0>eX0YFlESb4yg~0$(*or>uK7v1&R`$O3bj} ziAfjpf|JF(|IArxNNcX~&5pLiyhS|VW2!Oef$GZVLWH`2@=M)(b}jzps>k+;`!c8> ztOjidtGQq{)dU{ull=B(X7B^$@%X>0eqCrd*Zz1ClKkjK8(qjpcpS!+sb+mxV|eU> zND`JuoDX1>*w`f;*f;lBo(e41Udsy1Zzu>@R3y+J1TAGI4VID`{V-ELw_hO`d~f;U z;M~LKOgtQ?tvz*YICrwT#t>8{P6;#=(X-XD2b?oZkzj(CuPjObSrev*w&emE6-)9r{=K&YE=%hRpTDeE8GquupsHsp1h*>ZfSCX zl?u1w{~k_&hH7(%RJj2Woa-`_QOq4NGDdj(OcO5vuY2bGXv88)B3)brPK~T_#b>D# zy8=sKf0~O%S>?%JJudw*BN!I!tOK>Oa|(aXA0R3oz`-K*(aaxeetp z!~6hrtvl^H?TlT}dm3t7w<4mzeb^qGpCKQOK7}Mp=kjI*ZUDy9dVn!YJ6rYu*(G1g zB;&1g8aOa5@YZX`ASJJ<89}!}JXr=_%g(;9U=c;3x(dK>j=XI8^O8b`bd1c&uXM@P zh8dcf63E`x7w6}oUveNJOC8wA%Ag)#CV9Mht5N~;7}5!ZqORuaZ*Y+I=}FF2Q}+CP zV$axXTiabyo~hbL1H`}fQb#aZl%Li1&OU@16O7>-TugZ2*zKO=F;uLi+zIqvRux8}#h9bq4 ztZ>ytTmft(fiPqzzow+!!aN#g1QzP@Y zf!%!VpCMw5C)4E@5xbnq)Ew815dHj@|3 zhs%OiOpEF-{g;dD6F9>eyIULHuoJX$NI}71VaQk>ljh8Xz~y4gOgDBNGK!7G@_4b; zjd~G(e!~zZKIi>qlhSet?F7vdTQejE3-vp}fE@4Z#_htw`YQ2W3 zTJdJa?3!omBdY3u-4}+sMgM5Br9@HFvn+5XsJH^hXp{FE9Aabb5)A6Aw)%xIp}a-u zFM2!(Px;JM+-S)M_Q{`2@?tFT+KfFt$K-eJ9O+Z8=okzepbdZuW1H?U{88WFFDWHF z)(*qpRr2Rt8nkqNEfBd14cNb)9f=fM($9WY#XU^ulfJP*+&ah>7o@ROD`V>JIZ$e|B>VRxlrM56V!ja?ZRWZ z8pOBJIG*`R+qp6Vt~~Y|*i?vr-;{BrdnkWz5&lQl(C;@wWzD88 zOW%$do4<=O>54hZ7X0H_QQ=uh>EsWRxJ0?4CQC^P#v2t1<-1SaL@_+vw*jF z;Kc($ww@M{YnE8+k(_-uLs>xPJqF*QR)iF}#|4Q3u2B??rk;P@d|=DJZ^1dvQ`HU-rPwrlTdiY{s+M!K+K! zWg=w!HUUqj%q;AnN%xv}_IZY!x4NUU4B-A4E9&Aige+U{J*#3uEvL+8%Re_|`IoPV zlNJtz(U31P%y>#`wrO0}k>}>3eZb*E2)L{(IL%R$2gT~*MK)UP?c5Iywiz?yZRD*E ztDHT2>aEEV!zBO6i#3+JJNomH1W1D0xzSN-hL@WzkqJXh zpW6c8NAB|`)McEigYo2B2AX(j{N5+=_n#!(k4xFhK0C9wtAI=)xof>D?!tS0UDmTX zC%XJDrkgf&?)Z$gB__Ywf2$EVT`tG|;n?}jrTbig(E7CBXbS8c+~*JT`iSZOOmfs| z@lb@h4X`lYz;6fRPP*btUGeBEc$1D6riKHt>0Zh=suk{3?+ZSD6S`SYl1jPZqm>CS z)in599JhU}LyIb(kBBgIUT(Dmw=&mpWPfe%kCZb7Rnnhud!7@};2Cr2GFUmRL@o(sBX^De{03eJ(@zUH62eK_ z0@tbUE7osF>WC%1I5(sHlVnMVnKl%~nJyVD*klx?P%qDES#RTih#7Mgq_-7>Q63{* zfGJJ(Pxm)=n``jz_%7Yu*?{3Hc1Un+1CpwcL5hV~deub-S?8TQ1p3jklcWzrcvjC$ zuW0+i!W2={R8f=E`*Ewgu}qVD!};B~2S{m#q~Rjc_lG-_oSQ!!B(i8hK5DPCLVq0} zK3CTCGvx}^!T+rT=`k;sJE~ADx2((zGOH?Qqre7Alt@jDfs|cRcH@=Hja_5UZjgRk zFISz-tPaksc6nGm&sx+l?$%`4!6@^|{iNwk^TF@&w~~G@J{@d1elpbXCB+d-E13`7 za*m0T6iB^TrXY-c_YOPs;B0qdn)@iux)}F4kI)jTJ9VjnR8mJ}J-U4;M>*)gya==y zie7~KEW|m@V&;Bm8Q1L)s@Jd2O`hIlW;pMdwc5c#`l1i{Zcg2SMbSykRmQo&(4_2! z-aBlYQ*LkrfygwLp@^Nm^rPN)ya>Pf_m z8Xx~1oyG+lQpPv8`~N_NTvKBtbxr>Ho7UZX=_mUJ9j;klg4V)dsrrvD)+si9;o&`2 z=DJxTkV=l*cf{4Gw*Yzzd{ylG)l_tUWRd5xjXM~{>7L(k=p9x@-3;3s<`75&eX>8BQ+ z?XEk{-J8ht>y5&}s8IIH+df%!4w_&%dU;i;e_$|U=){>LJ^FMJun#7u3(8DzpwVbd!?-8sf!CWuJtA< zy5Q=LcF>Gn5&kpu*P!HGJc8HSHECQ^#^{@tA5k!P`BHApo^r09MJXJuy?PwE8_@R@ zB2HXu=@p^vDSga`n<2{2Qvh|((b_YA`gRR^H3e(xM$n!Rm@l`EkHz@$Oi4qPPxmR+ zLd+0Gl<#q>!pE7|28`BrE(WcdTcS&myfaEVDPcV5+@I|=VmDv8K+-A9W7WrZuCNa= z9Y8$htWOB5pPB}%&v%O=NQNOU5ve%8(I+_f1tdFJ&P)-Nm!BPXAua`dZNlXpOJ}26S@N>Tap#c7w)cw`203C&xe#Y>~)=cgzxARqMoyzyvMU@ z$LI#J1&P;as4DJ!cnDS(NO_e8t#itc==-`P%~o0Sh2gmakarkWXU38Q8Yp~0G;V0) zht*z>pv(zCq=1cGMHU5b`c8GmCLQA>T$rq!DH?OvQX<>{E*X&iB$F1k5`NPv)HgWU zGHbBS>2V|Xymz$k;{8 zZ>}lN?(+m)47%?%3mN!xFEzL#G(&tNqt6GSOn>Q=)1U|!k~L4Rk6${W`BRK*^{4ad zt(8xjqn+`F^(!*rS?ntOV5zfaMK#uNV}^b*d?4+dQC!eJ$majpMboc zi!syGtr-^!#ezQ=Z}-@HXDd zW*yFXy9f%U{K)t6;nQQZ02?J?#9gd5TXy8C&H|ERm0wkzt>f5bkE)U)8=;6ao8Y7| z9|5wy9)oZKiEYlAD0x=-EV1pN^p%x9m^!=2S3*L%Ab(vJ@b)zlbndV67Irpivi4$-5zW^ycE#V>I`y$RE-roTv()|7d6GSVAZ8eWa7 zzMYKrvpW~n;6Iv@hnI*{UlJY;G-!eBj#0fl8$Co4p{yLyfz{&sV~!?R8WQH4a&>)p zsAua}G{Up=zsmP3Dj59w1p`{F*zVmXg}}y7`d`gtX9`!PxKdvPgE#YqV~Z-=CA#^w2)l zX@l*ryBj_wLCsx)@eJSZK>KnE2ynUOhE9;fmjd^-2a#^ev3LSvEc zxU}rc*HV)qc9UB^djP!$@BJ?(DKIDKTQ=L!=x@JkfvygD|mTsNAW zwG}ue>r9j*z(CL9QsO@6xnqR+l3ZQ9+eLCiAMNYjsUGNr1WqP{u-nTV@7D5^+@Wkc zLk|Yk@bmY=%pLVQD6_XWXJv*Tdd>1O5cpOs3zKkY0!~Pm>T=i3E{UM4`B^=o(CV_# zuOs5Dhdu0sI}Gzg`2tyao#*MJ4Xw9{#U+-KlCm-~rQaA}hV~FUw}{6M*y=_Lq(>#o zL=v`GC_%1Dt~> z+-5W$pvV1`EP8QSviQ`s7-QV6?j3YY_g|+$}54Gwl*<{DQft9XK~#RUFut? zr0i6_=e%F+>MI%J+<40Ej+PMUR2wUg*|SOa7M#@y+PYoTV0*dV;$=y7xwef*E84}q zrkJ8z@sYC}N!FV$U#>jcNNe1Rdm+L_dLG)rmez1kMo!ozLf;@F+L%7tcwpPKekH?1 z4|IQ?-M}yU8Y?4l=ud>c$7SS=hE)@baLVgU+{3;_m%BHrX0DTSw@LRxNM%4I+3niH z@%nj;GxZ>lV4~MJD*#PHoP_kYd{DU8P%wk~lNAS$R6(|!`AUy!C+(}n#UN@giCjnB z`|Mzhv97*xUWc)8N9HM>4~}X6mOc@I;uWE()t+??yc*RLce(=MisN@)Fp3-ah)sM} zCjR6Cu5{thz(VQ6KH{!t-HFRL@^$S3aw+fGU!#{+|KIFWsr-JUi5?7Eowov@IYh`* z_iPeu-gtqI;xaPA@9V><@)+=-rA*b)hm|@^UirF88k~L`gL_Fg)9U_3VeY9s-gX5q zn}olo3uU4F9*K#*J5rm5V#L~Flv*E2q%?q7_NN!eOA2ep4(RI4g!nEqIscR(6*(Ex z-%s0oa96U6|JmnpX~^+tz0J$2M;{u%=t=(f#nME*C%=0_DpE_Y zO5iQAHPEe*{^?TKH`Oq_)Fe#INNx=+=g-86Eh${kntU9Ppl~zAm&Q%nuu0F6x338F zVNEoE+XpH4WI$ZUPC0*N2^8KcdmyjnuSA^?64!60er?>X_lu3+BWhT^? z{Dt3jqVC6+$KtQEHUYk5B!peKm1;=ONakM~IQld@xUKAk`j>MgUY-mI87H(PcPiPY zZQtiK1)9iQJ1>1_-p%BAebqbRp+_s*#_2*lF63`{MQ3G%`VKitw6+)RmrD0y4X3&7*y% zQd>n9$u2gZlh1PzpQf;s%f|F4XnnKBFjK`^F!?TV*>A-KP|vy^+$P0_I$|Rt6HE*? zvBTE0`}w{v+Kau;a%IAXSlxoq3%Mt8yF&t;p3U>x;_oKsANo$!%osWexkvCbDZX+6Z;{?sDI~0=Gy4w z*VKlDvAtuR2fJw*a(xW<=X|JaP5CcdeQBGxC!xfW&>I;i;1b`5S4_56fK}@MCq~r(+#PN4s;%`++OMY5NUG7J%0RFa?+3}B^m6r9 z;eJtSeR;J49p(NtU@q^P+-za4*Si>8UymZ)Qg@8$B}NqAd3meGReFq1vY@djE@G4V zQeH<0=~wsKh%@>32oC?U5Q)rnjyb-dK87@7u$TRlM|jv0AF9 z2vUtZQA#diuhB3HYiwr+?bRAfPX6ou>TZ@+yEmhwL|Azxns>{I#M98eRdl|uTCQ05 zd#P5c=i$3<&1*uXRqBVQ%KVwIVw0&B1$<@YA2B!{ftup@?hU7*kc>ASbg205pszVb z)eQvpGr8_L4#H!<_}a@@?lQw=ZL2Gpna*jRG6`(dn&LDy znqy3VKI5(4QK1sWqCYTSe{(dg_M%8v%UDT zYTWLa;t$>5Ww$SYV^)rir$>eL>`@%9sn_@jHQO$OIw#HSfxUm)b7(T~Yuvh9Pf0X1 z_B>Hr+^*&ZT+8Un408OFTUJWB=8yi`6a&=W-2XE9M2v}m}a?Cu@Ve{ zEcSwq5WMX}QxTDBet#=VM8Zx4{o{I3;akYF!^-yaI@@fOHxsGiw3K-lANKEc=Kgg*LqpcqBi6GgLf@4!^x38wI2H_B!$6cD@%#joh8?Wu*q2b4!Pc(=i_y~fTBz8j20D-$>`m|NOob;jC*k?>N|XE)Pq zls{BxY|n||JtzMv+&;VHWNg6_B*g;<2!~GzL3{{rI42KxLW-FkDqO3idR~^L$nh4n zjMLW5KfOWQhoixXf!GbsO3Ku!ht%E0j}Rrg%ubsgR{UHBWB>6&FXy=jsSTQm7wtTH z27Yb&cC(#f^S{T0Jp(O{rk1i7ud^a5ZCbC}gyTyr)n>hstURcA438JKgJO-_DeBFP zT}4?BZQaJ=O%_Yt=JPtTAt)~zdBL5{JNfpYg!Pxk%GRHcUXb-$?~3T?7~8695;d6WNo|j_a8gietP-Bd zUL-+9q^N=+rvBNp{0u6ov47uk60Y)nfJ<3A@PZR%-~F6O^@%;|IuuU+iRzm-u?3!k zEjMk#^Ke!fKKIbj5HjmJ*YzXA;BA>o`Ls}|MMTy=2WWS=sZ4&IF1O<0Ww*39=B-Ml zwNmZ3$${<9M3EjIE)lSQt;&S`T-YG<+c9l!Uu(4b1Oprrtf-Kd>n}C{`pZrpu!lZ4 zrn{yXDmIBT`>_p&UiD4QrQKzbYo40*nvJoCmug$?@112Aoj5f4xv{VJW;L)+&<-b4i8r{BET?ajs zkzrEr?h0zzVg zKXezqo@X3Y8cw!PQ_z+4IVA|rX4>^-mDJ9^aVo9Bflfy3<{NG=9Jd{o^h++n%o#2!Qz5fw%U`_7&*cJi=Jb z3%90vst}}ui=I9;P~oBerdCMp@Gu*4?Sr?BznXkX35nFfB-LJ(lu9C#yPJOsQu{O;!A=Wjr9t>3RkfV^YO7L zk7mWpnizz`6yGHDBFKJXks_tJ8t+R~?1?0(-<4cs9WJD#!*%+rw`}M_nLRJzzC3>} z^NTi}Vp!~plDwB-6nP9=1_I$&%5{n^vzVCm;b9}Pc#{3s8GuG z_DY!>mTNlNY`bs0CvBc~u%Qf)j!{BeeZM6`hY8CgmS|w`M9U+;O8Tm}b>k@}ug%3~ z_VMh)uY~(m^QJS~A8NJwlBt&53VC89+-LBBh_Z%jU>;e3i@4tc4`8N zE;AZ!Dak5u9SAkH+or!+e1G(&%{zh3TAh=ORJjwcW_PaShAdDkr7DwklbilTV|l#> z#`!UMwK=?UR3-A+DW zv)d(2kn9gKcvaBintJ=G1qJ&qd&8VS&3OAu%cfSOlhIXZdHj-_&6do8guS!BZOMd3 zG5fF~-v+!=*T80c>|BdCa{Y8T3a+z#e8Z*C#zHD+xi&Xv1K6Isw(i`cm&bg5Ye&Xt zv0)@r!;h@*v$K2jmde3y=d_6X#-BA#%8CxFzKNNVE(a}}WfO~_v~Po1-;aGIrR|l* zndw5HX8T(Hh;H?;5t68+IiDME?35-PyP|6K-ql;(fyIDSnTj*%0A26;fjthXSkI5k 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 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 + ? '
' + esc( r.preview ) + '
' : ''; + return '' + + '' + typeBadge + '' + + '
' + esc( r.displayName ) + '' + + restricted + preview + '' + + '' + esc( r.validityArea ) + '' + + '' + esc( r.status ) + '' + + '' + esc( r.language ) + '' + + '' + esc( r.docType ) + '' + + ''; + } ); + container.innerHTML = '' + + '' + + '' + + '' + + '' + + '' + rows.join( '' ) + '' + + '
TypeTitleValidity areaStatusLanguageDocument type
'; + } + + function setStatus( msg, busy ) { + var s = document.getElementById( 'gplx-search-status' ); + if ( !s ) { return; } + s.textContent = msg; + s.className = 'gplx-search-status' + ( busy ? ' gplx-search-busy' : '' ); + } + + function renderPage() { + var total = filteredResults.length; + var totalPages = Math.ceil( total / PAGE_SIZE ); + currentPage = Math.max( 1, Math.min( currentPage, totalPages || 1 ) ); + var start = ( currentPage - 1 ) * PAGE_SIZE; + var end = Math.min( start + PAGE_SIZE, total ); + + if ( total === 0 ) { + setStatus( '0 results', false ); + } else { + setStatus( + 'Showing ' + ( start + 1 ) + '–' + end + ' of ' + total + + ' result' + ( total !== 1 ? 's' : '' ), + false + ); + } + + renderTable( filteredResults.slice( start, end ) ); + renderPagination( total ); + } + + function refresh() { + filteredResults = applyFilters( allResults ); + currentPage = 1; + renderPage(); + } + + /* ---- build container UI ---- */ + + function buildUI( container ) { + var filterSection = document.createElement( 'div' ); + filterSection.className = 'gplx-filter-buttons'; + + var clearBtn = document.createElement( 'button' ); + clearBtn.type = 'button'; + clearBtn.className = 'gplx-filter-clear'; + clearBtn.textContent = 'Clear all filters'; + clearBtn.addEventListener( 'click', function () { + FILTER_GROUPS.forEach( function ( g ) { activeFilters[ g.key ] = {}; } ); + document.querySelectorAll( '.gplx-filter-btn' ).forEach( function ( b ) { + b.classList.remove( 'is-active' ); + b.setAttribute( 'aria-pressed', 'false' ); + } ); + refresh(); + } ); + + var searchLabel = document.createElement( 'label' ); + searchLabel.className = 'gplx-filter-label'; + searchLabel.htmlFor = 'gplx-filter-text'; + searchLabel.textContent = 'Search in titles and text'; + + var textInput = document.createElement( 'input' ); + textInput.type = 'search'; + textInput.id = 'gplx-filter-text'; + textInput.className = 'gplx-filter-input'; + textInput.placeholder = 'Search…'; + textInput.setAttribute( 'aria-label', 'Search in titles and text' ); + + var searchGroup = document.createElement( 'div' ); + searchGroup.className = 'gplx-filter-group gplx-filter-group--grow'; + searchGroup.style.marginBottom = '0.5rem'; + searchGroup.appendChild( searchLabel ); + searchGroup.appendChild( textInput ); + + var status = document.createElement( 'p' ); + status.id = 'gplx-search-status'; + status.className = 'gplx-search-status'; + + var results = document.createElement( 'div' ); + results.id = 'gplx-search-results'; + + var pagination = document.createElement( 'nav' ); + pagination.id = 'gplx-pagination'; + pagination.className = 'gplx-pagination'; + pagination.setAttribute( 'aria-label', 'Results pages' ); + + container.innerHTML = ''; + container.appendChild( filterSection ); + container.appendChild( clearBtn ); + container.appendChild( searchGroup ); + container.appendChild( status ); + container.appendChild( results ); + container.appendChild( pagination ); + + return { filterSection: filterSection, textInput: textInput }; + } + + /* ---- load data ---- */ + + function load( controls ) { + setStatus( 'Loading…', true ); + var api = new mw.Api(); + var docResults = [], refResults = []; + var docDone = false, refDone = false; + + function onBothDone() { + allResults = docResults.concat( refResults ); + fetchExtracts( allResults, function () { + var values = collectFieldValues( allResults ); + renderFilterButtons( controls.filterSection, values ); + if ( allResults.length ) { + refresh(); + } else { + setStatus( '', false ); + document.getElementById( 'gplx-search-results' ).innerHTML = ''; + } + } ); + } + + api.get( { action: 'ask', query: DOC_QUERY, format: 'json' } ) + .always( function ( data ) { + if ( data && data.query ) { docResults = parseResults( data, 'Document' ); } + docDone = true; + if ( refDone ) { onBothDone(); } + } ); + + api.get( { action: 'ask', query: REF_QUERY, format: 'json' } ) + .always( function ( data ) { + if ( data && data.query ) { refResults = parseResults( data, 'Reference' ); } + refDone = true; + if ( docDone ) { onBothDone(); } + } ); + } + + /* ---- init ---- */ + + mw.loader.using( [ 'mediawiki.api' ], function () { + $( function () { + var container = document.getElementById( 'gplx-search-app' ); + if ( !container ) { return; } + var controls = buildUI( container ); + var debounce; + controls.textInput.addEventListener( 'input', function () { + clearTimeout( debounce ); + debounce = setTimeout( refresh, 250 ); + } ); + load( controls ); + } ); + } ); +}() ); diff --git a/scripts/templates/Contribute_to_GxPlex.wiki b/scripts/templates/Contribute_to_GxPlex.wiki new file mode 100644 index 0000000..f5d9c6a --- /dev/null +++ b/scripts/templates/Contribute_to_GxPlex.wiki @@ -0,0 +1,26 @@ +== Contribute to GxPlex == + +Registered users can contribute documents and content to GxPlex. + +=== How to contribute === + +# Log in with your user account. +# Use [[Create_new_page|Create new page]] to start a new document. +# Fill in the required metadata fields (scope, status, area of validity, etc.). +# Submit the page — it will enter the approval workflow and be reviewed before publication. + +=== Roles === + +{| class="wikitable" +! Role !! Permissions +|- +| Author || Create and edit pages +|- +| Reviewer || Approve or reject submitted pages +|- +| Administrator || Full access including user management +|} + +=== Questions? === + +Contact the wiki administrator or visit [[About_GxPlex|About GxPlex]]. diff --git a/scripts/templates/Create_new_page.wiki b/scripts/templates/Create_new_page.wiki new file mode 100644 index 0000000..97eb89e --- /dev/null +++ b/scripts/templates/Create_new_page.wiki @@ -0,0 +1,13 @@ +== Create new page == + +Enter a title for your new document and click '''Create'''. + + +type=create +width=40 +placeholder=Document title… +buttonlabel=Create page +preload=Template:RegDocument + + +New pages are pre-filled with the [[Template:RegDocument|RegDocument]] template to ensure all required metadata fields are present. diff --git a/scripts/templates/DocumentIndex.wiki b/scripts/templates/DocumentIndex.wiki index c44e524..a6eda9f 100644 --- a/scripts/templates/DocumentIndex.wiki +++ b/scripts/templates/DocumentIndex.wiki @@ -1,70 +1,118 @@ -This page lists all regulatory documents in this wiki. Use the sections below to browse by status or scope. +This page lists all regulatory documents in this wiki. Use [[GxPlex_Search|GxPlex Search]] for filter-based browsing, or [[Special:Ask|Special:Ask]] for custom queries. -== Current documents == +== All documents == -{{#ask: [[Status::Current]] -| ?Document name = Name -| ?Scope = Scope -| ?Area of validity = Region -| ?Document type = Type +{{#ask: [[Document:+]] +| ?DocName = Document name +| ?DocScopes = Scope +| ?DocValidityArea = Region +| ?DocStatus = Status +| ?DocType = Type +| format=table +| headers=show +| limit=500 +| sort=DocName +| order=asc +}} + +== By status == + +=== Current === + +{{#ask: [[Document:+]] [[DocStatus::Current]] +| ?DocName = Document name +| ?DocScopes = Scope +| ?DocValidityArea = Region +| ?DocType = Type +| format=table +| headers=show +| limit=200 +| sort=DocName +| order=asc +}} + +=== Draft === + +{{#ask: [[Document:+]] [[DocStatus::Draft]] +| ?DocName = Document name +| ?DocScopes = Scope +| ?DocValidityArea = Region | format=table | headers=show | limit=100 -| sort=Scope +| sort=DocName }} -== Grouped by scope == +=== Superseded === + +{{#ask: [[Document:+]] [[DocStatus::Superseded]] +| ?DocName = Document name +| ?DocScopes = Scope +| ?DocValidityArea = Region +| format=table +| headers=show +| limit=100 +| sort=DocName +}} + +== By scope == + +=== Human Medicines (HM) === + +{{#ask: [[Document:+]] [[DocScopes::~*HM*]] +| ?DocName = Document name +| ?DocStatus = Status +| ?DocValidityArea = Region +| format=table +| headers=show +| limit=100 +| sort=DocName +}} === Medical Devices (MD) === -{{#ask: [[Scope::~*MD*]] -| ?Document name = Name -| ?Status = Status -| ?Area of validity = Region +{{#ask: [[Document:+]] [[DocScopes::~*MD*]] +| ?DocName = Document name +| ?DocStatus = Status +| ?DocValidityArea = Region | format=table | headers=show -| limit=50 +| limit=100 +| sort=DocName }} === In Vitro Diagnostics (IVD) === -{{#ask: [[Scope::~*IVD*]] -| ?Document name = Name -| ?Status = Status -| ?Area of validity = Region +{{#ask: [[Document:+]] [[DocScopes::~*IVD*]] +| ?DocName = Document name +| ?DocStatus = Status +| ?DocValidityArea = Region | format=table | headers=show -| limit=50 +| limit=100 +| sort=DocName }} === GMP / PV / NIS === -{{#ask: [[Scope::~*GMP*]] OR [[Scope::~*PV*]] OR [[Scope::~*NIS*]] -| ?Document name = Name -| ?Scope = Scope -| ?Status = Status +{{#ask: [[Document:+]] [[DocScopes::~*GMP*]] OR [[Document:+]] [[DocScopes::~*PV*]] OR [[Document:+]] [[DocScopes::~*NIS*]] +| ?DocName = Document name +| ?DocScopes = Scope +| ?DocStatus = Status | format=table | headers=show -| limit=50 +| limit=100 +| sort=DocName }} -== Draft and not yet promulgated == +== References == -{{#ask: [[Status::Draft]] OR [[Status::Final not promulgated]] -| ?Document name = Name -| ?Scope = Scope -| ?Area of validity = Region +{{#ask: [[Reference:+]] +| ?HasDocument = Document +| ?RefScope = Scope +| ?RefLanguage = Language | format=table | headers=show -| limit=50 -}} - -== Superseded == - -{{#ask: [[Status::Superseded]] -| ?Document name = Name -| ?Scope = Scope -| format=table -| headers=show -| limit=50 +| limit=200 +| sort=HasDocument }} diff --git a/scripts/templates/Editnotice_Advertisement.wiki b/scripts/templates/Editnotice_Advertisement.wiki new file mode 100644 index 0000000..ac851de --- /dev/null +++ b/scripts/templates/Editnotice_Advertisement.wiki @@ -0,0 +1,20 @@ +
+'''Advertisement – editing notes''' + +The content of this page is displayed below the toolbox in the left sidebar. Standard wikitext and links are supported. + +; Disable the ad slot +: Enter only a hyphen (-) 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. +
diff --git a/scripts/templates/GxPlex_Search.wiki b/scripts/templates/GxPlex_Search.wiki new file mode 100644 index 0000000..b4395c2 --- /dev/null +++ b/scripts/templates/GxPlex_Search.wiki @@ -0,0 +1,16 @@ +== Search in GxPlex == + +=== Full-text search === + +Searches across all page content, titles, and annotations. + + +type=fulltext +width=50 +searchbuttonlabel=Search +placeholder=Search all pages… + + +=== Filter documents and references === + +
Loading…
diff --git a/scripts/templates/Help_Adding_a_topic.wiki b/scripts/templates/Help_Adding_a_topic.wiki new file mode 100644 index 0000000..8c764e8 --- /dev/null +++ b/scripts/templates/Help_Adding_a_topic.wiki @@ -0,0 +1,59 @@ +This page explains how to add a new topic category to GxPlex so it appears automatically on [[Themes_and_topics|Themes and topics]]. + +== What is a topic? == + +A topic is a [[Special:Categories|category page]] that groups related regulatory documents. Every topic must belong to the parent category "Themes and topics" and carry a short description so it appears in the overview table. + +== Adding a topic via the browser == + +=== Step 1 – Open the new category page === + +Navigate to 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 @@ + +Display template for document metadata. Does not set any SMW properties — use {{#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=
+}}
+
+
{| class="wikitable gplx-infobox" +! colspan="2" class="gplx-infobox-header" | Document information +|- +! scope="row" | Reference +| {{{reference|}}} +|- +! scope="row" | Validity area +| {{{validity_area|}}} +|- +! scope="row" | Scope(s) +| {{{scopes|}}} +|- +! scope="row" | Document name +| {{{doc_name|}}} +|- +! scope="row" | Version / Revision +| {{{version|}}} +|- +! scope="row" | Status +| {{{status|}}} +|- +! scope="row" | Document type +| {{{doc_type|}}} +|- +! scope="row" | Language(s) +| {{{language|}}} +|- +! scope="row" | Description +| {{{description|}}} +|- +! scope="row" | Official source +| {{#if:{{{official_link|}}}|[{{{official_link|}}} Official link]|—}} +|- +! scope="row" | Restricted access +| {{{restricted|No}}} +|- +! scope="row" | Submitted by +| {{{submitted_by|}}} +|- +! scope="row" | Contributors +| {{{contributors|}}} +|} diff --git a/scripts/templates/Template_ReferenceBox.wiki b/scripts/templates/Template_ReferenceBox.wiki new file mode 100644 index 0000000..3e8c2ca --- /dev/null +++ b/scripts/templates/Template_ReferenceBox.wiki @@ -0,0 +1,57 @@ + +Sidebar template for reference-specific metadata on Reference: pages. Does not set any SMW properties — use {{#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=
+}}
+
+
{| class="wikitable gplx-infobox" +! colspan="2" class="gplx-infobox-header" | Reference details +|- +! scope="row" | Scope +| {{{scope|}}} +|- +! scope="row" | Document part +| {{{doc_part|}}} +|- +! scope="row" | Language +| {{{language|}}} +|- +! scope="row" | Tags (EN) +| {{{tags_en|}}} +{{#if:{{{tags_original|}}}| +|- +! scope="row" | Tags (original) +| {{{tags_original|}}} +}}{{#if:{{{tags_fr|}}}| +|- +! scope="row" | Tags (FR) +| {{{tags_fr|}}} +}} +|} diff --git a/scripts/templates/The_Project.wiki b/scripts/templates/The_Project.wiki new file mode 100644 index 0000000..59e24fc --- /dev/null +++ b/scripts/templates/The_Project.wiki @@ -0,0 +1,17 @@ +== The Project == + +GxPlex was started to address the need for a transparent, traceable document management system tailored to medical device regulation. + +=== Background === + +Regulatory documentation under MDR (EU 2017/745) and IVDR (EU 2017/746) requires structured, versioned, and approved records. GxPlex provides a wiki-based platform optimised for this workflow. + +=== Goals === + +* Traceable document lifecycle from draft to approved +* Full-text and property-based search across all documents +* Role-based access for authors, reviewers, and administrators + +=== Status === + +The project is currently in active development. diff --git a/scripts/templates/Themes_and_topics.wiki b/scripts/templates/Themes_and_topics.wiki new file mode 100644 index 0000000..b426faa --- /dev/null +++ b/scripts/templates/Themes_and_topics.wiki @@ -0,0 +1,11 @@ +{{#ask: [[Category:Themes and topics]] + | ?Description + | format=table + | headers=plain + | mainlabel=Topic + | limit=50 + | sort=title +}} + +---- +[[Help:Adding a topic|How to add or remove a topic →]]