From: jenkins-bot Date: Tue, 15 Nov 2016 03:48:31 +0000 (+0000) Subject: Merge "Fix SpecialPasswordResetOnSubmit parameter handling" X-Git-Tag: 1.31.0-rc.0~4870 X-Git-Url: http://git.cyclocoop.org/%7B%24admin_url%7Dmes_infos.php?a=commitdiff_plain;h=1493438594c13afa4384abb0c2ff14ecd844295d;hp=f9a86b01c975b4863fd7c9bc039353a5b8753f3c;p=lhc%2Fweb%2Fwiklou.git Merge "Fix SpecialPasswordResetOnSubmit parameter handling" --- diff --git a/.mailmap b/.mailmap index 5c82af8118..6d9f8e8bcb 100644 --- a/.mailmap +++ b/.mailmap @@ -1,30 +1,62 @@ +# Map author and committer names and email addresses to canonical real names +# and email addresses. +# +# To update the CREDITS file, run maintenance/updateCredits.php +# +# Two types of entries are useful here. The first sets a cannonical author +# name for a given email address: +# +# Cannonical Author Name +# +# The second allows collecting alternate email addresses into a single +# cannonical author name and email address: +# +# Cannonical Author Name +# +# Mappings are only needed for authors who have used multiple author names +# and/or author emails for revisions over time. Author names begenning with +# "[BOT]" will be omitted from the CREDITS file. +# +# See also: https://git-scm.com/docs/git-shortlog#_mapping_authors +# +[BOT] Gerrit Code Review [BOT] Gerrit Patch Uploader +[BOT] jenkins-bot +[BOT] jenkins-bot [BOT] Translation updater bot Aaron Schulz Aaron Schulz Adam Roses Wight +Adam Roses Wight addshore +Aditya Sastry Adrian Heine -Alex Monk -Alex Monk -Alex Z +Alex Z. Alexander Emsenhuber Alexander Emsenhuber Alexander Emsenhuber +Alexander Monk +Alexander Monk +Alexander Monk Alexia E. Smith Amir E. Aharoni Amir E. Aharoni +Amir Sarabadani Anders Wegge Jakobsen Andre Engels +Andrew Garrett Andrew Garrett Angela Beesley Starling Antoine Musso Antoine Musso Aran Dunkley Ariel Glenn +Ariel Glenn Arlo Breault +Arthur Richards Arthur Richards Aryeh Gregor +Asher Feldman Asher Feldman aude Audrey Tang @@ -32,25 +64,30 @@ Audrey Tang ayush_garg Bahodir Mansurov Bartosz Dziewoński -Bartosz Dziewoński Bartosz Dziewoński +Bartosz Dziewoński Ben Hartshorne Bene -Benjamin Lees +Bene +Benny Situ Benny Situ Bertrand Grondin Brad Jorsch +Brad Jorsch Brandon Harris -Brian Wolff Brian Wolff +Brian Wolff +Brian Wolff Brion Vibber Brion Vibber Brion Vibber Bryan Davis +Bryan Davis +Bryan Tong Minh Bryan Tong Minh C. Scott Ananian C. Scott Ananian -cacycle@gerrit.wikimedia.org +Cacycle cenarium Chad Horohoe Chad Horohoe @@ -58,44 +95,64 @@ Charles Melbye Chiefwei Chris McMahon Chris Steipp -Christian Aistleitner Christian Aistleitner +Christian Aistleitner Christian Williams Christian Williams Christian Williams +Christopher Johnson +church of emacs +Cindy Cicalese ckoerner Conrad Irwin Dan Duvall dan-nl Daniel A. R. Werner Daniel Cannon +Daniel Friesen +Daniel Friesen Daniel Friesen +Daniel Friesen Daniel Kinzler Daniel Kinzler -Danny B +Danny B. +Danny B. +Danny B. +Danny B. +Darian Anthony Patrick +Darkdragon09 David Chan +Dereckson +Derk-Jan Hartman +Derk-Jan Hartman Derk-Jan Hartman -Derk-Jan Hartman Diederik van Liere Domas Mituzas Douglas Gardner DPStokesNZ Ebrahim Byagowi Ed Sanders -Elliott Eggleston +Elliott Eggleston +Elliott Eggleston Emmanuel Engelhart -eranroz +Emufarmers +Emufarmers +Entlinkt +Eranroz Erik Bernhardson Erik Moeller Erik Moeller Erwin Dokter Evan McIntire +Evan Prodromou Federico Leva Fenzik Joseph -Florianschmidtwelzow -Florianschmidtwelzow Florian -Fomafix +Florian Schmidt +Florian Schmidt +fomafix +Fran Rogers Fran Rogers +freakolowsky FunPika Gabriel Wicke Gabriel Wicke @@ -110,31 +167,38 @@ glaisher Greg Sabino Mullane Greg Sabino Mullane Greg Sabino Mullane +Grunny Guy Van den Broeck Happy-melon Helder Helder Hoo man +Huji Huji Ian Baker Ilmari Karonen Inez Korczyński Inez Korczyński isarra +isarra Ivan Lanin -Jack Phoenix Jack Phoenix +Jack Phoenix Jackmcbarn -Jackmcbarn +Jackmcbarn jagori -James D. Forrester +James Forrester Jan Gerber +Jan Luca Naumann Jan Luca Naumann Jan Paul Posma Jan Zerebecki +Jared Flores Jaroslav Škarvada jarrettmunton +Jason Richey Jason Richey +Jason Richey Jeff Hall Jeff Hall Jeff Janes @@ -151,40 +215,58 @@ Jon Robson Juliusz Gonera Juliusz Gonera JuneHyeon Bae +Jure Kajzer Jure Kajzer +Karun Dambiec +Katie Filbert Katie Filbert Kevin Israel -Kunal Mehta -Kunal Mehta +Kunal Grover +Kunal Mehta +Kunal Mehta +Kunal Mehta Kwan Ting Chan lekshmi Leo Koppelkamm +Leon Liesener Leon Weber Leonardo Gregianin Leons Petrazickis -Liangent +liangent Lisa Ridley Ljudusika Luis Felipe Schenone +Lupo m4tx +Madman Magnus Manske Manuel Schneider <80686@users.mediawiki.org> +Marc-André Pelletier +Marcin Cieślak Marcin Cieślak +Marco Falke +MarcoAurelio Marielle Volz Marius Hoch -Mark A. Hershberger -Mark A. Hershberger -Mark A. Hershberger Mark Clements +Mark Hershberger +Mark Hershberger +Mark Hershberger +Mark Hershberger Mark Holmquist +Mark Holmquist Marko Obrovac +Markus Glaser +Markus Glaser Matt Johnston Matthew Britton Matthew Flaschen Matthias Mullie +Matthias Mullie Matěj Grabovský Max Semenik Max Semenik +Max Semenik mgooley Michael Dale mjbmr @@ -192,23 +274,30 @@ Mohamed Magdy Moriel Schottlender Moriel Schottlender Mormegil +MrBlueSky +MrBlueSky Mukunda Modell +Mwalker MZMcBride nadeesha Namit Nathaniel Herman Neil Kandalgaonkar Nemo bis -Nephele +nephele Nick Jenkins Nik Everett Niklas Laxström Niklas Laxström Nimish Gautam Nuria Ruiz -Ori.livneh +Ori Livneh +Ori Livneh OverlordQ +Owen Davis +Owen Davis paladox +Patrick Reilly Patrick Reilly Patrick Westerhoff Paul Copperman @@ -223,9 +312,9 @@ PranavK Prateek Saxena Prateek Saxena Priyanka Dhanda -Purodha B Blissenbach -Purodha B Blissenbach -Purodha B Blissenbach +Purodha Blissenbach +Purodha Blissenbach +Purodha Blissenbach Raimond Spekking Raimond Spekking Remember the dot @@ -233,13 +322,15 @@ Reza Ricordisamoa rillke rillke -River Tarnell River Tarnell +River Tarnell Roan Kattouw Roan Kattouw Roan Kattouw Rob Church +Rob Lanphier Rob Lanphier +Rob Lanphier Rob Moen Rob Moen Rob Moen @@ -247,24 +338,30 @@ Robert Hoenig Robert Leverington Robert Rohde Robert Stojnić +Robin Pepermans Robin Pepermans robinhood701 Rohan Rotem Liss Rummana Yasmeen Russ Nelson -Ryan Kaldari Ryan Kaldari Ryan Kaldari +Ryan Kaldari +Ryan Lane Ryan Lane +Ryan Lane +Ryan Schmidt +Ryan Schmidt Ryan Schmidt S Page Sam Reed +Sam Reed +Sam Reed Sam Reed Sam Smith -Santhosh Thottingal Santhosh Thottingal -saper +Santhosh Thottingal Schnark Scimonster Sean Colombo @@ -272,11 +369,14 @@ Sean Pringle Seb35 Sergio Santoro Shahyar +Shinjiman Shinjiman Siebrand Mazeland Siebrand Mazeland Siebrand Mazeland Siebrand Mazeland +Smriti Singh +Sorawee Porncharoenwase Southparkfan SQL Stanislav Malyshev @@ -288,9 +388,10 @@ Steven Roddis Subramanya Sastry Sucheta Ghoshal Sumit Asthana +Swalling Thalia Chan -TheDJ Thiemo Mättig (WMDE) +Thiemo Mättig (WMDE) This, that and the other tholam Thomas Bleher @@ -305,29 +406,45 @@ Timo Tijhof Timo Tijhof Timo Tijhof Tina Johnson +Tisane +Tjones Tom Maaswinkel Tomasz Finc +Tomasz W. Kozlowski +Tomasz W. Kozlowski +Tomasz W. Kozlowski Tony Thomas <01tonythomas@gmail.com> +Tpt Trevor Parscal Trevor Parscal Trevor Parscal Tyler Cipriani Tyler Romeo -umherirrender +Umherirrender +Victor Vasiliev Victor Vasiliev +Victor Vasiliev Vikas S Yaligar Vivek Ghaisas wctaiwan withoutaname X! +Yaron Koren +Yaron Koren Yaroslav Melnychuk +Yongmin Hong +Yongmin Hong +Yongmin Hong Yuri Astrakhan +Yuri Astrakhan Yuri Astrakhan Yusuke Matsubara -YuviPanda +Yuvi Panda Zak Greant +Zhengzhu Feng +Zhengzhu Feng +Zppix Ævar Arnfjörð Bjarmason +Étienne Beaulé Željko Filipin Željko Filipin -Zhengzhu Feng -Zhengzhu Feng diff --git a/CREDITS b/CREDITS index 46d5c9ca0b..1c1cf8795c 100644 --- a/CREDITS +++ b/CREDITS @@ -6,252 +6,657 @@ following names for their contribution to the product. --> -== Developers == -* Aaron Schulz -* Alex Z. -* Alexander Monk -* Alexandre Emsenhuber -* Andrew Garrett -* Antoine Musso -* Arthur Richards -* Aryeh Gregor -* Bartosz Dziewoński -* Bertrand Grondin -* Brad Jorsch -* Brian Wolff -* Brion Vibber -* Bryan Davis -* Bryan Tong Minh -* Chad Horohoe -* Charles Melbye -* Chris Steipp -* church of emacs -* Daniel Friesen -* Daniel Kinzler -* Daniel Renfro -* Danny B. -* David McCabe -* Derk-Jan Hartman -* Domas Mituzas -* Ed Sanders -* Emufarmers -* Fran Rogers -* Greg Sabino Mullane -* Guy Van den Broeck -* Happy-melon -* Hojjat -* Ian Baker -* Ilmari Karonen -* Jack D. Pond -* Jack Phoenix -* Jackmcbarn -* James Forrester -* Jan Paul Posma -* Jason Richey -* Jeroen De Dauw -* John Du Hart -* Jon Harald Søby -* Juliano F. Ravasi -* JuneHyeon Bae -* Leo Koppelkamm -* Leon Weber -* Leslie Hoare -* Marco Schuster -* Marius Hoch -* Matěj Grabovský -* Matt Johnston -* Matthew Flaschen -* Max Semenik -* Meno25 -* MinuteElectron -* Mohamed Magdy -* Nathaniel Herman -* Neil Kandalgaonkar -* Nicolas Dumazet -* Niklas Laxström -* Ori Livneh -* Patrick Reilly -* Philip Tzou -* Platonides -* Purodha Blissenbach -* Raimond Spekking -* Remember the dot -* Roan Kattouw -* Robert Stojnić -* Robin Pepermans -* Rotem Liss -* Ryan Kaldari -* Ryan Lane -* Ryan Schmidt -* Sam Reed -* Shinjiman -* Siebrand Mazeland -* Soxred93 -* SQL -* Szymon Świerkosz -* This, that and the other -* Thomas Bleher -* Thomas Gries -* Tim Starling -* Timo Tijhof -* Trevor Parscal -* Tyler Anthony Romeo -* Victor Vasiliev -* Yesid Carrillo -* Yuri Astrakhan - -== Patch Contributors == +== Contributors == + + +* aalekhN * Aaron Ball * Aaron Pramana +* Aaron Schulz +* Aarti Dwivedi +* Aashaka Shah +* abhinand +* Abhishek Das +* Adam Miller +* Adam Roses Wight +* addshore +* Aditya Sastry +* Adrian Heine +* Adrian Lang +* Ævar Arnfjörð Bjarmason * Agbad * Ahmad Sherif +* Ajayrahul P +* Alangi Derick +* Albert221 * Alejandro Mery +* AlephNull +* Alex Ivanov +* Alex Shih-Han Lin +* Alex Z. +* Alexander Emsenhuber +* Alexander I. Mashin +* Alexander Lehmann +* Alexander Monk +* Alexander Sigachov +* Alexandre Emsenhuber +* Alexia E. Smith * Amalthea * Amir E. Aharoni +* Amir Sarabadani +* ananay +* Anders Wegge Jakobsen +* Andre Engels +* Andrew Bogott * Andrew Dunbar +* Andrew Garrett +* Andrew Green +* Andrew H +* Andrew Harris +* Andrew Otto +* Andrius R +* andymw +* Angela Beesley Starling +* ankur +* Antoine Musso * Antonio Ospite +* apexkid +* April King +* Aran Dunkley +* Arash Boostani +* Arcane21 +* Ariel Glenn +* Arlo Breault +* Arne Heizmann +* Arthur Richards +* Aryeh Gregor +* Asher Feldman * Asier Lostalé +* ayush_garg * Azliq7 * Bagariavivek +* Bahodir Mansurov +* balloonguy +* Bartosz Dziewoński * Beau +* Ben Davis +* Ben Hartshorne +* Bene * Benny Situ * Bergi +* Bertrand Grondin +* Bill Traynor +* Billinghurst +* billm +* blotmandroid +* Bogdan Stancescu +* Boris Nagaev * Borislav Manolov +* Brad Jorsch +* Brandon Black +* Brandon Harris * Brent G +* Brent Garber +* Brian Wolff * Brianna Laugher +* Brion Vibber +* Bryan Davis +* Bryan Tong Minh +* burthsceh +* C. Scott Ananian +* Cacycle +* Calak +* Camille Constans +* Carl Fürstenberg * Carlin * Carsten Nielsen +* Cblair91 +* cenarium +* Chad Horohoe +* Charles Melbye +* Chiefwei +* Chris McMahon +* Chris Seaton +* Chris Steipp * Christian Aistleitner +* Christian List * Christian Neubauer +* Christopher Johnson +* church of emacs +* Cindy Cicalese +* ckoerner * Conrad Irwin * cryptocoryne * Dan Barrett * Dan Collins +* Dan Duvall * Dan Nessett +* Dan Poltawski +* dan-nl +* Daniel A. R. Werner * Daniel Arnold +* Daniel Cannon +* Daniel De Marco +* Daniel Evans +* Daniel Friesen +* Daniel Kinzler +* Daniel Renfro * Daniel Werner +* DanielRenfro +* Danny B. +* Darian Anthony Patrick +* Darkdragon09 +* DaSch * David Baumgarten +* David Chan +* David E. Narváez +* David Lynch +* David McCabe +* David Mudrák +* dcausse +* dennisroczek * Denny Vrandecic +* Dereckson +* Derk-Jan Hartman +* Derric Atzrott +* Derrick Coetzee * Dévai Tamás +* Devi Krishnan +* Diederik van Liere +* Domas Mituzas +* Douglas Gardner +* DPStokesNZ +* dr0ptp4kt * Ebrahim Byagowi +* Ed Sanders +* Edward Chernenko * Edward Z. Yang +* Elisabeth Bauer +* Elliott Eggleston * Elvis Stansvik +* Emil Podlaszewski +* Emmanuel Engelhart +* Emmanuel Gil Peyrot +* Emmet Hikory +* Emufarmers +* enigmaeth +* Entlinkt * Eranroz +* Eric Evans +* Eric Schneider +* Erich Lerch +* Erick Guan +* Erik Bernhardson +* Erik Moeller * Erwin Dokter * Étienne Beaulé +* Evan McIntire +* Evan Prodromou +* ExplosiveHippo +* Faidon Liambotis * Federico Leva +* Fenzik Joseph +* firebus * Florian Schmidt * fomafix +* Fran Rogers +* Fred Emmott * FunPika * Gabriel Wicke +* Gary Guo +* gbt248 * Geoffrey Mon +* georggi +* Gergő Tisza * Gero Scholz +* gicode +* Giftpflanze +* Gilles Dubuc * Gilles van den Hoven +* Giuseppe Lavagetto +* gladoscc +* glaisher +* Greg Maxwell +* Greg Sabino Mullane +* Gregory Szorc * Grunny +* Guillaume Blanchard +* Guy Van den Broeck +* Happy-melon +* haritha28 * Harry Burt +* Hazard-SJ +* Hector A Escobedo +* Helder +* Henning Snater +* Hojjat +* Huji +* Hydriz +* Ian Baker +* Ilmari Karonen +* Inez Korczyński +* IoannisKydonis * Ireas +* isarra +* Ivan Lanin +* Jack D. Pond +* Jack Phoenix +* Jackmcbarn * Jacob Block +* Jacob Clark +* jagori +* Jakub Vrana +* James Earl Douglas +* James Forrester +* Jan Berkel +* Jan Drewniak * Jan Gerber * Jan Luca Naumann +* Jan Paul Posma +* Jan Zerebecki +* Jared Flores +* Jaroslav Škarvada +* jarrettmunton +* jarry1250 * Jaska Zedlik +* Jason Richey +* jeblad +* Jeff Janes +* jeff303 +* Jens Frank +* Jens Ohlig +* Jérémie Roquet * Jeremy Baron +* Jeremy Postlethwaite +* jeremyb +* Jeroen De Dauw +* Jerome Jamnicky +* Jesús Martínez Novo +* jhobs +* Jiabao * Jidanni +* Jimmy Collins * Jimmy Xu +* joakin +* Joan Creus +* Joel Natividad +* Joerg +* Johan Dahlin +* John Du Hart * John N +* Jon Harald Søby +* Jon Robson * Jonathan Wiltshire +* Jools Wills +* jsahleen +* Julian Ostrow +* Juliano F. Ravasi +* Juliusz Gonera +* JuneHyeon Bae * Jure Kajzer +* Justin Du +* Kai_WMDE +* kaligula +* Kartik Mistry * Karun Dambiec * Katie Filbert * Kevin Israel +* Kghbln +* Kim Eik * Kim Hyun-Joon +* kipod +* kishanio +* konarak +* krishna keshav +* Krzysztof Krzyzaniak +* Krzysztof Zbudniewek +* Kunal Grover +* Kunal Mehta +* Kwan Ting Chan +* Laurence Parry +* Lee Bousfield +* Lee Daniel Crocker * Lee Worden * Lejonel +* lekshmi +* Leo Koppelkamm * Leon Liesener +* Leon Weber +* Leonardo Gregianin +* Leons Petrazickis +* Leslie Hoare +* Leszek Manicki +* lethosor +* Lewis Cawte +* Liam Edwards-Playne * liangent +* Lisa Ridley +* Ljudusika +* Lojjik Braughler * Louperivois +* Ltrlg +* Luc Van Oostenryck * Lucas Garczewski * Luigi Corsaro +* Luis Felipe Schenone * Luke Faraone +* Lupin * Lupo +* lwelling +* m4tx * Madman +* madurangasiriwardena +* Magnus Manske * Manuel Menal +* Manuel Schneider +* Marc Ordinas i Llopis * Marc-André Pelletier * Marcin Cieślak +* Marco Falke +* Marco Schuster +* MarcoAurelio * Marcus Buck +* Marius Hoch +* Mark Bergsma +* Mark Clements * Mark Hershberger * Mark Holmquist +* Marko Obrovac +* Markus Glaser +* Markus Krötzsch * Marooned +* Martin Urbanec +* Massaf +* Matěj Grabovský +* matejsuchanek * Mathias Ertl * mati +* Matt Fitzpatrick +* Matt Johnston +* Matt Russell +* Matthew Bowker * Matthew Britton +* Matthew Flaschen +* Matthias Jordan * Matthias Mullie +* MatthiasDD * Max +* Max Semenik * Max Sikström +* mayankmadan +* Meno25 * merl +* Merlijn S. van Deen +* MGChecker +* mgooley +* mhutti1 * Michael Dale * Michael De La Rue +* Michael Holloway * Michael M. * Michael Newton * Michael Walsh +* Michał Łazowik +* Michał Roszka +* Michał Zieliński * Mike Horvath +* Minh Nguyễn +* MinuteElectron +* Misza13 +* mjbmr * moejoe0000 +* Mohamed Magdy +* Molly White +* Moriel Schottlender * Mormegil +* Mr. E23 * MrBlueSky * MrPete +* Mukunda Modell +* Mwalker +* mwjames * mybugs.mail * MZMcBride +* nadeesha * Nakon +* Namit * Nathan Larson +* Nathaniel Herman +* Neil Kandalgaonkar +* Nemo bis * nephele +* Nicholas Pisarro, Jr +* Nick Jenkins +* nicoco007 +* Nicolas Dumazet +* Nicolas Weeger * Nik +* Nik Everett +* Niklas Laxström * Nikola Kovacs +* Nikola Smolenski * Nikolaos S. Karastathis +* Nimish Gautam * Nischay Nahata +* nischayn22 +* nomoa +* nullspoon +* Nuria Ruiz * Nx.devnull +* Ocean behind ears * Olaf Lenz * Olivier Finlay Beaton +* onei +* opatel99 +* Oren Held +* Ori Livneh +* oskar.jauch@gmail.com +* OverlordQ +* Owen Davis +* Paa Kwesi Imbeah +* paladox * Patricio Molina +* Patrick Reilly +* Patrick Westerhoff +* Pau Giner * Paul Copperman * Paul Oranje +* Pavel Astakhov +* Pavel Selitskas +* Pcoombe +* Perside Rosalie * Peter Gehres +* Peter Hedenskog +* Peter Potrowl +* Petr Bena +* Petr Kadlec * Petr Onderka +* Petr Pchelko +* Philip Tzou +* physikerwelt (Moritz Schubotz) * PieRRoMaN +* Pikne +* PiRSquared17 +* Platonides +* Pmlineditor +* pmolina +* prageck +* Pranav Ravichandran +* PranavK +* Prateek Saxena +* Priyanka Dhanda +* Prod +* ptarjan +* pubudu538 +* Purodha Blissenbach +* quiddity * quietust +* Quim Gil +* rahul21 +* Raimond Spekking +* Ramunas Geciauskas +* Remember the dot * René Kijewski +* Reza * rgcjonas +* Ricordisamoa +* rillke +* River Tarnell +* Roan Kattouw +* Rob Church +* Rob Lanphier * Rob Moen +* Robert Hoenig +* Robert Leverington +* Robert Rohde +* Robert Stojnić * Robert Treat +* Robert Vogel +* Robin Pepermans +* robinhood701 * RockMFR +* Rohan +* Roman Nosov +* Roman Tsukanov +* Rotem Liss +* Rowan Collins +* Russ Nelson * Russell Blau * Rusty Burchfield +* Ruud Koot +* Ryan Bies +* Ryan Finnie +* Ryan Kaldari +* Ryan Lane +* Ryan Schmidt * S Page * Salvatore Ingala +* Sam Reed +* Sam Smith * Santhosh Thottingal +* Schnark +* Scimonster +* scnd * Scott Colcord * se4598 +* Sean Colombo +* Sean Pringle +* Seb35 +* Sebastian Brückner * Sébastien Santoro +* Sergio Santoro +* Sethakill +* Shahyar +* Shane Gibbons +* Shane King +* Shinjiman +* shirayuki +* Sidhant Gupta +* Siebrand Mazeland * Simon Walker +* Smriti Singh * Solitarius +* Sorawee Porncharoenwase * Søren Løvborg * Southparkfan +* Soxred93 +* SQL * Srikanth Lakshmanan +* Stanislav Malyshev * Stefano Codari +* Steinsplitter +* Stephan Gambke +* Stephan Muggli +* Stephane Bisson +* Stephen Liang +* Steve Sanbeg +* Steven Roddis * Str4nd * Subramanya Sastry +* Sumit Asthana * svip +* Swalling +* Szymon Świerkosz +* T.D. Corell +* Tarquin +* The Discoverer * The Evil IP address +* theopolisme +* Thiemo Mättig (WMDE) +* This, that and the other +* tholam +* Thomas Arrow +* Thomas Bleher +* Thomas Dalton +* Thomas Gries +* ThomasV +* Tim Hollmann * Tim Landscheidt +* Tim Laqua +* Tim Starling +* Timo Tijhof +* Tina Johnson * Tisane +* tjlsangria +* Tjones +* TK-999 +* Tobias Gritschacher +* Tom Arrow +* Tom Gilder +* Tom Maaswinkel +* Tomasz Finc +* Tomasz W. Kozlowski +* Tomasz Wegrzanowski +* tomek +* Tony Thomas +* Tpt +* Trevor Parscal +* TyA +* Tychay +* Tyler Anthony Romeo +* Tyler Cipriani +* Tyler Romeo +* U-REDMOND\emadelw +* UltrasonicNXT * Umherirrender +* utkarsh95 * Van de Bugger +* Viačeslav +* Victor Porton +* Victor Vasiliev +* victorbarbu * Ville Stadista +* vishnu * Vitaliy Filippov * Vivek Ghaisas +* vlakoff +* Volker E * Waldir Pimenta +* wctaiwan +* Wikinaut +* Wil Mahan * William Demchick +* withoutaname +* WMDE-Fisch +* X! +* XP1 +* Yaron Koren +* Yaroslav Melnychuk +* Yesid Carrillo +* Yogesh K S +* Yongmin Hong +* yoonghm +* Yuri Astrakhan * Yusuke Matsubara * Yuvi Panda * Zachary Hauri +* Zak Greant +* Željko Filipin +* Zhaofeng Li +* Zhengzhu Feng +* Zppix +* محمد شعیب + == Translators == diff --git a/RELEASE-NOTES-1.28 b/RELEASE-NOTES-1.28 index a2a986f1d1..58ae23b39b 100644 --- a/RELEASE-NOTES-1.28 +++ b/RELEASE-NOTES-1.28 @@ -5,6 +5,20 @@ THIS IS NOT A RELEASE YET MediaWiki 1.28 is an alpha-quality branch and is not recommended for use in production. +=== Changes since 1.28.0rc0 === +* (T142210) The changes to move the parser "NewPP limit report" from a HTML + comment to a machine-readable JavaScript config option 'wgPageParseReport' + have been undone. They caused the human-readable limit report to be shown + incompletely or not at all. ParserOutput::setLimitReportData() and + getLimitReportData() behave as they did in MediaWiki 1.27 again. +* (T149510) Value of {{DISPLAYTITLE:}} parser function will not be used for + the text of subheadings on a category page when creating it. This wasn't + working correctly. +* (T106793) MediaWiki will no longer try to perform a HTTP redirect to the + canonical pretty URL when a non-pretty URL is used. It resulted in redirect + loops in some clients and in some server configurations. This undoes a change + made in MediaWiki 1.26. + === Configuration changes in 1.28 === * $wgSend404Code now affects status code of action=history if the page is not there. * BREAKING CHANGE: $wgHTTPProxy is now *required* for all external requests @@ -34,6 +48,14 @@ production. instead of just administrators ('sysop'). Documentation for this feature is available at . * $wgRevisionCacheExpiry is now set to one week by default instead of being disabled. +* Magic links are now disabled by default, and can be re-enabled by modifying the value + of $wgEnableMagicLinks. Their usage is discouraged, but if they are manually enabled, + a tracking category will be added to help identify usage and make it easier to migrate + away from. If you depend upon magic link functionality, it is requested that you comment + on and + explain your use case(s). +* New config variable $wgCSPFalsePositiveUrls to control what URLs to ignore + in upcoming Content-Security-Policy feature's reporting. === New features in 1.28 === * User::isBot() method for checking if an account is a bot role account. @@ -177,6 +199,10 @@ changes to languages because of Phabricator reports. Saiddzone Saimawnkham, Saosukham, and Sengwan. * Czech (cs) and Slovak (sk) set as reciprocal fallbacks. * (T146744) Livvi-Karelian (olo) namespace messages created thanks to translator Ilja.mos. +* Karelian (krl), thanks to translators Flrn, Ilja.mos, Likopiän tyttö, Mashoi7, Matma Rex, + Ontoi, Theunitedstatesofme, and Varvana. +* Gorontalo (gor), thanks to translators Ilham, Lukman Tomayahu, Marwan Mohamad, Matma Rex, + NoiX180, and Zhoelyakin. === Other changes in 1.28 === * (T128697) Improved handling of large diffs. @@ -203,7 +229,6 @@ changes to languages because of Phabricator reports. * Skin::linkKnown() (use MediaWiki\Linker\LinkRenderer instead) * Skin::userLink() (use Linker::userLink() instead) * Skin::userToolLinks() (use Linker::userToolLinks() instead) -* The 'ParserLimitReportFormat' hook was removed. * Disabled "bug 2702" HTML tidying of parsed UI messages on wikis where Tidy is disabled. * DifferenceEngine::generateDiffBody() was removed (deprecated since 1.21). @@ -223,8 +248,6 @@ changes to languages because of Phabricator reports. Instead of --keep-uploads, use the same option to parserTests.php, but you must specify a directory with --upload-dir. * The 'jquery.arrowSteps' ResourceLoader module is now deprecated. -* (T62604) Core parser functions returning a number now format the number according - to the page content language, not wiki content language. * IP::isConfiguredProxy() and IP::isTrustedProxy() were removed. Callers should migrate to using the same functions on a ProxyLookup instance, obtainable from MediaWikiServices. diff --git a/RELEASE-NOTES-1.29 b/RELEASE-NOTES-1.29 index 6c5380942b..fa1e1c4e69 100644 --- a/RELEASE-NOTES-1.29 +++ b/RELEASE-NOTES-1.29 @@ -20,6 +20,10 @@ production. === Bug fixes in 1.29 === === Action API changes in 1.29 === +* Submitting sensitive authentication request parameters to action=clientlogin, + action=createaccount, action=linkaccount, and action=changeauthenticationdata + in the query string is now an error. They should be submitted in the POST + body instead. === Action API internal changes in 1.29 === @@ -30,6 +34,8 @@ regularly. Below only new and removed languages are listed, as well as changes to languages because of Phabricator reports. === Other changes in 1.29 === +* Database::getSearchEngine() (deprecated in 1.28) was removed. Use + SearchEngineFactory::getSearchEngineClass() instead. == Compatibility == diff --git a/autoload.php b/autoload.php index 8b1e9c55f4..30ef985be0 100644 --- a/autoload.php +++ b/autoload.php @@ -859,6 +859,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php', 'MediaWiki\\Interwiki\\ClassicInterwikiLookup' => __DIR__ . '/includes/interwiki/ClassicInterwikiLookup.php', 'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ . '/includes/interwiki/InterwikiLookup.php', + 'MediaWiki\\Interwiki\\InterwikiLookupAdapter' => __DIR__ . '/includes/interwiki/InterwikiLookupAdapter.php', 'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php', 'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php', 'MediaWiki\\Linker\\LinkRenderer' => __DIR__ . '/includes/linker/LinkRenderer.php', @@ -920,6 +921,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php', 'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php', 'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php', + 'MediaWiki\\Widget\\DateInputWidget' => __DIR__ . '/includes/widget/DateInputWidget.php', 'MediaWiki\\Widget\\DateTimeInputWidget' => __DIR__ . '/includes/widget/DateTimeInputWidget.php', 'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php', 'MediaWiki\\Widget\\SearchInputWidget' => __DIR__ . '/includes/widget/SearchInputWidget.php', @@ -1008,6 +1010,7 @@ $wgAutoloadLocalClasses = [ 'OrphanStats' => __DIR__ . '/maintenance/storage/orphanStats.php', 'Orphans' => __DIR__ . '/maintenance/orphans.php', 'OutputPage' => __DIR__ . '/includes/OutputPage.php', + 'PHPVersionCheck' => __DIR__ . '/includes/PHPVersionCheck.php', 'PNGHandler' => __DIR__ . '/includes/media/PNG.php', 'PNGMetadataExtractor' => __DIR__ . '/includes/media/PNGMetadataExtractor.php', 'PPCustomFrame_DOM' => __DIR__ . '/includes/parser/Preprocessor_DOM.php', @@ -1530,6 +1533,7 @@ $wgAutoloadLocalClasses = [ 'WatchAction' => __DIR__ . '/includes/actions/WatchAction.php', 'WatchedItem' => __DIR__ . '/includes/WatchedItem.php', 'WatchedItemQueryService' => __DIR__ . '/includes/WatchedItemQueryService.php', + 'WatchedItemQueryServiceExtension' => __DIR__ . '/includes/WatchedItemQueryServiceExtension.php', 'WatchedItemStore' => __DIR__ . '/includes/WatchedItemStore.php', 'WatchlistCleanup' => __DIR__ . '/maintenance/cleanupWatchlist.php', 'WebInstaller' => __DIR__ . '/includes/installer/WebInstaller.php', diff --git a/composer.json b/composer.json index fdbd0cd7d7..e1d9f47962 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ext-xml": "*", "liuggio/statsd-php-client": "1.0.18", "mediawiki/at-ease": "1.1.0", - "oojs/oojs-ui": "0.17.10", + "oojs/oojs-ui": "0.18.0", "oyejorge/less.php": "1.7.0.10", "php": ">=5.5.9", "psr/log": "1.0.0", diff --git a/docs/hooks.txt b/docs/hooks.txt index 562d7b4b2f..5b707c1ca2 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -565,6 +565,18 @@ your callback to the $tokenFunctions array and return true (returning false makes no sense). &$tokenFunctions: array(action => callback) +'ApiQueryWatchlistExtractOutputData': Extract row data for ApiQueryWatchlist. +$module: ApiQueryWatchlist instance +$watchedItem: WatchedItem instance +$recentChangeInfo: Array of recent change info data +&$vals: Associative array of data to be output for the row + +'ApiQueryWatchlistPrepareWatchedItemQueryServiceOptions': Populate the options +to be passed from ApiQueryWatchlist to WatchedItemQueryService. +$module: ApiQueryWatchlist instance +$params: Array of parameters, as would be returned by $module->extractRequestParams() +&$options: Array of options for WatchedItemQueryService::getWatchedItemsWithRecentChangeInfo() + 'ApiRsdServiceApis': Add or remove APIs from the RSD services list. Each service should have its own entry in the $apis array and have a unique name, passed as key for the array that represents the service data. In this data array, the @@ -1188,7 +1200,6 @@ wrapped in a span element which has class="patrollink". $differenceEngine: DifferenceEngine object &$markAsPatrolledLink: The "mark as patrolled" link HTML (string) $rcid: Recent change ID (rc_id) for this change (int) -$token: Patrol token; $rcid is used in generating this variable 'DifferenceEngineMarkPatrolledRCID': Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the @@ -1914,8 +1925,8 @@ $code: language of the preferred translations in various places to allow extensions to define the effective language links for a page. $title: The page's Title. -&$links: Associative array mapping language codes to prefixed links of the - form "language:title". +&$links: Array with elements of the form "language:title" in the order + that they will be output. &$linkFlags: Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. @@ -2475,12 +2486,24 @@ cache or return false to not use it. &$parser: Parser object &$varCache: variable cache (array) -'ParserLimitReport': DEPRECATED! Use ParserLimitReportPrepare instead. +'ParserLimitReport': DEPRECATED! Use ParserLimitReportPrepare and +ParserLimitReportFormat instead. Called at the end of Parser:parse() when the parser will include comments about size of the text parsed. $parser: Parser object &$limitReport: text that will be included (without comment tags) +'ParserLimitReportFormat': Called for each row in the parser limit report that +needs formatting. If nothing handles this hook, the default is to use "$key" to +get the label, and "$key-value" or "$key-value-text"/"$key-value-html" to +format the value. +$key: Key for the limit report item (string) +&$value: Value of the limit report item +&$report: String onto which to append the data +$isHTML: If true, $report is an HTML table with two columns; if false, it's + text intended for display in a monospaced font. +$localize: If false, $report should be output in English. + 'ParserLimitReportPrepare': Called at the end of Parser:parse() when the parser will include comments about size of the text parsed. Hooks should use $output->setLimitReportData() to populate data. Functions for this hook should @@ -2509,10 +2532,6 @@ $showEditLinks: boolean describing whether this section has an edit link &$globals: Array with all the globals which should be set for parser tests. The arrays keys serve as the globals names, its values are the globals values. -'ParserTestParser': Called when creating a new instance of Parser in -tests/parser/parserTest.inc. -&$parser: Parser object created - 'ParserTestTables': Alter the list of tables to duplicate when parser tests are run. Use when page save hooks require the presence of custom tables to ensure that tests continue to run properly. @@ -3349,7 +3368,7 @@ PageArchive object has been created but before any further processing is done. &$archive: PageArchive object $title: Title object of the page that we're viewing -'UndeleteForm::undelete': Called un UndeleteForm::undelete, after checking that +'UndeleteForm::undelete': Called in UndeleteForm::undelete, after checking that the site is not in read-only mode, that the Title object is not null and after a PageArchive object has been constructed but before performing any further processing. @@ -3735,6 +3754,10 @@ used to alter the SQL query which gets the list of wanted pages. &$user: user that watched &$page: WikiPage object watched +'WatchedItemQueryServiceExtensions': Create a WatchedItemQueryServiceExtension. +&$extensions: Add WatchedItemQueryServiceExtension objects to this array +$watchedItemQueryService: Service object + 'WatchlistEditorBeforeFormRender': Before building the Special:EditWatchlist form, used to manipulate the list of pages or preload data based on that list. &$watchlistInfo: array of watchlisted pages in diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index 91422385a2..d444a2791f 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -90,7 +90,6 @@ class AjaxDispatcher { # Or we could throw an exception: # throw new MWException( __METHOD__ . ' called without any data (mode empty).' ); } - } /** @@ -156,6 +155,5 @@ class AjaxDispatcher { } } } - } } diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index 4fe46dd1af..0686578c17 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -161,7 +161,7 @@ class AjaxResponse { // For back-compat, it is supported that mResponseCode be a string like " 200 OK" // (with leading space and the status message after). Cast response code to an integer // to take advantage of PHP's conversion rules which will turn " 200 OK" into 200. - // http://php.net/string#language.types.string.conversion + // https://secure.php.net/manual/en/language.types.string.php#language.types.string.conversion $n = intval( trim( $this->mResponseCode ) ); HttpStatus::header( $n ); } diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index c858dd7164..b95f274406 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -422,26 +422,11 @@ class CategoryViewer extends ContextSource { return $r; } - /** - * Return pretty name which is display name if given and different from prefix text or - * the unprefixed page name. - * - * @return string HTML safe name. - */ - function getPrettyPageNameHtml() { - $displayTitle = $this->getOutput()->getPageTitle(); - if ( $displayTitle === $this->getTitle()->getPrefixedText() ) { - return htmlspecialchars( $this->getTitle()->getText() ); - } else { - return $displayTitle; - } - } - /** * @return string */ function getPagesSection() { - $name = $this->getPrettyPageNameHtml(); + $ti = wfEscapeWikiText( $this->title->getText() ); # Don't show articles section if there are none. $r = ''; @@ -457,7 +442,7 @@ class CategoryViewer extends ContextSource { if ( $rescnt > 0 ) { $r = "
\n"; - $r .= '

' . $this->msg( 'category_header' )->rawParams( $name )->parse() . "

\n"; + $r .= '

' . $this->msg( 'category_header', $ti )->parse() . "

\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'page' ); $r .= $this->formatList( $this->articles, $this->articles_start_char ); @@ -471,7 +456,6 @@ class CategoryViewer extends ContextSource { * @return string */ function getImageSection() { - $name = $this->getPrettyPageNameHtml(); $r = ''; $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery ); $dbcnt = $this->cat->getFileCount(); @@ -481,7 +465,10 @@ class CategoryViewer extends ContextSource { if ( $rescnt > 0 ) { $r .= "
\n"; $r .= '

' . - $this->msg( 'category-media-header' )->rawParams( $name )->parse() . + $this->msg( + 'category-media-header', + wfEscapeWikiText( $this->title->getText() ) + )->text() . "

\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'file' ); diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 0b0016c8b1..9d8ccf89c4 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -311,7 +311,7 @@ $wgAppleTouchIcon = false; * Value for the referrer policy meta tag. * One of 'never', 'default', 'origin', 'always'. Setting it to false just * prevents the meta tag from being output. - * See http://www.w3.org/TR/referrer-policy/ for details. + * See https://www.w3.org/TR/referrer-policy/ for details. * * @since 1.25 */ @@ -658,7 +658,7 @@ $wgLockManagers = []; /** * Show Exif data, on by default if available. - * Requires PHP's Exif extension: http://www.php.net/manual/en/ref.exif.php + * Requires PHP's Exif extension: https://secure.php.net/manual/en/ref.exif.php * * @note FOR WINDOWS USERS: * To enable Exif functions, add the following line to the "Windows @@ -1511,7 +1511,7 @@ $wgDjvuTxt = null; * For now we recommend you use djvudump instead. The djvuxml output is * probably more stable, so we'll switch back to it as soon as they fix * the efficiency problem. - * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583 + * https://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583 * * @par Example: * @code @@ -1973,7 +1973,7 @@ $wgDBerrorLog = false; * Defaults to the wiki timezone ($wgLocaltimezone). * * A list of usable timezones can found at: - * http://php.net/manual/en/timezones.php + * https://secure.php.net/manual/en/timezones.php * * @par Examples: * @code @@ -3110,7 +3110,7 @@ $wgForceUIMsgAsContentMsg = []; * timezone-nameinlowercase like timezone-utc. * * A list of usable timezones can found at: - * http://php.net/manual/en/timezones.php + * https://secure.php.net/manual/en/timezones.php * * @par Examples: * @code @@ -3178,7 +3178,7 @@ $wgHtml5 = true; * * If your wiki uses RDFa, set it to the correct value for RDFa+HTML5. * Correct current values are 'HTML+RDFa 1.0' or 'XHTML+RDFa 1.0'. - * See also http://www.w3.org/TR/rdfa-in-html/#document-conformance + * See also https://www.w3.org/TR/rdfa-in-html/#document-conformance * @since 1.16 */ $wgHtml5Version = null; @@ -4216,7 +4216,7 @@ $wgAllowImageTag = false; /** * Configuration for HTML postprocessing tool. Set this to a configuration * array to enable an external tool. Dave Raggett's "HTML Tidy" is typically - * used. See http://www.w3.org/People/Raggett/tidy/ + * used. See https://www.w3.org/People/Raggett/tidy/ * * If this is null and $wgUseTidy is true, the deprecated configuration * parameters will be used instead. @@ -4363,9 +4363,9 @@ $wgTranscludeCacheExpiry = 3600; * @since 1.28 */ $wgEnableMagicLinks = [ - 'ISBN' => true, - 'PMID' => true, - 'RFC' => true + 'ISBN' => false, + 'PMID' => false, + 'RFC' => false ]; /** @} */ # end of parser settings } @@ -6375,9 +6375,9 @@ $wgDisableInternalSearch = false; * To forward to Google you'd have something like: * @code * $wgSearchForwardUrl = - * 'http://www.google.com/search?q=$1' . - * '&domains=http://example.com' . - * '&sitesearch=http://example.com' . + * 'https://www.google.com/search?q=$1' . + * '&domains=https://example.com' . + * '&sitesearch=https://example.com' . * '&ie=utf-8&oe=utf-8'; * @endcode */ @@ -6710,9 +6710,9 @@ $wgFeedDiffCutoff = 32768; * Should be a format as key (either 'rss' or 'atom') and an URL to the feed * as value. * @par Example: - * Configure the 'atom' feed to http://example.com/somefeed.xml + * Configure the 'atom' feed to https://example.com/somefeed.xml * @code - * $wgSiteFeed['atom'] = "http://example.com/somefeed.xml"; + * $wgSiteFeed['atom'] = "https://example.com/somefeed.xml"; * @endcode */ $wgOverrideSiteFeed = []; @@ -7121,7 +7121,7 @@ $wgAutoloadAttemptLowercase = true; * 'Foo Barstein', * ], * 'version' => '1.9.0', - * 'url' => 'http://example.org/example-extension/', + * 'url' => 'https://example.org/example-extension/', * 'descriptionmsg' => 'exampleextension-desc', * 'license-name' => 'GPL-2.0+', * ]; @@ -7148,7 +7148,7 @@ $wgAutoloadAttemptLowercase = true; * - author: A string or an array of strings. Authors can be linked using * the regular wikitext link syntax. To have an internationalized version of * "and others" show, add an element "...". This element can also be linked, - * for instance "[http://example ...]". + * for instance "[https://example ...]". * * - descriptionmsg: A message key or an an array with message key and parameters: * `'descriptionmsg' => 'exampleextension-desc',` @@ -7366,7 +7366,7 @@ $wgCategoryPagingLimit = 200; * all languages in a mediocre way. However, it is better than "uppercase". * * To use the uca-default collation, you must have PHP's intl extension - * installed. See http://php.net/manual/en/intl.setup.php . The details of the + * installed. See https://secure.php.net/manual/en/intl.setup.php . The details of the * resulting collation will depend on the version of ICU installed on the * server. * @@ -8035,7 +8035,7 @@ $wgShellCgroup = false; $wgPhpCli = '/usr/bin/php'; /** - * Locale for LC_CTYPE, to work around http://bugs.php.net/bug.php?id=45132 + * Locale for LC_CTYPE, to work around https://bugs.php.net/bug.php?id=45132 * For Unix-like operating systems, set this to to a locale that has a UTF-8 * character set. Only the character set is relevant. */ diff --git a/includes/EditPage.php b/includes/EditPage.php index 4aa87d6c89..82ddee019c 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -1044,7 +1044,6 @@ class EditPage { // Allow extensions to modify form data Hooks::run( 'EditPage::importFormData', [ $this, $request ] ); - } /** @@ -1646,7 +1645,7 @@ class EditPage { // being set. This is used by ConfirmEdit to display a captcha // without any error message cruft. } else { - $this->hookError = $this->formatStatusErrors( $status ); + $this->hookError = $status->getWikiText(); } // Use the existing $status->value if the hook set it if ( !$status->value ) { @@ -1656,7 +1655,7 @@ class EditPage { } elseif ( !$status->isOK() ) { # ...or the hook could be expecting us to produce an error // FIXME this sucks, we should just use the Status object throughout - $this->hookError = $this->formatStatusErrors( $status ); + $this->hookError = $status->getWikiText(); $status->fatal( 'hookaborted' ); $status->value = self::AS_HOOK_ERROR_EXPECTED; return false; @@ -1665,26 +1664,6 @@ class EditPage { return true; } - /** - * Wrap status errors in an errorbox for increased visiblity - * - * @param Status $status - * @return string - */ - private function formatStatusErrors( Status $status ) { - $errmsg = $status->getHTML( - 'edit-error-short', - 'edit-error-long', - $this->context->getLanguage() - ); - return << -{$errmsg} -
-
-ERROR; - } - /** * Return the summary to be used for a new section. * @@ -2257,7 +2236,6 @@ ERROR; * @return bool */ private function mergeChangesIntoContent( &$editContent ) { - $db = wfGetDB( DB_MASTER ); // This is the revision the editor started from @@ -2620,10 +2598,21 @@ ERROR; $this->setHeaders(); - if ( $this->showHeader() === false ) { + $this->addTalkPageText(); + $this->addEditNotices(); + + if ( !$this->isConflict && + $this->section != '' && + !$this->isSectionEditSupported() ) { + // We use $this->section to much before this and getVal('wgSection') directly in other places + // at this point we can't reset $this->section to '' to fallback to non-section editing. + // Someone is welcome to try refactoring though + $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); return; } + $this->showHeader(); + $wgOut->addHTML( $this->editFormPageTop ); if ( $wgUser->getOption( 'previewontop' ) ) { @@ -2833,7 +2822,6 @@ ERROR; if ( !$wgUser->getOption( 'previewontop' ) ) { $this->displayPreviewArea( $previewOutput, false ); } - } /** @@ -2859,7 +2847,6 @@ ERROR; return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ], $templateListFormatter->format( $templates, $type ) ); - } /** @@ -2878,29 +2865,14 @@ ERROR; } } - /** - * @return bool - */ protected function showHeader() { - global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang; + global $wgOut, $wgUser; global $wgAllowUserCss, $wgAllowUserJs; - $this->addTalkPageText(); - - $this->addEditNotices(); - if ( $this->isConflict ) { - $wgOut->wrapWikiMsg( "
\n$1\n
", 'explainconflict' ); + $this->addExplainConflictHeader( $wgOut ); $this->editRevId = $this->page->getLatest(); } else { - if ( $this->section != '' && !$this->isSectionEditSupported() ) { - // We use $this->section to much before this and getVal('wgSection') directly in other places - // at this point we can't reset $this->section to '' to fallback to non-section editing. - // Someone is welcome to try refactoring though - $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); - return false; - } - if ( $this->section != '' && $this->section != 'new' ) { if ( !$this->summary && !$this->preview && !$this->diff ) { $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object @@ -3026,69 +2998,12 @@ ERROR; } } - if ( $this->mTitle->isProtected( 'edit' ) && - MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ] - ) { - # Is the title semi-protected? - if ( $this->mTitle->isSemiProtected() ) { - $noticeMsg = 'semiprotectedpagewarning'; - } else { - # Then it must be protected based on static groups (regular) - $noticeMsg = 'protectedpagewarning'; - } - LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '', - [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] ); - } - if ( $this->mTitle->isCascadeProtected() ) { - # Is this page under cascading protection from some source pages? - /** @var Title[] $cascadeSources */ - list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources(); - $notice = "
\n$1\n"; - $cascadeSourcesCount = count( $cascadeSources ); - if ( $cascadeSourcesCount > 0 ) { - # Explain, and list the titles responsible - foreach ( $cascadeSources as $page ) { - $notice .= '* [[:' . $page->getPrefixedText() . "]]\n"; - } - } - $notice .= '
'; - $wgOut->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] ); - } - if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) { - LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '', - [ 'lim' => 1, - 'showIfEmpty' => false, - 'msgKey' => [ 'titleprotectedwarning' ], - 'wrap' => "
\n$1
" ] ); - } + $this->addPageProtectionWarningHeaders(); - if ( $this->contentLength === false ) { - $this->contentLength = strlen( $this->textbox1 ); - } + $this->addLongPageWarningHeader(); - if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) { - $wgOut->wrapWikiMsg( "
\n$1\n
", - [ - 'longpageerror', - $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ), - $wgLang->formatNum( $wgMaxArticleSize ) - ] - ); - } else { - if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) { - $wgOut->wrapWikiMsg( "
\n$1\n
", - [ - 'longpage-hint', - $wgLang->formatSize( strlen( $this->textbox1 ) ), - strlen( $this->textbox1 ) - ] - ); - } - } # Add header copyright warning $this->showHeaderCopyrightWarning(); - - return true; } /** @@ -3303,44 +3218,9 @@ HTML global $wgOut, $wgUser; $wikitext = $this->safeUnicodeOutput( $text ); - if ( strval( $wikitext ) !== '' ) { - // Ensure there's a newline at the end, otherwise adding lines - // is awkward. - // But don't add a newline if the ext is empty, or Firefox in XHTML - // mode will show an extra newline. A bit annoying. - $wikitext .= "\n"; - } + $wikitext = $this->addNewLineAtEnd( $wikitext ); - $attribs = $customAttribs + [ - 'accesskey' => ',', - 'id' => $name, - 'cols' => $wgUser->getIntOption( 'cols' ), - 'rows' => $wgUser->getIntOption( 'rows' ), - // Avoid PHP notices when appending preferences - // (appending allows customAttribs['style'] to still work). - 'style' => '' - ]; - - // The following classes can be used here: - // * mw-editfont-default - // * mw-editfont-monospace - // * mw-editfont-sans-serif - // * mw-editfont-serif - $class = 'mw-editfont-' . $wgUser->getOption( 'editfont' ); - - if ( isset( $attribs['class'] ) ) { - if ( is_string( $attribs['class'] ) ) { - $attribs['class'] .= ' ' . $class; - } elseif ( is_array( $attribs['class'] ) ) { - $attribs['class'][] = $class; - } - } else { - $attribs['class'] = $class; - } - - $pageLang = $this->mTitle->getPageLanguage(); - $attribs['lang'] = $pageLang->getHtmlCode(); - $attribs['dir'] = $pageLang->getDir(); + $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $wgUser ); $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) ); } @@ -3527,6 +3407,7 @@ HTML * * @param Title $title * @param string $format Output format, valid values are any function of a Message object + * @param Language|string|null $langcode Language code or Language object. * @return string */ public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) { @@ -3574,22 +3455,16 @@ HTML ] ) . Html::openElement( 'tbody' ); - foreach ( $output->getLimitReportData()['limitreport'] as $key => $value ) { + foreach ( $output->getLimitReportData() as $key => $value ) { if ( Hooks::run( 'ParserLimitReportFormat', [ $key, &$value, &$limitReport, true, true ] ) ) { - $keyMsg = wfMessage( "limitreport-$key" ); - $valueMsg = wfMessage( - [ "limitreport-$key-value-html", "limitreport-$key-value" ] - ); + $keyMsg = wfMessage( $key ); + $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] ); if ( !$valueMsg->exists() ) { $valueMsg = new RawMessage( '$1' ); } if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) { - // If it's a value/limit array, convert it for $1/$2 - if ( is_array( $value ) && isset( $value['value'] ) ) { - $value = [ $value['value'], $value['limit'] ]; - } $limitReport .= Html::openElement( 'tr' ) . Html::rawElement( 'th', null, $keyMsg->parse() ) . Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) . @@ -3660,7 +3535,7 @@ HTML global $wgOut; if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$this, &$wgOut ] ) ) { - $stats = $wgOut->getContext()->getStats(); + $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); $stats->increment( 'edit.failures.conflict' ); // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics if ( @@ -3799,7 +3674,7 @@ HTML global $wgOut, $wgRawHtml, $wgLang; global $wgAllowUserCss, $wgAllowUserJs; - $stats = $wgOut->getContext()->getStats(); + $stats = MediaWikiServices::getInstance()->getStatsdDataFactory(); if ( $wgRawHtml && !$this->mTokenOk ) { // Could be an offsite preview attempt. This is very unsafe if @@ -4421,6 +4296,9 @@ HTML return strtr( $result, [ "�" => "&#x" ] ); } + /** + * @since 1.29 + */ protected function addEditNotices() { global $wgOut; @@ -4439,6 +4317,9 @@ HTML } } + /** + * @since 1.29 + */ protected function addTalkPageText() { global $wgOut; @@ -4446,4 +4327,145 @@ HTML $wgOut->addWikiMsg( 'talkpagetext' ); } } + + /** + * @since 1.29 + */ + protected function addLongPageWarningHeader() { + global $wgMaxArticleSize, $wgOut, $wgLang; + + if ( $this->contentLength === false ) { + $this->contentLength = strlen( $this->textbox1 ); + } + + if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) { + $wgOut->wrapWikiMsg( "
\n$1\n
", + [ + 'longpageerror', + $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ), + $wgLang->formatNum( $wgMaxArticleSize ) + ] + ); + } else { + if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) { + $wgOut->wrapWikiMsg( "
\n$1\n
", + [ + 'longpage-hint', + $wgLang->formatSize( strlen( $this->textbox1 ) ), + strlen( $this->textbox1 ) + ] + ); + } + } + } + + /** + * @since 1.29 + */ + protected function addPageProtectionWarningHeaders() { + global $wgOut; + + if ( $this->mTitle->isProtected( 'edit' ) && + MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ] + ) { + # Is the title semi-protected? + if ( $this->mTitle->isSemiProtected() ) { + $noticeMsg = 'semiprotectedpagewarning'; + } else { + # Then it must be protected based on static groups (regular) + $noticeMsg = 'protectedpagewarning'; + } + LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '', + [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] ); + } + if ( $this->mTitle->isCascadeProtected() ) { + # Is this page under cascading protection from some source pages? + /** @var Title[] $cascadeSources */ + list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources(); + $notice = "
\n$1\n"; + $cascadeSourcesCount = count( $cascadeSources ); + if ( $cascadeSourcesCount > 0 ) { + # Explain, and list the titles responsible + foreach ( $cascadeSources as $page ) { + $notice .= '* [[:' . $page->getPrefixedText() . "]]\n"; + } + } + $notice .= '
'; + $wgOut->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] ); + } + if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) { + LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '', + [ 'lim' => 1, + 'showIfEmpty' => false, + 'msgKey' => [ 'titleprotectedwarning' ], + 'wrap' => "
\n$1
" ] ); + } + } + + /** + * @param OutputPage $out + * @since 1.29 + */ + protected function addExplainConflictHeader( OutputPage $out ) { + $out->wrapWikiMsg( "
\n$1\n
", 'explainconflict' ); + } + + /** + * @param string $name + * @param mixed[] $customAttribs + * @param User $user + * @return mixed[] + * @since 1.29 + */ + protected function buildTextboxAttribs( $name, array $customAttribs, User $user ) { + $attribs = $customAttribs + [ + 'accesskey' => ',', + 'id' => $name, + 'cols' => $user->getIntOption( 'cols' ), + 'rows' => $user->getIntOption( 'rows' ), + // Avoid PHP notices when appending preferences + // (appending allows customAttribs['style'] to still work). + 'style' => '' + ]; + + // The following classes can be used here: + // * mw-editfont-default + // * mw-editfont-monospace + // * mw-editfont-sans-serif + // * mw-editfont-serif + $class = 'mw-editfont-' . $user->getOption( 'editfont' ); + + if ( isset( $attribs['class'] ) ) { + if ( is_string( $attribs['class'] ) ) { + $attribs['class'] .= ' ' . $class; + } elseif ( is_array( $attribs['class'] ) ) { + $attribs['class'][] = $class; + } + } else { + $attribs['class'] = $class; + } + + $pageLang = $this->mTitle->getPageLanguage(); + $attribs['lang'] = $pageLang->getHtmlCode(); + $attribs['dir'] = $pageLang->getDir(); + + return $attribs; + } + + /** + * @param string $wikitext + * @return string + * @since 1.29 + */ + protected function addNewLineAtEnd( $wikitext ) { + if ( strval( $wikitext ) !== '' ) { + // Ensure there's a newline at the end, otherwise adding lines + // is awkward. + // But don't add a newline if the text is empty, or Firefox in XHTML + // mode will show an extra newline. A bit annoying. + $wikitext .= "\n"; + return $wikitext; + } + return $wikitext; + } } diff --git a/includes/Feed.php b/includes/Feed.php index 8bfe1c7ef9..189fd9f2fb 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -236,7 +236,6 @@ abstract class ChannelFeed extends FeedItem { $wgOut->addVaryHeader( 'X-Forwarded-Proto' ); } $wgOut->sendCacheControl(); - } /** diff --git a/includes/FormOptions.php b/includes/FormOptions.php index 5e5e8d4011..725a512980 100644 --- a/includes/FormOptions.php +++ b/includes/FormOptions.php @@ -52,6 +52,9 @@ class FormOptions implements ArrayAccess { * This is useful for the namespace selector. */ const INTNULL = 3; + /** Array type, maps guessType() to WebRequest::getArray() + * @since 1.29 */ + const ARR = 5; /* @} */ /** @@ -120,6 +123,8 @@ class FormOptions implements ArrayAccess { return self::FLOAT; } elseif ( is_string( $data ) ) { return self::STRING; + } elseif ( is_array( $data ) ) { + return self::ARR; } else { throw new MWException( 'Unsupported datatype' ); } @@ -358,6 +363,9 @@ class FormOptions implements ArrayAccess { case self::INTNULL: $value = $r->getIntOrNull( $name ); break; + case self::ARR: + $value = $r->getArray( $name ); + break; default: throw new MWException( 'Unsupported datatype' ); } @@ -370,7 +378,7 @@ class FormOptions implements ArrayAccess { /** @name ArrayAccess functions * These functions implement the ArrayAccess PHP interface. - * @see http://php.net/manual/en/class.arrayaccess.php + * @see https://secure.php.net/manual/en/class.arrayaccess.php */ /* @{ */ /** diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index bae9c772e3..b3ccc5675a 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -40,7 +40,7 @@ use Wikimedia\ScopedCallback; */ // hash_equals function only exists in PHP >= 5.6.0 -// http://php.net/hash_equals +// https://secure.php.net/hash_equals if ( !function_exists( 'hash_equals' ) ) { /** * Check whether a user-provided string is equal to a fixed-length secret string @@ -1670,7 +1670,9 @@ function wfClientAcceptsGzip( $force = false ) { function wfEscapeWikiText( $text ) { global $wgEnableMagicLinks; static $repl = null, $repl2 = null; - if ( $repl === null ) { + if ( $repl === null || defined( 'MW_PARSER_TEST' ) || defined( 'MW_PHPUNIT_TEST' ) ) { + // Tests depend upon being able to change $wgEnableMagicLinks, so don't cache + // in those situations $repl = [ '"' => '"', '&' => '&', "'" => ''', '<' => '<', '=' => '=', '>' => '>', '[' => '[', ']' => ']', @@ -2136,7 +2138,7 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) { */ function wfRecursiveRemoveDir( $dir ) { wfDebug( __FUNCTION__ . "( $dir )\n" ); - // taken from http://de3.php.net/manual/en/function.rmdir.php#98622 + // taken from https://secure.php.net/manual/en/function.rmdir.php#98622 if ( is_dir( $dir ) ) { $objects = scandir( $dir ); foreach ( $objects as $object ) { @@ -2229,8 +2231,8 @@ function wfEscapeShellArg( /*...*/ ) { // Escaping for an MSVC-style command line parser and CMD.EXE // @codingStandardsIgnoreStart For long URLs // Refs: - // * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html - // * http://technet.microsoft.com/en-us/library/cc723564.aspx + // * https://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html + // * https://technet.microsoft.com/en-us/library/cc723564.aspx // * T15518 // * CR r63214 // Double the backslashes before any double quotes. Escape the double quotes. @@ -2330,7 +2332,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = [], if ( wfIsWindows() ) { /* Surrounding a set in quotes (method used by wfEscapeShellArg) makes the quotes themselves * appear in the environment variable, so we must use carat escaping as documented in - * http://technet.microsoft.com/en-us/library/cc723564.aspx + * https://technet.microsoft.com/en-us/library/cc723564.aspx * Note however that the quote isn't listed there, but is needed, and the parentheses * are listed there but doesn't appear to need it. */ @@ -2548,7 +2550,7 @@ function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = } /** - * Workaround for http://bugs.php.net/bug.php?id=45132 + * Workaround for https://bugs.php.net/bug.php?id=45132 * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale */ function wfInitShellLocale() { @@ -2800,7 +2802,7 @@ function wfUseMW( $req_ver ) { /** * Return the final portion of a pathname. * Reimplemented because PHP5's "basename()" is buggy with multibyte text. - * http://bugs.php.net/bug.php?id=33898 + * https://bugs.php.net/bug.php?id=33898 * * PHP's basename() only considers '\' a pathchar on Windows and Netware. * We'll consider it so always, as we don't want '\s' in our Unix paths either. diff --git a/includes/Html.php b/includes/Html.php index 48b30c7802..0b6b6556ac 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -3,7 +3,7 @@ * Collection of methods to generate HTML content * * Copyright © 2009 Aryeh Gregor - * http://www.mediawiki.org/ + * https://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -156,10 +156,10 @@ class Html { * @param string $contents The raw HTML contents of the element: *not* * escaped! * @param array $attrs Associative array of attributes, e.g., [ - * 'href' => 'http://www.mediawiki.org/' ]. See expandAttributes() for + * 'href' => 'https://www.mediawiki.org/' ]. See expandAttributes() for * further documentation. * @param string[] $modifiers classes to add to the button - * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers + * @see https://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers * @return string Raw HTML */ public static function linkButton( $contents, array $attrs, array $modifiers = [] ) { @@ -176,10 +176,10 @@ class Html { * @param string $contents The raw HTML contents of the element: *not* * escaped! * @param array $attrs Associative array of attributes, e.g., [ - * 'href' => 'http://www.mediawiki.org/' ]. See expandAttributes() for + * 'href' => 'https://www.mediawiki.org/' ]. See expandAttributes() for * further documentation. * @param string[] $modifiers classes to add to the button - * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers + * @see https://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers * @return string Raw HTML */ public static function submitButton( $contents, array $attrs, array $modifiers = [] ) { @@ -200,7 +200,7 @@ class Html { * * @param string $element The element's name, e.g., 'a' * @param array $attribs Associative array of attributes, e.g., [ - * 'href' => 'http://www.mediawiki.org/' ]. See expandAttributes() for + * 'href' => 'https://www.mediawiki.org/' ]. See expandAttributes() for * further documentation. * @param string $contents The raw HTML contents of the element: *not* * escaped! @@ -321,7 +321,7 @@ class Html { * * @param string $element Name of the element, e.g., 'a' * @param array $attribs Associative array of attributes, e.g., [ - * 'href' => 'http://www.mediawiki.org/' ]. See expandAttributes() for + * 'href' => 'https://www.mediawiki.org/' ]. See expandAttributes() for * further documentation. * @return array An array of attributes functionally identical to $attribs */ @@ -431,8 +431,8 @@ class Html { /** * Given an associative array of element attributes, generate a string * to stick after the element name in HTML output. Like [ 'href' => - * 'http://www.mediawiki.org/' ] becomes something like - * ' href="http://www.mediawiki.org"'. Again, this is like + * 'https://www.mediawiki.org/' ] becomes something like + * ' href="https://www.mediawiki.org"'. Again, this is like * Xml::expandAttributes(), but it implements some HTML-specific logic. * * Attributes that can contain space-separated lists ('class', 'accesskey' and 'rel') array @@ -458,7 +458,7 @@ class Html { * @endcode * * @param array $attribs Associative array of attributes, e.g., [ - * 'href' => 'http://www.mediawiki.org/' ]. Values will be HTML-escaped. + * 'href' => 'https://www.mediawiki.org/' ]. Values will be HTML-escaped. * A value of false means to omit the attribute. For boolean attributes, * you can omit the key, e.g., [ 'checked' ] instead of * [ 'checked' => 'checked' ] or such. @@ -501,8 +501,8 @@ class Html { continue; } - // http://www.w3.org/TR/html401/index/attributes.html ("space-separated") - // http://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated") + // https://www.w3.org/TR/html401/index/attributes.html ("space-separated") + // https://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated") $spaceSeparatedListAttributes = [ 'class', // html4, html5 'accesskey', // as of html5, multiple space-separated values allowed @@ -956,7 +956,7 @@ class Html { * @return bool */ public static function isXmlMimeType( $mimetype ) { - # http://www.whatwg.org/html/infrastructure.html#xml-mime-type + # https://html.spec.whatwg.org/multipage/infrastructure.html#xml-mime-type # * text/xml # * application/xml # * Any MIME type with a subtype ending in +xml (this implicitly includes application/xhtml+xml) @@ -1005,7 +1005,7 @@ class Html { * * @note srcset width and height values are not supported. * - * @see http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset + * @see https://html.spec.whatwg.org/#attr-img-srcset * * @par Example: * @code diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index f21128e59e..eaa1c99539 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -313,8 +313,6 @@ class MediaWiki { * - Normalise empty title: * /wiki/ -> /wiki/Main * /w/index.php?title= -> /wiki/Main - * - Normalise non-standard title urls: - * /w/index.php?title=Foo_Bar -> /wiki/Foo_Bar * - Don't redirect anything with query parameters other than 'title' or 'action=view'. * * @param Title $title @@ -327,6 +325,8 @@ class MediaWiki { if ( $request->getVal( 'action', 'view' ) != 'view' || $request->wasPosted() + || ( $request->getVal( 'title' ) !== null + && $title->getPrefixedDBkey() == $request->getVal( 'title' ) ) || count( $request->getValueNames( [ 'action', 'title' ] ) ) || !Hooks::run( 'TestCanonicalRedirect', [ $request, $title, $output ] ) ) { @@ -341,19 +341,7 @@ class MediaWiki { } // Redirect to canonical url, make it a 301 to allow caching $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); - - if ( $targetUrl != $request->getFullRequestURL() ) { - $output->setCdnMaxage( 1200 ); - $output->redirect( $targetUrl, '301' ); - return true; - } - - // If there is no title, or the title is in a non-standard encoding, we demand - // a redirect. If cgi somehow changed the 'title' query to be non-standard while - // the url is standard, the server is misconfigured. - if ( $request->getVal( 'title' ) === null - || $title->getPrefixedDBkey() != $request->getVal( 'title' ) - ) { + if ( $targetUrl == $request->getFullRequestURL() ) { $message = "Redirect loop detected!\n\n" . "This means the wiki got confused about what page was " . "requested; this sometimes happens when moving a wiki " . @@ -375,7 +363,9 @@ class MediaWiki { } throw new HttpError( 500, $message ); } - return false; + $output->setSquidMaxage( 1200 ); + $output->redirect( $targetUrl, '301' ); + return true; } /** @@ -676,14 +666,14 @@ class MediaWiki { /** * @param string $url * @param IContextSource $context - * @return string|bool Either "local" or "remote" if in the farm, false otherwise + * @return string Either "local", "remote" if in the farm, "external" otherwise */ private static function getUrlDomainDistance( $url, IContextSource $context ) { static $relevantKeys = [ 'host' => true, 'port' => true ]; $infoCandidate = wfParseUrl( $url ); if ( $infoCandidate === false ) { - return false; + return 'external'; } $infoCandidate = array_intersect_key( $infoCandidate, $relevantKeys ); @@ -705,7 +695,7 @@ class MediaWiki { } } - return false; + return 'external'; } /** diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index ba8b71b0f1..7c9363ca19 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -22,6 +22,7 @@ use MediaWiki\Services\NoSuchServiceException; use MWException; use MimeAnalyzer; use ObjectCache; +use Parser; use ProxyLookup; use SearchEngine; use SearchEngineConfig; @@ -191,7 +192,6 @@ class MediaWikiServices extends ServiceContainer { } else { $oldInstance->destroy(); } - } /** @@ -297,7 +297,7 @@ class MediaWikiServices extends ServiceContainer { self::resetGlobalInstance(); // Child, reseed because there is no bug in PHP: - // http://bugs.php.net/bug.php?id=42465 + // https://bugs.php.net/bug.php?id=42465 mt_srand( getmypid() ); } @@ -565,6 +565,14 @@ class MediaWikiServices extends ServiceContainer { return $this->getService( 'ProxyLookup' ); } + /** + * @since 1.29 + * @return Parser + */ + public function getParser() { + return $this->getService( 'Parser' ); + } + /** * @since 1.28 * @return GenderCache diff --git a/includes/MergeHistory.php b/includes/MergeHistory.php index f797fe355c..e57f88099a 100644 --- a/includes/MergeHistory.php +++ b/includes/MergeHistory.php @@ -90,7 +90,8 @@ class MergeHistory { 'revision', 'MAX(rev_timestamp)', [ - 'rev_timestamp <= ' . $this->dbw->timestamp( $mwTimestamp ), + 'rev_timestamp <= ' . + $this->dbw->addQuotes( $this->dbw->timestamp( $mwTimestamp ) ), 'rev_page' => $this->source->getArticleID() ], __METHOD__ @@ -118,7 +119,8 @@ class MergeHistory { $this->timestampLimit = $lasttimestamp; } - $this->timeWhere = "rev_timestamp <= {$this->dbw->timestamp( $timeInsert )}"; + $this->timeWhere = "rev_timestamp <= " . + $this->dbw->addQuotes( $this->dbw->timestamp( $timeInsert ) ); } catch ( TimestampException $ex ) { // The timestamp we got is screwed up and merge cannot continue // This should be detected by $this->isValidMerge() diff --git a/includes/Message.php b/includes/Message.php index c1a12aa912..7d86d07517 100644 --- a/includes/Message.php +++ b/includes/Message.php @@ -157,6 +157,16 @@ * @since 1.17 */ class Message implements MessageSpecifier, Serializable { + /** Use message text as-is */ + const FORMAT_PLAIN = 'plain'; + /** Use normal wikitext -> HTML parsing (the result will be wrapped in a block-level HTML tag) */ + const FORMAT_BLOCK_PARSE = 'block-parse'; + /** Use normal wikitext -> HTML parsing but strip the block-level wrapper */ + const FORMAT_PARSE = 'parse'; + /** Transform {{..}} constructs but don't transform to HTML */ + const FORMAT_TEXT = 'text'; + /** Transform {{..}} constructs, HTML-escape the result */ + const FORMAT_ESCAPED = 'escaped'; /** * In which language to get this message. True, which is the default, @@ -190,15 +200,8 @@ class Message implements MessageSpecifier, Serializable { protected $parameters = []; /** - * Format for the message. - * Supported formats are: - * * text (transform) - * * escaped (transform+htmlspecialchars) - * * block-parse - * * parse (default) - * * plain - * * @var string + * @deprecated */ protected $format = 'parse'; @@ -347,8 +350,10 @@ class Message implements MessageSpecifier, Serializable { * @since 1.21 * * @return string + * @deprecated since 1.29 formatting is not stateful */ public function getFormat() { + wfDeprecated( __METHOD__, '1.29' ); return $this->format; } @@ -796,9 +801,18 @@ class Message implements MessageSpecifier, Serializable { * * @since 1.17 * + * @param string|null $format One of the FORMAT_* constants. Null means use whatever was used + * the last time (this is for B/C and should be avoided). + * * @return string HTML */ - public function toString() { + public function toString( $format = null ) { + if ( $format === null ) { + $ex = new LogicException( __METHOD__ . ' using implicit format: ' . $this->format ); + \MediaWiki\Logger\LoggerFactory::getInstance( 'message-format' )->warning( + $ex->getMessage(), [ 'exception' => $ex, 'format' => $this->format, 'key' => $this->key ] ); + $format = $this->format; + } $string = $this->fetchMessage(); if ( $string === false ) { @@ -808,6 +822,7 @@ class Message implements MessageSpecifier, Serializable { // message key is user-controlled. // '⧼' is used instead of '<' to side-step any // double-escaping issues. + // (Keep synchronised with mw.Message#toString in JS.) return '⧼' . htmlspecialchars( $this->key ) . '⧽'; } @@ -821,23 +836,23 @@ class Message implements MessageSpecifier, Serializable { } # Replace parameters before text parsing - $string = $this->replaceParameters( $string, 'before' ); + $string = $this->replaceParameters( $string, 'before', $format ); # Maybe transform using the full parser - if ( $this->format === 'parse' ) { + if ( $format === self::FORMAT_PARSE ) { $string = $this->parseText( $string ); $string = Parser::stripOuterParagraph( $string ); - } elseif ( $this->format === 'block-parse' ) { + } elseif ( $format === self::FORMAT_BLOCK_PARSE ) { $string = $this->parseText( $string ); - } elseif ( $this->format === 'text' ) { + } elseif ( $format === self::FORMAT_TEXT ) { $string = $this->transformText( $string ); - } elseif ( $this->format === 'escaped' ) { + } elseif ( $format === self::FORMAT_ESCAPED ) { $string = $this->transformText( $string ); $string = htmlspecialchars( $string, ENT_QUOTES, 'UTF-8', false ); } # Raw parameter replacement - $string = $this->replaceParameters( $string, 'after' ); + $string = $this->replaceParameters( $string, 'after', $format ); return $string; } @@ -852,17 +867,11 @@ class Message implements MessageSpecifier, Serializable { * @return string */ public function __toString() { - if ( $this->format !== 'parse' ) { - $ex = new LogicException( __METHOD__ . ' using implicit format: ' . $this->format ); - \MediaWiki\Logger\LoggerFactory::getInstance( 'message-format' )->warning( - $ex->getMessage(), [ 'exception' => $ex, 'format' => $this->format, 'key' => $this->key ] ); - } - // PHP doesn't allow __toString to throw exceptions and will // trigger a fatal error if it does. So, catch any exceptions. try { - return $this->toString(); + return $this->toString( self::FORMAT_PARSE ); } catch ( Exception $ex ) { try { trigger_error( "Exception caught in " . __METHOD__ . " (message " . $this->key . "): " @@ -871,10 +880,7 @@ class Message implements MessageSpecifier, Serializable { // Doh! Cause a fatal error after all? } - if ( $this->format === 'plain' || $this->format === 'text' ) { - return '<' . $this->key . '>'; - } - return '<' . htmlspecialchars( $this->key ) . '>'; + return '⧼' . htmlspecialchars( $this->key ) . '⧽'; } } @@ -886,8 +892,8 @@ class Message implements MessageSpecifier, Serializable { * @return string Parsed HTML. */ public function parse() { - $this->format = 'parse'; - return $this->toString(); + $this->format = self::FORMAT_PARSE; + return $this->toString( self::FORMAT_PARSE ); } /** @@ -898,8 +904,8 @@ class Message implements MessageSpecifier, Serializable { * @return string Unescaped message text. */ public function text() { - $this->format = 'text'; - return $this->toString(); + $this->format = self::FORMAT_TEXT; + return $this->toString( self::FORMAT_TEXT ); } /** @@ -910,8 +916,8 @@ class Message implements MessageSpecifier, Serializable { * @return string Unescaped untransformed message text. */ public function plain() { - $this->format = 'plain'; - return $this->toString(); + $this->format = self::FORMAT_PLAIN; + return $this->toString( self::FORMAT_PLAIN ); } /** @@ -922,8 +928,8 @@ class Message implements MessageSpecifier, Serializable { * @return string HTML */ public function parseAsBlock() { - $this->format = 'block-parse'; - return $this->toString(); + $this->format = self::FORMAT_BLOCK_PARSE; + return $this->toString( self::FORMAT_BLOCK_PARSE ); } /** @@ -935,8 +941,8 @@ class Message implements MessageSpecifier, Serializable { * @return string Escaped message text. */ public function escaped() { - $this->format = 'escaped'; - return $this->toString(); + $this->format = self::FORMAT_ESCAPED; + return $this->toString( self::FORMAT_ESCAPED ); } /** @@ -1070,13 +1076,14 @@ class Message implements MessageSpecifier, Serializable { * * @param string $message The message text. * @param string $type Either "before" or "after". + * @param string $format One of the FORMAT_* constants. * * @return string */ - protected function replaceParameters( $message, $type = 'before' ) { + protected function replaceParameters( $message, $type = 'before', $format ) { $replacementKeys = []; foreach ( $this->parameters as $n => $param ) { - list( $paramType, $value ) = $this->extractParam( $param ); + list( $paramType, $value ) = $this->extractParam( $param, $format ); if ( $type === $paramType ) { $replacementKeys['$' . ( $n + 1 )] = $value; } @@ -1091,10 +1098,11 @@ class Message implements MessageSpecifier, Serializable { * @since 1.18 * * @param mixed $param Parameter as defined in this class. + * @param string $format One of the FORMAT_* constants. * * @return array Array with the parameter type (either "before" or "after") and the value. */ - protected function extractParam( $param ) { + protected function extractParam( $param, $format ) { if ( is_array( $param ) ) { if ( isset( $param['raw'] ) ) { return [ 'after', $param['raw'] ]; @@ -1113,7 +1121,7 @@ class Message implements MessageSpecifier, Serializable { } elseif ( isset( $param['bitrate'] ) ) { return [ 'before', $this->getLanguage()->formatBitrate( $param['bitrate'] ) ]; } elseif ( isset( $param['plaintext'] ) ) { - return [ 'after', $this->formatPlaintext( $param['plaintext'] ) ]; + return [ 'after', $this->formatPlaintext( $param['plaintext'], $format ) ]; } else { $warning = 'Invalid parameter for message "' . $this->getKey() . '": ' . htmlspecialchars( serialize( $param ) ); @@ -1127,7 +1135,7 @@ class Message implements MessageSpecifier, Serializable { // Message objects should not be before parameters because // then they'll get double escaped. If the message needs to be // escaped, it'll happen right here when we call toString(). - return [ 'after', $param->toString() ]; + return [ 'after', $param->toString( $format ) ]; } else { return [ 'before', $param ]; } @@ -1207,18 +1215,19 @@ class Message implements MessageSpecifier, Serializable { * @since 1.25 * * @param string $plaintext String to ensure plaintext output of + * @param string $format One of the FORMAT_* constants. * - * @return string Input plaintext encoded for output to $this->format + * @return string Input plaintext encoded for output to $format */ - protected function formatPlaintext( $plaintext ) { - switch ( $this->format ) { - case 'text': - case 'plain': + protected function formatPlaintext( $plaintext, $format ) { + switch ( $format ) { + case self::FORMAT_TEXT: + case self::FORMAT_PLAIN: return $plaintext; - case 'parse': - case 'block-parse': - case 'escaped': + case self::FORMAT_PARSE: + case self::FORMAT_BLOCK_PARSE: + case self::FORMAT_ESCAPED: default: return htmlspecialchars( $plaintext, ENT_QUOTES ); diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 863a426524..50629ba154 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -295,9 +295,6 @@ class OutputPage extends ContextSource { */ private $copyrightUrl; - /** @var array Profiling data */ - private $limitReportData = []; - /** * Constructor for OutputPage. This should not be called directly. * Instead a new RequestContext should be created and it will implicitly create @@ -1214,8 +1211,8 @@ class OutputPage extends ContextSource { /** * Add new language links * - * @param array $newLinkArray Associative array mapping language code to the page - * name + * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles + * (e.g. 'fr:Test page') */ public function addLanguageLinks( array $newLinkArray ) { $this->mLanguageLinks += $newLinkArray; @@ -1224,8 +1221,8 @@ class OutputPage extends ContextSource { /** * Reset the language links and add new language links * - * @param array $newLinkArray Associative array mapping language code to the page - * name + * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles + * (e.g. 'fr:Test page') */ public function setLanguageLinks( array $newLinkArray ) { $this->mLanguageLinks = $newLinkArray; @@ -1234,7 +1231,7 @@ class OutputPage extends ContextSource { /** * Get the list of language links * - * @return array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') + * @return string[] Array of interwiki-prefixed (non DB key) titles (e.g. 'fr:Test page') */ public function getLanguageLinks() { return $this->mLanguageLinks; @@ -1713,7 +1710,6 @@ class OutputPage extends ContextSource { $popts->setTidy( $oldTidy ); $this->addParserOutput( $parserOutput ); - } /** @@ -1776,16 +1772,11 @@ class OutputPage extends ContextSource { } } - // Enable OOUI if requested via ParserOutput + // enable OOUI if requested via ParserOutput if ( $parserOutput->getEnableOOUI() ) { $this->enableOOUI(); } - // Include profiling data - if ( !$this->limitReportData ) { - $this->setLimitReportData( $parserOutput->getLimitReportData() ); - } - // Link flags are ignored for now, but may in the future be // used to mark individual language links. $linkFlags = []; @@ -2198,22 +2189,26 @@ class OutputPage extends ContextSource { # We'll purge the proxy cache explicitly, but require end user agents # to revalidate against the proxy on each visit. # Surrogate-Control controls our CDN, Cache-Control downstream caches - wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **", 'private' ); + wfDebug( __METHOD__ . + ": proxy caching with ESI; {$this->mLastModified} **", 'private' ); # start with a shorter timeout for initial testing # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); - $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' ) - . '+' . $this->mCdnMaxage . ', content="ESI/1.0"' ); + $response->header( + "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" . + "+{$this->mCdnMaxage}, content=\"ESI/1.0\"" + ); $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); } else { # We'll purge the proxy cache for anons explicitly, but require end user agents # to revalidate against the proxy on each visit. # IMPORTANT! The CDN needs to replace the Cache-Control header with # Cache-Control: s-maxage=0, must-revalidate, max-age=0 - wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **", 'private' ); + wfDebug( __METHOD__ . + ": local proxy caching; {$this->mLastModified} **", 'private' ); # start with a shorter timeout for initial testing # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); - $response->header( 'Cache-Control: s-maxage=' . $this->mCdnMaxage - . ', must-revalidate, max-age=0' ); + $response->header( "Cache-Control: " . + "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" ); } } else { # We do want clients to cache if they can, but they *must* check for updates @@ -2807,8 +2802,8 @@ class OutputPage extends ContextSource { // The spec recommends defining XHTML5's charset using the XML declaration // instead of meta. // Our XML declaration is output by Html::htmlHeader. - // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type - // http://www.whatwg.org/html/semantics.html#charset + // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type + // https://html.spec.whatwg.org/multipage/semantics.html#charset $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] ); } @@ -2963,15 +2958,6 @@ class OutputPage extends ContextSource { } } - if ( $this->limitReportData ) { - $chunks[] = ResourceLoader::makeInlineScript( - ResourceLoader::makeConfigSetScript( - [ 'wgPageParseReport' => $this->limitReportData ], - true - ) - ); - } - return self::combineWrappedStrings( $chunks ); } @@ -3692,7 +3678,7 @@ class OutputPage extends ContextSource { public static function transformCssMedia( $media ) { global $wgRequest; - // http://www.w3.org/TR/css3-mediaqueries/#syntax + // https://www.w3.org/TR/css3-mediaqueries/#syntax $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i'; // Switch in on-screen display for media testing @@ -3873,12 +3859,4 @@ class OutputPage extends ContextSource { 'mediawiki.widgets.styles', ] ); } - - /** - * @param array $data Data from ParserOutput::getLimitReportData() - * @since 1.28 - */ - public function setLimitReportData( array $data ) { - $this->limitReportData = $data; - } } diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php index 656ba43d4d..e6e96c7ede 100644 --- a/includes/PHPVersionCheck.php +++ b/includes/PHPVersionCheck.php @@ -1,4 +1,7 @@ 'mbstring', 'utf8_encode' => 'xml', 'ctype_digit' => 'ctype', 'json_decode' => 'json', 'iconv' => 'iconv', ); - // List of extensions we're missing - $missingExtensions = array(); - // @codingStandardsIgnoreEnd - foreach ( $extensions as $function => $extension ) { - if ( !function_exists( $function ) ) { - $missingExtensions[] = $extension; + /** + * @var string Which entry point we are protecting. One of: + * - index.php + * - load.php + * - api.php + * - mw-config/index.php + * - cli + */ + var $entryPoint = null; + + /** + * @param string $entryPoint Which entry point we are protecting. One of: + * - index.php + * - load.php + * - api.php + * - mw-config/index.php + * - cli + * @return $this + */ + function setEntryPoint( $entryPoint ) { + $this->entryPoint = $entryPoint; + } + + /** + * Returns the version of the installed php implementation. + * + * @return string + */ + function getPHPImplVersion() { + return PHP_VERSION; + } + + /** + * Displays an error, if the installed php version does not meet the minimum requirement. + * + * @return $this + */ + function checkRequiredPHPVersion() { + if ( !function_exists( 'version_compare' ) + || version_compare( $this->getPHPImplVersion(), $this->minimumVersionPHP ) < 0 + ) { + $shortText = "MediaWiki $this->mwVersion requires at least PHP version" + . " $this->minimumVersionPHP, you are using PHP {$this->getPHPImplVersion()}."; + + $longText = "Error: You might be using on older PHP version. \n" + . "MediaWiki $this->mwVersion needs PHP $this->minimumVersionPHP or higher.\n\n" + . "Check if you have a newer php executable with a different name, " + . "such as php5.\n\n"; + + $longHtml = <<upgrading your copy of PHP. + PHP versions less than 5.5.0 are no longer supported by the PHP Group and will not receive + security or bugfix updates. +

+

+ If for some reason you are unable to upgrade your PHP version, you will need to + download an older version + of MediaWiki from our website. See our + compatibility page + for details of which versions are compatible with prior versions of PHP. +HTML; + $this->triggerError( 'Supported PHP versions', $shortText, $longText, $longHtml ); } } - if ( $missingExtensions ) { - wfMissingExtensions( $entryPoint, $mwVersion, $missingExtensions ); + /** + * Displays an error, if the vendor/autoload.php file could not be found. + * + * @return $this + */ + function checkVendorExistence() { + if ( !file_exists( dirname( __FILE__ ) . '/../vendor/autoload.php' ) ) { + $shortText = "Installing some external dependencies (e.g. via composer) is required."; + + $longText = "Error: You are missing some external dependencies. \n" + . "MediaWiki now also has some external dependencies that need to be installed\n" + . "via composer or from a separate git repo. Please see\n" + . "https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries\n" + . "for help on installing the required components."; + + $longHtml = <<mediawiki.org + for help on installing the required components. +HTML; + + $this->triggerError( 'External dependencies', $shortText, $longText, $longHtml ); + } } -} -/** - * Display something vaguely comprehensible in the event of a totally unrecoverable error. - * Does not assume access to *anything*; no globals, no autoloader, no database, no localisation. - * Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php - * no longer need to be). - * - * Calling this function kills execution immediately. - * - * @param string $type Which entry point we are protecting. One of: - * - index.php - * - load.php - * - api.php - * - mw-config/index.php - * - cli - * @param string $mwVersion The number of the MediaWiki version used - * @param string $title HTML code to be put within an

tag - * @param string $shortText - * @param string $longText - * @param string $longHtml - */ -function wfGenericError( $type, $mwVersion, $title, $shortText, $longText, $longHtml ) { - $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; + /** + * Displays an error, if a PHP extension does not exist. + * + * @return $this + */ + function checkExtensionExistence() { + $missingExtensions = array(); + foreach ( $this->functionsExtensionsMapping as $function => $extension ) { + if ( !function_exists( $function ) ) { + $missingExtensions[] = $extension; + } + } + + if ( $missingExtensions ) { + $shortText = "Installing some PHP extensions is required."; + + $missingExtText = ''; + $missingExtHtml = ''; + $baseUrl = 'https://secure.php.net'; + foreach ( $missingExtensions as $ext ) { + $missingExtText .= " * $ext <$baseUrl/$ext>\n"; + $missingExtHtml .= "
  • $ext " + . "(more information)
  • "; + } + + $cliText = "Error: Missing one or more required components of PHP.\n" + . "You are missing a required extension to PHP that MediaWiki needs.\n" + . "Please install:\n" . $missingExtText; + + $longHtml = << + $missingExtHtml + +HTML; + + $this->triggerError( 'Required components', $shortText, $cliText, $longHtml ); + } + } + + /** + * Output headers that prevents error pages to be cached. + */ + function outputHTMLHeader() { + $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; - if ( $type == 'cli' ) { - $finalOutput = $longText; - } else { header( "$protocol 500 MediaWiki configuration Error" ); // Don't cache error pages! They cause no end of trouble... header( 'Cache-control: none' ); header( 'Pragma: no-cache' ); + } - if ( $type == 'index.php' || $type == 'mw-config/index.php' ) { - $pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] ); - if ( $type == 'mw-config/index.php' ) { - $dirname = dirname( $pathinfo['dirname'] ); - } else { - $dirname = $pathinfo['dirname']; - } - $encLogo = htmlspecialchars( - str_replace( '//', '/', $dirname . '/' ) . - 'resources/assets/mediawiki.png' - ); - $shortHtml = htmlspecialchars( $shortText ); + /** + * Returns an error page, which is suitable for output to the end user via a web browser. + * + * @param $title + * @param $longHtml + * @param $shortText + * @return string + */ + function getIndexErrorOutput( $title, $longHtml, $shortText ) { + $pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] ); + if ( $this->entryPoint == 'mw-config/index.php' ) { + $dirname = dirname( $pathinfo['dirname'] ); + } else { + $dirname = $pathinfo['dirname']; + } + $encLogo = + htmlspecialchars( str_replace( '//', '/', $dirname . '/' ) . + 'resources/assets/mediawiki.png' ); + $shortHtml = htmlspecialchars( $shortText ); - header( 'Content-type: text/html; charset=UTF-8' ); + header( 'Content-type: text/html; charset=UTF-8' ); - $finalOutput = << - MediaWiki {$mwVersion} + MediaWiki {$this->mwVersion} - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png index 183aaeb7aa..af290b9654 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg index fedf7877a2..e1f33d8f84 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr-progressive.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.png index 82fbd14db4..e8b2911d8f 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.svg index 6d95fc6693..fffbcdd3de 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-ltr.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.png index 628de3db59..f40c30327d 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.svg index aeb562c74e..8ed760bd6c 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-invert.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png index 47af038789..a5095a1ab3 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg index 6c7b5fc591..34ec7c004b 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl-progressive.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.png index 64d1cf1d38..2f5e05dfad 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.svg index 807cdd9143..7b903c4b3d 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/bigger-rtl.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.png index 84b1bcd153..1b597da510 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.svg index 0eb2bfa0e4..b29f71839c 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-invert.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png index ba4fcefd76..2878f2358f 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg index b3c6452f85..9b7962ef1c 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr-progressive.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.png index f5e89ba8e9..a9b29d887d 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.svg index 82d16af934..943125224f 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-ltr.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.png index c9cdd7741c..f94184a449 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.svg index a87f7ba50b..171b31dac3 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-invert.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png index 03a4bccd56..99abfdbe5b 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg index 64d103c539..23c0b09a98 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl-progressive.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.png b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.png index e07c7b0d10..006bd2bd1c 100644 Binary files a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.png and b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.png differ diff --git a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.svg b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.svg index 7466f48b06..d2d28587a4 100644 --- a/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.svg +++ b/resources/lib/oojs-ui/themes/mediawiki/images/icons/smaller-rtl.svg @@ -1,7 +1,6 @@ - - - - + + + diff --git a/resources/src/jquery/jquery.accessKeyLabel.js b/resources/src/jquery/jquery.accessKeyLabel.js index e52d6a7c86..a6106e4562 100644 --- a/resources/src/jquery/jquery.accessKeyLabel.js +++ b/resources/src/jquery/jquery.accessKeyLabel.js @@ -97,7 +97,7 @@ function getAccessKeyLabel( element ) { return ''; } // use accessKeyLabel if possible - // http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#dom-accesskeylabel + // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel if ( !useTestPrefix && element.accessKeyLabel ) { return element.accessKeyLabel; } diff --git a/resources/src/jquery/jquery.byteLimit.js b/resources/src/jquery/jquery.byteLimit.js index dd71a2bc4d..b1692bbc44 100644 --- a/resources/src/jquery/jquery.byteLimit.js +++ b/resources/src/jquery/jquery.byteLimit.js @@ -176,7 +176,7 @@ // maxLength is a strange property. Removing or setting the property to // undefined directly doesn't work. Instead, it can only be unset internally // by the browser when removing the associated attribute (Firefox/Chrome). - // http://code.google.com/p/chromium/issues/detail?id=136004 + // https://bugs.chromium.org/p/chromium/issues/detail?id=136004 $el.removeAttr( 'maxlength' ); } else { @@ -203,7 +203,7 @@ // changed while text is being entered and keyup/change will not be fired yet // (such as holding down a single key, fires keydown, and after each keydown, // we can trim the previous one). - // See http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for + // See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for // the order and characteristics of the key events. $el.on( eventKeys, function () { var res = $.trimByteLength( diff --git a/resources/src/mediawiki.action/mediawiki.action.edit.preview.js b/resources/src/mediawiki.action/mediawiki.action.edit.preview.js index 3b19b35817..f26c336da1 100644 --- a/resources/src/mediawiki.action/mediawiki.action.edit.preview.js +++ b/resources/src/mediawiki.action/mediawiki.action.edit.preview.js @@ -245,7 +245,11 @@ $( '' ).addClass( 'comment' ).html( // There is no equivalent to rawParams mw.message( 'parentheses' ).escaped() - .replace( '$1', parse.parsedsummary ) + // .replace() use $ as start of a pattern. + // $$ is the pattern for '$'. + // The inner .replace() duplicates any $ and + // the outer .replace() simplifies the $$. + .replace( '$1', parse.parsedsummary.replace( /\$/g, '$$$$' ) ) ) ); } diff --git a/resources/src/mediawiki.legacy/oldshared.css b/resources/src/mediawiki.legacy/oldshared.css index 7ccf59e548..4daf77f262 100644 --- a/resources/src/mediawiki.legacy/oldshared.css +++ b/resources/src/mediawiki.legacy/oldshared.css @@ -5,7 +5,7 @@ */ /* For clarity, explicitly state some recommendations from - * http://www.w3.org/TR/CSS21/sample.html to make sure the editsection links scale right + * https://www.w3.org/TR/CSS21/sample.html to make sure the editsection links scale right */ h1 { diff --git a/resources/src/mediawiki.less/mediawiki.ui/variables.less b/resources/src/mediawiki.less/mediawiki.ui/variables.less index 28ad10ae56..676ecca8a8 100644 --- a/resources/src/mediawiki.less/mediawiki.ui/variables.less +++ b/resources/src/mediawiki.less/mediawiki.ui/variables.less @@ -50,7 +50,7 @@ @colorWarningText: #705000; // UI colors -@colorFieldBorder: #9aa0a7; +@colorFieldBorder: #a2a9b1; @colorShadow: @colorGray14; @colorPlaceholder: @colorGray10; @colorNeutral: @colorGray7; diff --git a/resources/src/mediawiki.skinning/content.css b/resources/src/mediawiki.skinning/content.css index d9cdf5a67f..d4c93fd266 100644 --- a/resources/src/mediawiki.skinning/content.css +++ b/resources/src/mediawiki.skinning/content.css @@ -89,7 +89,7 @@ table.toc td { display: table-cell; /* Text decorations are not propagated to the contents of inline blocks and inline tables, - according to , and 'display: table-cell' + according to , and 'display: table-cell' generates an inline table when used without any parent table-rows and tables. */ text-decoration: inherit; @@ -99,8 +99,8 @@ table.toc td { .tocnumber { padding-left: 0; padding-right: 0.5em; + color: #222; } - /* @noflip */ .mw-content-ltr .tocnumber { padding-left: 0; diff --git a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js index 98ed3eecfb..64cfcf5c47 100644 --- a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js +++ b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js @@ -26,7 +26,11 @@ }, apiCheckValid: function () { var that = this; - return this.isValid().done( function ( ok ) { + return this.getValidity().then( function () { + return $.Deferred().resolve( true ).promise(); + }, function () { + return $.Deferred().resolve( false ).promise(); + } ).done( function ( ok ) { ok = ok || suppressErrors; that.setIcon( ok ? null : 'alert' ); that.setIconTitle( ok ? '' : mw.message( 'apisandbox-alert-field' ).plain() ); @@ -35,9 +39,12 @@ }, dateTimeInputWidget: { - isValid: function () { - var ok = !Util.apiBool( this.paramInfo.required ) || this.getApiValue() !== ''; - return $.Deferred().resolve( ok ).promise(); + getValidity: function () { + if ( !Util.apiBool( this.paramInfo.required ) || this.getApiValue() !== '' ) { + return $.Deferred().resolve().promise(); + } else { + return $.Deferred().reject().promise(); + } } }, @@ -372,7 +379,7 @@ } ); widget.setIcon = widget.input.setIcon.bind( widget.input ); widget.setIconTitle = widget.input.setIconTitle.bind( widget.input ); - widget.isValid = widget.input.isValid.bind( widget.input ); + widget.getValidity = widget.input.getValidity.bind( widget.input ); widget.paramInfo = pi; $.extend( widget, WidgetMethods.textInputWidget ); if ( Util.apiBool( pi.enforcerange ) ) { @@ -388,7 +395,7 @@ } ); widget.setIcon = widget.input.setIcon.bind( widget.input ); widget.setIconTitle = widget.input.setIconTitle.bind( widget.input ); - widget.isValid = widget.input.isValid.bind( widget.input ); + widget.getValidity = widget.input.getValidity.bind( widget.input ); widget.input.setValidation( function ( value ) { return value === 'max' || widget.validateNumber( value ); } ); @@ -969,6 +976,14 @@ if ( data.modules.length ) { mw.loader.load( data.modules ); } + if ( data.status && data.status !== 200 ) { + $( '
    ' ) + .addClass( 'api-pretty-header api-pretty-status' ) + .append( + mw.message( 'api-format-prettyprint-status', data.status, data.statustext ).parse() + ) + .appendTo( $result ); + } $result.append( Util.parseHTML( data.html ) ); loadTime = data.time; } else if ( ( m = data.match( /][\s\S]*<\/pre>/ ) ) ) { diff --git a/resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js index a9b5bc30b5..ed251f24ee 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js @@ -183,6 +183,11 @@ .addClass( 'mw-widget-dateInputWidget' ) .append( this.$handle, this.textInput.$element, this.calendar.$element ); + // config.overlay is the selector to be used for config.$overlay, specified from PHP + if ( config.overlay ) { + config.$overlay = $( config.overlay ); + } + if ( config.$overlay ) { this.calendar.setFloatableContainer( this.$element ); config.$overlay.append( this.calendar.$element ); @@ -219,6 +224,12 @@ this.updateUI(); this.textInput.toggle( false ); this.calendar.toggle( false ); + + // Hide unused from PHP after infusion is done + // See InputWidget#reusePreInfuseDOM about config.$input + if ( config.$input ) { + config.$input.addClass( 'oo-ui-element-hidden' ); + } }; /* Inheritance */ diff --git a/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js b/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js index 222586f6e3..7ca19df0ad 100644 --- a/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.TitleWidget.js @@ -30,7 +30,6 @@ * @cfg {boolean} [relative=true] If a namespace is set, display titles relative to it * @cfg {boolean} [suggestions=true] Display search suggestions * @cfg {boolean} [showRedirectTargets=true] Show the targets of redirects - * @cfg {boolean} [showRedlink] Show red link to exact match if it doesn't exist * @cfg {boolean} [showImages] Show page images * @cfg {boolean} [showDescriptions] Show page descriptions * @cfg {boolean} [excludeCurrentPage] Exclude the current page from suggestions @@ -52,7 +51,6 @@ this.relative = config.relative !== undefined ? config.relative : true; this.suggestions = config.suggestions !== undefined ? config.suggestions : true; this.showRedirectTargets = config.showRedirectTargets !== false; - this.showRedlink = !!config.showRedlink; this.showImages = !!config.showImages; this.showDescriptions = !!config.showDescriptions; this.excludeCurrentPage = !!config.excludeCurrentPage; @@ -246,13 +244,6 @@ ) ); - if ( !pageExists ) { - pageData[ this.getQueryValue() ] = { - missing: true, known: false, redirect: false, disambiguation: false, - description: mw.msg( 'mw-widgets-titleinput-description-new-page' ) - }; - } - if ( this.cache ) { this.cache.set( pageData ); } @@ -261,10 +252,7 @@ if ( pageExists && !pageExistsExact ) { titles.unshift( this.getQueryValue() ); } - // Offer the exact text as a new page if the title is valid - if ( this.showRedlink && !pageExists && titleObj ) { - titles.push( this.getQueryValue() ); - } + for ( i = 0, len = titles.length; i < len; i++ ) { page = pageData[ titles[ i ] ] || {}; items.push( new mw.widgets.TitleOptionWidget( this.getOptionWidgetData( titles[ i ], page ) ) ); @@ -281,14 +269,18 @@ * @return {Object} Data for option widget */ mw.widgets.TitleWidget.prototype.getOptionWidgetData = function ( title, data ) { - var mwTitle = new mw.Title( title ); + var mwTitle = new mw.Title( title ), + description = data.description; + if ( data.missing && !description ) { + description = mw.msg( 'mw-widgets-titleinput-description-new-page' ); + } return { data: this.namespace !== null && this.relative ? mwTitle.getRelativeText( this.namespace ) : title, url: mwTitle.getUrl(), imageUrl: this.showImages ? data.imageUrl : null, - description: this.showDescriptions ? data.description : null, + description: this.showDescriptions ? description : null, missing: data.missing, redirect: data.redirect, disambiguation: data.disambiguation, diff --git a/resources/src/mediawiki/htmlform/ooui.styles.css b/resources/src/mediawiki/htmlform/ooui.styles.css index fc0fd6e0f3..40f4f5242e 100644 --- a/resources/src/mediawiki/htmlform/ooui.styles.css +++ b/resources/src/mediawiki/htmlform/ooui.styles.css @@ -18,8 +18,8 @@ /* Flatlist styling for PHP widgets... */ .mw-htmlform-flatlist .oo-ui-fieldLayout-align-inline, /* ...and for JS widgets */ -.mw-htmlform-flatlist .oo-ui-optionWidget, -.mw-htmlform-flatlist .oo-ui-multioptionWidget { +.mw-htmlform-flatlist .oo-ui-radioOptionWidget, +.mw-htmlform-flatlist .oo-ui-checkboxMultioptionWidget { display: inline-block; margin-right: 1em; } diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 6280c95eaa..d5258131ae 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -325,12 +325,15 @@ var text; if ( !this.exists() ) { - // Use as text if key does not exist - if ( this.format === 'escaped' || this.format === 'parse' ) { - // format 'escaped' and 'parse' need to have the brackets and key html escaped - return mw.html.escape( '<' + this.key + '>' ); - } - return '<' + this.key + '>'; + // Use ⧼key⧽ as text if key does not exist + // Err on the side of safety, ensure that the output + // is always html safe in the event the message key is + // missing, since in that case its highly likely the + // message key is user-controlled. + // '⧼' is used instead of '<' to side-step any + // double-escaping issues. + // (Keep synchronised with Message::toString() in PHP.) + return '⧼' + mw.html.escape( this.key ) + '⧽'; } if ( this.format === 'plain' || this.format === 'text' || this.format === 'parse' ) { @@ -1782,6 +1785,7 @@ // Depending on how corrupt the string is, it is likely that some // modules' implement() succeeded while the ones after the error will // never run and leave their modules in the 'loading' state forever. + mw.loader.store.stats.failed++; // Since this is an error not caused by an individual module but by // something that infected the implement call itself, don't take any @@ -2184,7 +2188,7 @@ items: {}, // Cache hit stats - stats: { hits: 0, misses: 0, expired: 0 }, + stats: { hits: 0, misses: 0, expired: 0, failed: 0 }, /** * Construct a JSON-serializable object representing the content of the store. @@ -2504,7 +2508,7 @@ * - this.Raw: The raw value is directly included. * - this.Cdata: The raw value is directly included. An exception is * thrown if it contains any illegal ETAGO delimiter. - * See . + * See . * @return {string} HTML */ element: function ( name, attrs, contents ) { diff --git a/resources/src/mediawiki/mediawiki.util.js b/resources/src/mediawiki/mediawiki.util.js index 866f213bd9..654f232c3d 100644 --- a/resources/src/mediawiki/mediawiki.util.js +++ b/resources/src/mediawiki/mediawiki.util.js @@ -128,7 +128,8 @@ ? util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query : util.wikiScript() + '?' + query; } else { - url = mw.config.get( 'wgArticlePath' ).replace( '$1', util.wikiUrlencode( title ) ); + url = mw.config.get( 'wgArticlePath' ) + .replace( '$1', util.wikiUrlencode( title ).replace( /\$/g, '$$$$' ) ); } // Append the encoded fragment diff --git a/resources/src/mediawiki/page/gallery.css b/resources/src/mediawiki/page/gallery.css index 2ff75d27c3..474d541d83 100644 --- a/resources/src/mediawiki/page/gallery.css +++ b/resources/src/mediawiki/page/gallery.css @@ -17,9 +17,6 @@ ul.gallery { margin: 2px; padding: 2px; display: block; - width: -moz-fit-content; - width: -webkit-fit-content; - width: fit-content; } li.gallerycaption { diff --git a/resources/src/oojs-ui-local.js b/resources/src/oojs-ui-local.js index 84ec92d0b0..99d97849f9 100644 --- a/resources/src/oojs-ui-local.js +++ b/resources/src/oojs-ui-local.js @@ -1,5 +1,10 @@ -// Connect OOjs UI to MediaWiki's localisation system ( function ( mw ) { + // Connect OOjs UI to MediaWiki's localisation system OO.ui.getUserLanguages = mw.language.getFallbackLanguageChain; OO.ui.msg = mw.msg; + // Connect OOjs UI's deprecation warnings to MediaWiki's logging system + OO.ui.warnDeprecation = function ( message ) { + mw.track( 'mw.deprecate', 'oojs-ui' ); + mw.log.warn( message ); + }; }( mediaWiki ) ); diff --git a/resources/src/polyfill-nodeTypes.js b/resources/src/polyfill-nodeTypes.js index 556b51b4fd..5f52d1e309 100644 --- a/resources/src/polyfill-nodeTypes.js +++ b/resources/src/polyfill-nodeTypes.js @@ -1,6 +1,6 @@ /** * Adds window.Node with node types according to: - * http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 + * https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 */ window.Node = window.Node || { diff --git a/resources/src/startup.js b/resources/src/startup.js index d026cb01ab..5e05590a44 100644 --- a/resources/src/startup.js +++ b/resources/src/startup.js @@ -64,10 +64,10 @@ function isCompatible( str ) { // Hardcoded exceptions for browsers that pass the requirement but we don't want to // support in the modern run-time. && !( - ua.match( /webOS\/1\.[0-4]/ ) || + ua.match( /webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass/ ) || ua.match( /PlayStation/i ) || - ua.match( /SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo/ ) || - ( ua.match( /Glass/ ) && ua.match( /Android/ ) ) + // UC Mini (speed mode on) + ua.match( /^Mozilla\/5\.0 .+ Gecko\/$/ ) ) ); } diff --git a/tests/common/TestSetup.php b/tests/common/TestSetup.php index 6c3ad0761e..53e724bbed 100644 --- a/tests/common/TestSetup.php +++ b/tests/common/TestSetup.php @@ -15,6 +15,7 @@ class TestSetup { global $wgMainStash; global $wgLanguageConverterCacheType, $wgUseDatabaseMessages; global $wgLocaltimezone, $wgLocalisationCacheConf; + global $wgSearchType; global $wgDevelopmentWarnings; global $wgSessionProviders, $wgSessionPbkdf2Iterations; global $wgJobTypeConf; @@ -50,6 +51,9 @@ class TestSetup { $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull'; + // Do not bother updating search tables + $wgSearchType = 'SearchEngineDummy'; + // Generic MediaWiki\Session\SessionManager configuration for tests // We use CookieSessionProvider because things might be expecting // cookies to show up in a FauxRequest somewhere. diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index a19fea1bd0..0bfa318081 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -41,6 +41,7 @@ $wgAutoloadClasses += [ 'ParserTestResult' => "$testDir/parser/ParserTestResult.php", 'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php", 'PhpunitTestRecorder' => "$testDir/parser/PhpunitTestRecorder.php", + 'TestFileEditor' => "$testDir/parser/TestFileEditor.php", 'TestFileReader' => "$testDir/parser/TestFileReader.php", 'TestRecorder' => "$testDir/parser/TestRecorder.php", 'TidySupport' => "$testDir/parser/TidySupport.php", diff --git a/tests/parser/TestFileEditor.php b/tests/parser/TestFileEditor.php new file mode 100644 index 0000000000..05b1216fe6 --- /dev/null +++ b/tests/parser/TestFileEditor.php @@ -0,0 +1,196 @@ +execute(); + return $editor->result; + } + + private function __construct( $text, array $deletions, array $changes, $warningCallback ) { + $this->lines = explode( "\n", $text ); + $this->numLines = count( $this->lines ); + $this->deletions = array_flip( $deletions ); + $this->changes = $changes; + $this->pos = 0; + $this->warningCallback = $warningCallback; + $this->result = ''; + } + + private function execute() { + while ( $this->pos < $this->numLines ) { + $line = $this->lines[$this->pos]; + switch ( $this->getHeading( $line ) ) { + case 'test': + $this->parseTest(); + break; + case 'hooks': + case 'functionhooks': + case 'transparenthooks': + $this->parseHooks(); + break; + default: + if ( $this->pos < $this->numLines - 1 ) { + $line .= "\n"; + } + $this->emitComment( $line ); + $this->pos++; + } + } + foreach ( $this->deletions as $deletion => $unused ) { + $this->warning( "Could not find test \"$deletion\" to delete it" ); + } + foreach ( $this->changes as $test => $sectionChanges ) { + foreach ( $sectionChanges as $section => $change ) { + $this->warning( "Could not find section \"$section\" in test \"$test\" " . + "to {$change['op']} it" ); + } + } + } + + private function warning( $text ) { + $cb = $this->warningCallback; + if ( $cb ) { + $cb( $text ); + } + } + + private function getHeading( $line ) { + if ( preg_match( '/^!!\s*(\S+)/', $line, $m ) ) { + return $m[1]; + } else { + return false; + } + } + + private function parseTest() { + $test = []; + $line = $this->lines[$this->pos++]; + $heading = $this->getHeading( $line ); + $section = [ + 'name' => $heading, + 'headingLine' => $line, + 'contents' => '' + ]; + + while ( $this->pos < $this->numLines ) { + $line = $this->lines[$this->pos++]; + $nextHeading = $this->getHeading( $line ); + if ( $nextHeading === 'end' ) { + $test[] = $section; + + // Add trailing line breaks to the "end" section, to allow for neat deletions + $trail = ''; + for ( $i = 0; $i < $this->numLines - $this->pos - 1; $i++ ) { + if ( $this->lines[$this->pos + $i] === '' ) { + $trail .= "\n"; + } else { + break; + } + } + $this->pos += strlen( $trail ); + + $test[] = [ + 'name' => 'end', + 'headingLine' => $line, + 'contents' => $trail + ]; + $this->emitTest( $test ); + return; + } elseif ( $nextHeading !== false ) { + $test[] = $section; + $heading = $nextHeading; + $section = [ + 'name' => $heading, + 'headingLine' => $line, + 'contents' => '' + ]; + } else { + $section['contents'] .= "$line\n"; + } + } + + throw new Exception( 'Unexpected end of file' ); + } + + private function parseHooks() { + $line = $this->lines[$this->pos++]; + $heading = $this->getHeading( $line ); + $expectedEnd = 'end' . $heading; + $contents = $line; + + do { + $line = $this->lines[$this->pos++]; + $nextHeading = $this->getHeading( $line ); + $contents .= "$line\n"; + } while ( $this->pos < $this->numLines && $nextHeading !== $expectedEnd ); + + if ( $nextHeading !== $expectedEnd ) { + throw new Exception( 'Unexpected end of file' ); + } + $this->emitHooks( $heading, $contents ); + } + + protected function emitComment( $contents ) { + $this->result .= $contents; + } + + protected function emitTest( $test ) { + $testName = false; + foreach ( $test as $section ) { + if ( $section['name'] === 'test' ) { + $testName = rtrim( $section['contents'], "\n" ); + } + } + if ( isset( $this->deletions[$testName] ) ) { + // Acknowledge deletion + unset( $this->deletions[$testName] ); + return; + } + if ( isset( $this->changes[$testName] ) ) { + $changes =& $this->changes[$testName]; + foreach ( $test as $i => $section ) { + $sectionName = $section['name']; + if ( isset( $changes[$sectionName] ) ) { + $change = $changes[$sectionName]; + switch ( $change['op'] ) { + case 'rename': + $test[$i]['name'] = $change['value']; + $test[$i]['headingLine'] = "!! {$change['value']}"; + break; + case 'update': + $test[$i]['contents'] = $change['value']; + break; + case 'delete': + $test[$i]['deleted'] = true; + break; + default: + throw new Exception( "Unknown op: ${change['op']}" ); + } + // Acknowledge + // Note that we use the old section name for the rename op + unset( $changes[$sectionName] ); + } + } + } + foreach ( $test as $section ) { + if ( isset( $section['deleted'] ) ) { + continue; + } + $this->result .= $section['headingLine'] . "\n"; + $this->result .= $section['contents']; + } + } + + protected function emitHooks( $heading, $contents ) { + $this->result .= $contents; + } +} diff --git a/tests/parser/TestFileReader.php b/tests/parser/TestFileReader.php index 6279d68fd8..b6e811b1b8 100644 --- a/tests/parser/TestFileReader.php +++ b/tests/parser/TestFileReader.php @@ -130,12 +130,15 @@ class TestFileReader { 'input' => $data[$input], 'options' => $data['options'], 'config' => $data['config'], + 'line' => $this->sectionLineNum['test'], + 'file' => $this->file ]; if ( $nonTidySection !== false ) { // Add non-tidy test $this->tests[] = [ 'result' => $data[$nonTidySection], + 'resultSection' => $nonTidySection ] + $commonInfo; if ( $tidySection !== false ) { @@ -143,13 +146,16 @@ class TestFileReader { $this->tests[] = [ 'desc' => $data['test'] . ' (with tidy)', 'result' => $data[$tidySection], + 'resultSection' => $tidySection, 'options' => $data['options'] . ' tidy', + 'isSubtest' => true, ] + $commonInfo; } } elseif ( $tidySection !== false ) { // No need to override desc when there is no subtest $this->tests[] = [ 'result' => $data[$tidySection], + 'resultSection' => $tidySection, 'options' => $data['options'] . ' tidy' ] + $commonInfo; } else { @@ -258,7 +264,6 @@ class TestFileReader { $this->sectionLineNum = []; $this->sectionData = []; $this->section = null; - } /** diff --git a/tests/parser/TestRecorder.php b/tests/parser/TestRecorder.php index 70215b6efe..4b816991a3 100644 --- a/tests/parser/TestRecorder.php +++ b/tests/parser/TestRecorder.php @@ -32,7 +32,7 @@ * * @since 1.22 */ -abstract class TestRecorder { +class TestRecorder { /** * Called at beginning of the parser test run diff --git a/tests/parser/editTests.php b/tests/parser/editTests.php new file mode 100644 index 0000000000..a9704e69e2 --- /dev/null +++ b/tests/parser/editTests.php @@ -0,0 +1,490 @@ +addOption( 'session-data', 'internal option, do not use', false, true ); + $this->addOption( 'use-tidy-config', + 'Use the wiki\'s Tidy configuration instead of known-good' . + 'defaults.' ); + } + + public function finalSetup() { + parent::finalSetup(); + self::requireTestsAutoloader(); + TestSetup::applyInitialConfig(); + } + + public function execute() { + $this->termWidth = $this->getTermSize()[0] - 1; + + $this->recorder = new TestRecorder(); + $this->setupFileData(); + + if ( $this->hasOption( 'session-data' ) ) { + $this->session = json_decode( $this->getOption( 'session-data' ), true ); + } else { + $this->session = [ 'options' => [] ]; + } + if ( $this->hasOption( 'use-tidy-config' ) ) { + $this->session['options']['use-tidy-config'] = true; + } + $this->runner = new ParserTestRunner( $this->recorder, $this->session['options'] ); + + $this->runTests(); + + if ( $this->numFailed === 0 ) { + if ( $this->numSkipped === 0 ) { + print "All tests passed!\n"; + } else { + print "All tests passed (but skipped {$this->numSkipped})\n"; + } + return; + } + print "{$this->numFailed} test(s) failed.\n"; + $this->showResults(); + } + + protected function setupFileData() { + global $wgParserTestFiles; + $this->testFiles = []; + $this->testCount = 0; + foreach ( $wgParserTestFiles as $file ) { + $fileInfo = TestFileReader::read( $file ); + $this->testFiles[$file] = $fileInfo; + $this->testCount += count( $fileInfo['tests'] ); + } + } + + protected function runTests() { + $teardown = $this->runner->staticSetup(); + $teardown = $this->runner->setupDatabase( $teardown ); + $teardown = $this->runner->setupUploads( $teardown ); + + print "Running tests...\n"; + $this->results = []; + $this->numExecuted = 0; + $this->numSkipped = 0; + $this->numFailed = 0; + foreach ( $this->testFiles as $fileName => $fileInfo ) { + $this->runner->addArticles( $fileInfo['articles'] ); + foreach ( $fileInfo['tests'] as $testInfo ) { + $result = $this->runner->runTest( $testInfo ); + if ( $result === false ) { + $this->numSkipped++; + } elseif ( !$result->isSuccess() ) { + $this->results[$fileName][$testInfo['desc']] = $result; + $this->numFailed++; + } + $this->numExecuted++; + $this->showProgress(); + } + } + print "\n"; + } + + protected function showProgress() { + $done = $this->numExecuted; + $total = $this->testCount; + $width = $this->termWidth - 9; + $pos = round( $width * $done / $total ); + printf( '│' . str_repeat( '█', $pos ) . str_repeat( '-', $width - $pos ) . + "│ %5.1f%%\r", $done / $total * 100 ); + } + + protected function showResults() { + if ( isset( $this->session['startFile'] ) ) { + $startFile = $this->session['startFile']; + $startTest = $this->session['startTest']; + $foundStart = false; + } else { + $startFile = false; + $startTest = false; + $foundStart = true; + } + + $testIndex = 0; + foreach ( $this->testFiles as $fileName => $fileInfo ) { + if ( !isset( $this->results[$fileName] ) ) { + continue; + } + if ( !$foundStart && $startFile !== false && $fileName !== $startFile ) { + $testIndex += count( $this->results[$fileName] ); + continue; + } + foreach ( $fileInfo['tests'] as $testInfo ) { + if ( !isset( $this->results[$fileName][$testInfo['desc']] ) ) { + continue; + } + $result = $this->results[$fileName][$testInfo['desc']]; + $testIndex++; + if ( !$foundStart && $startTest !== false ) { + if ( $testInfo['desc'] !== $startTest ) { + continue; + } + $foundStart = true; + } + + $this->handleFailure( $testIndex, $testInfo, $result ); + } + } + + if ( !$foundStart ) { + print "Could not find the test after a restart, did you rename it?"; + unset( $this->session['startFile'] ); + unset( $this->session['startTest'] ); + $this->showResults(); + } + print "All done\n"; + } + + protected function heading( $text ) { + $term = new AnsiTermColorer; + $heading = "─── $text "; + $heading .= str_repeat( '─', $this->termWidth - mb_strlen( $heading ) ); + $heading = $term->color( 34 ) . $heading . $term->reset() . "\n"; + return $heading; + } + + protected function unifiedDiff( $left, $right ) { + $fromLines = explode( "\n", $left ); + $toLines = explode( "\n", $right ); + $formatter = new UnifiedDiffFormatter; + return $formatter->format( new Diff( $fromLines, $toLines ) ); + } + + protected function handleFailure( $index, $testInfo, $result ) { + $term = new AnsiTermColorer; + $div1 = $term->color( 34 ) . str_repeat( '━', $this->termWidth ) . + $term->reset() . "\n"; + $div2 = $term->color( 34 ) . str_repeat( '─', $this->termWidth ) . + $term->reset() . "\n"; + + print $div1; + print "Failure $index/{$this->numFailed}: {$testInfo['file']} line {$testInfo['line']}\n" . + "{$testInfo['desc']}\n"; + + print $this->heading( 'Input' ); + print "{$testInfo['input']}\n"; + + print $this->heading( 'Alternating expected/actual output' ); + print $this->alternatingAligned( $result->expected, $result->actual ); + + print $this->heading( 'Diff' ); + + $dwdiff = $this->dwdiff( $result->expected, $result->actual ); + if ( $dwdiff !== false ) { + $diff = $dwdiff; + } else { + $diff = $this->unifiedDiff( $result->expected, $result->actual ); + } + print $diff; + + if ( $testInfo['options'] || $testInfo['config'] ) { + print $this->heading( 'Options / Config' ); + if ( $testInfo['options'] ) { + print $testInfo['options'] . "\n"; + } + if ( $testInfo['config'] ) { + print $testInfo['config'] . "\n"; + } + } + + print $div2; + print "What do you want to do?\n"; + $specs = [ + '[R]eload code and run again', + '[U]pdate source file, copy actual to expected', + '[I]gnore' ]; + + if ( strpos( $testInfo['options'], ' tidy' ) === false ) { + if ( empty( $testInfo['isSubtest'] ) ) { + $specs[] = "Enable [T]idy"; + } + } else { + $specs[] = 'Disable [T]idy'; + } + + if ( !empty( $testInfo['isSubtest'] ) ) { + $specs[] = 'Delete [s]ubtest'; + } + $specs[] = '[D]elete test'; + $specs[] = '[Q]uit'; + + $options = []; + foreach ( $specs as $spec ) { + if ( !preg_match( '/^(.*\[)(.)(\].*)$/', $spec, $m ) ) { + throw new MWException( 'Invalid option spec: ' . $spec ); + } + print '* ' . $m[1] . $term->color( 35 ) . $m[2] . $term->color( 0 ) . $m[3] . "\n"; + $options[strtoupper( $m[2] )] = true; + } + + do { + $response = $this->readconsole(); + $cmdResult = false; + if ( $response === false ) { + exit( 0 ); + } + + $response = strtoupper( trim( $response ) ); + if ( !isset( $options[$response] ) ) { + print "Invalid response, please enter a single letter from the list above\n"; + continue; + } + + switch ( strtoupper( trim( $response ) ) ) { + case 'R': + $cmdResult = $this->reload( $testInfo ); + break; + case 'U': + $cmdResult = $this->update( $testInfo, $result ); + break; + case 'I': + return; + case 'T': + $cmdResult = $this->switchTidy( $testInfo ); + break; + case 'S': + $cmdResult = $this->deleteSubtest( $testInfo ); + break; + case 'D': + $cmdResult = $this->deleteTest( $testInfo ); + break; + case 'Q': + exit( 0 ); + } + } while ( !$cmdResult ); + } + + protected function dwdiff( $expected, $actual ) { + if ( !is_executable( '/usr/bin/dwdiff' ) ) { + return false; + } + + $markers = [ + "\n" => '¶', + ' ' => '·', + "\t" => '→' + ]; + $markedExpected = strtr( $expected, $markers ); + $markedActual = strtr( $actual, $markers ); + $diff = $this->unifiedDiff( $markedExpected, $markedActual ); + + $tempFile = tmpfile(); + fwrite( $tempFile, $diff ); + fseek( $tempFile, 0 ); + $pipes = []; + $proc = proc_open( '/usr/bin/dwdiff -Pc --diff-input', + [ 0 => $tempFile, 1 => [ 'pipe', 'w' ], 2 => STDERR ], + $pipes ); + + if ( !$proc ) { + return false; + } + + $result = stream_get_contents( $pipes[1] ); + proc_close( $proc ); + fclose( $tempFile ); + return $result; + } + + protected function alternatingAligned( $expectedStr, $actualStr ) { + $expectedLines = explode( "\n", $expectedStr ); + $actualLines = explode( "\n", $actualStr ); + $maxLines = max( count( $expectedLines ), count( $actualLines ) ); + $result = ''; + for ( $i = 0; $i < $maxLines; $i++ ) { + if ( $i < count( $expectedLines ) ) { + $expectedLine = $expectedLines[$i]; + $expectedChunks = str_split( $expectedLine, $this->termWidth - 3 ); + } else { + $expectedChunks = []; + } + + if ( $i < count( $actualLines ) ) { + $actualLine = $actualLines[$i]; + $actualChunks = str_split( $actualLine, $this->termWidth - 3 ); + } else { + $actualChunks = []; + } + + $maxChunks = max( count( $expectedChunks ), count( $actualChunks ) ); + + for ( $j = 0; $j < $maxChunks; $j++ ) { + if ( isset( $expectedChunks[$j] ) ) { + $result .= "E: " . $expectedChunks[$j]; + if ( $j === count( $expectedChunks ) - 1 ) { + $result .= "¶"; + } + $result .= "\n"; + } else { + $result .= "E:\n"; + } + $result .= "\33[4m" . // underline + "A: "; + if ( isset( $actualChunks[$j] ) ) { + $result .= $actualChunks[$j]; + if ( $j === count( $actualChunks ) - 1 ) { + $result .= "¶"; + } + } + $result .= "\33[0m\n"; // reset + } + } + return $result; + } + + protected function reload( $testInfo ) { + global $argv; + pcntl_exec( PHP_BINARY, [ + $argv[0], + '--session-data', + json_encode( [ + 'startFile' => $testInfo['file'], + 'startTest' => $testInfo['desc'] + ] + $this->session ) ] ); + + print "pcntl_exec() failed\n"; + return false; + } + + protected function findTest( $file, $testInfo ) { + $initialPart = ''; + for ( $i = 1; $i < $testInfo['line']; $i++ ) { + $line = fgets( $file ); + if ( $line === false ) { + print "Error reading from file\n"; + return false; + } + $initialPart .= $line; + } + + $line = fgets( $file ); + if ( !preg_match( '/^!!\s*test/', $line ) ) { + print "Test has moved, cannot edit\n"; + return false; + } + + $testPart = $line; + + $desc = fgets( $file ); + if ( trim( $desc ) !== $testInfo['desc'] ) { + print "Description does not match, cannot edit\n"; + return false; + } + $testPart .= $desc; + return [ $initialPart, $testPart ]; + } + + protected function getOutputFileName( $inputFileName ) { + if ( is_writable( $inputFileName ) ) { + $outputFileName = $inputFileName; + } else { + $outputFileName = wfTempDir() . '/' . basename( $inputFileName ); + print "Cannot write to input file, writing to $outputFileName instead\n"; + } + return $outputFileName; + } + + protected function editTest( $fileName, $deletions, $changes ) { + $text = file_get_contents( $fileName ); + if ( $text === false ) { + print "Unable to open test file!"; + return false; + } + $result = TestFileEditor::edit( $text, $deletions, $changes, + function ( $msg ) { + print "$msg\n"; + } + ); + if ( is_writable( $fileName ) ) { + file_put_contents( $fileName, $result ); + print "Wrote updated file\n"; + } else { + print "Cannot write updated file, here is a patch you can paste:\n\n"; + print + "--- {$fileName}\n" . + "+++ {$fileName}~\n" . + $this->unifiedDiff( $text, $result ) . + "\n"; + } + } + + protected function update( $testInfo, $result ) { + $this->editTest( $testInfo['file'], + [], // deletions + [ // changes + $testInfo['test'] => [ + $testInfo['resultSection'] => [ + 'op' => 'update', + 'value' => $result->actual . "\n" + ] + ] + ] + ); + } + + protected function deleteTest( $testInfo ) { + $this->editTest( $testInfo['file'], + [ $testInfo['test'] ], // deletions + [] // changes + ); + } + + protected function switchTidy( $testInfo ) { + $resultSection = $testInfo['resultSection']; + if ( in_array( $resultSection, [ 'html/php', 'html/*', 'html', 'result' ] ) ) { + $newSection = 'html+tidy'; + } elseif ( in_array( $resultSection, [ 'html/php+tidy', 'html+tidy' ] ) ) { + $newSection = 'html'; + } else { + print "Unrecognised result section name \"$resultSection\""; + return; + } + + $this->editTest( $testInfo['file'], + [], // deletions + [ // changes + $testInfo['test'] => [ + $resultSection => [ + 'op' => 'rename', + 'value' => $newSection + ] + ] + ] + ); + } + + protected function deleteSubtest( $testInfo ) { + $this->editTest( $testInfo['file'], + [], // deletions + [ // changes + $testInfo['test'] => [ + $testInfo['resultSection'] => [ + 'op' => 'delete' + ] + ] + ] + ); + } +} + +$maintClass = 'ParserEditTests'; +require RUN_MAINTENANCE_IF_MAIN; diff --git a/tests/parser/parserTests.php b/tests/parser/parserTests.php index 38923f0494..1d0867abf0 100644 --- a/tests/parser/parserTests.php +++ b/tests/parser/parserTests.php @@ -68,7 +68,8 @@ class ParserTestsMaintenance extends Maintenance { 'are: removeTbody to remove tags; and trimWhitespace ' . 'to trim whitespace from the start and end of text nodes.', false, true ); - $this->addOption( 'use-tidy-config', 'Use the wiki\'s Tidy configuration instead of known-good' . + $this->addOption( 'use-tidy-config', + 'Use the wiki\'s Tidy configuration instead of known-good' . 'defaults.' ); } diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index ba7b0d462a..9fe30291c9 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -1,5 +1,5 @@ # MediaWiki Parser test cases -# Some taken from http://meta.wikimedia.org/wiki/Parser_testing +# Some taken from https://meta.wikimedia.org/wiki/Parser_testing # All (C) their respective authors and released under the GPL # # The syntax should be fairly self-explanatory. @@ -524,7 +524,7 @@ http://fr.wikipedia.org/wiki/🍺 !! end # Note that the html+tidy output removes the spaces after the
  • , -# which is a bug (http://sourceforge.net/p/tidy/bugs/945/, etc). +# which is a bug (https://sourceforge.net/p/tidy/bugs/945/, etc). # This is an issue for all tests with lists. We intentionally do # *not* add html+tidy clauses for these, as we don't want to # document/test the broken behavior. (Parsoid matches the non-tidy @@ -1230,7 +1230,7 @@ Text-level semantic html elements in wikitext !! end # test cases taken from -# http://www.w3.org/TR/html5/text-level-semantics.html#the-ruby-element +# https://www.w3.org/TR/html5/text-level-semantics.html#the-ruby-element !! test Ruby markup (W3C-style) !! wikitext @@ -1293,7 +1293,7 @@ Non-word characters don't terminate tag names (bug 17663, 40670, 52022)

    !! end -# There is a tidy bug here: http://sourceforge.net/p/tidy/bugs/946/ +# There is a tidy bug here: https://sourceforge.net/p/tidy/bugs/946/ # If the non-word-character tag made it through the sanitizer, tidy # would munge it up. !! test @@ -1420,6 +1420,15 @@ sed abit.

    !! end +!! test +Don't parse (T149622) +!! wikitext + +!! html/php +

    <span class="error"> +

    +!! end + !! test nowiki 3 !! wikitext @@ -2703,10 +2712,12 @@ Templates: Handle empty comment-and-ws-only lines correctly bar}} -!! html +!! html/php

    foo bar

    +!! html/parsoid +

    foo bar

    !! end !! test @@ -2722,7 +2733,13 @@ Templates: Handle comments in the target {{echo|foo}} {{echo|foo}} -!!html/parsoid +!! html/php +

    foo +

    foo +

    foo +

    foo +

    +!! html/parsoid

    foo

    foo

    @@ -2746,7 +2763,13 @@ Templates: Handle comments in parameter names (bug 67657) {{echo|1=foo}} {{echo|1=foo}} -!!html/parsoid +!! html/php +

    foo +

    foo +

    foo +

    foo +

    +!! html/parsoid

    foo

    foo

    @@ -2760,11 +2783,11 @@ Templates: Handle comments in parameter names (bug 67657) Templates: Other wikitext in parameter names (bug 67657) !! wikitext {{echo|''1''=foo}} -!!html/parsoid -

    {{{1}}}

    -!!html/php +!! html/php

    {{{1}}}

    +!! html/parsoid +

    {{{1}}}

    !!end #-------------------------------------------------------------------- @@ -3840,7 +3863,7 @@ Definition Lists: Hacky use to indent tables (WS-insensitive) ## All Parsoid only definition list tests have this difference. ## ## See also: https://phabricator.wikimedia.org/T8569 -## and http://lists.wikimedia.org/pipermail/wikitext-l/2011-November/000483.html +## and https://lists.wikimedia.org/pipermail/wikitext-l/2011-November/000483.html !! test Table / list interaction: indented table with lists in table contents @@ -5194,7 +5217,7 @@ http://www.example.com/?title=AT%26T

    http://www.example.com/?title=AT%26T

    !! end -# According to http://www.w3.org/TR/2011/WD-html5-20110525/Overview.html#parsing-urls a plain +# According to https://www.w3.org/TR/2011/WD-html5-20110525/Overview.html#parsing-urls a plain # % is actually legal in HTML5. Any change in output would need testing though. !! test Bug 4781, 5267: %25 in URL @@ -5781,7 +5804,7 @@ Plain ''italic'''s plain # This should not produce
    as
    # is the bare minimum required by the spec, see: -# http://www.w3.org/TR/xhtml-modularization/dtd_module_defs.html#a_module_Basic_Tables +# https://www.w3.org/TR/xhtml-modularization/dtd_module_defs.html#a_module_Basic_Tables # Parsoid team replies: empty table tags are legal in HTML5 !! test A table with no data. @@ -19098,7 +19121,7 @@ parsoid=wt2html,wt2wt,html2html

    îî

    !! end -# See: http://www.w3.org/TR/html5/syntax.html#character-references +# See: https://www.w3.org/TR/html5/syntax.html#character-references # Note that U+000C (form feed) is not a valid XML character, so # it is banned even though allowed in HTML5. !! test diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 959901653d..e0f44166d6 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -1190,7 +1190,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { /** @var ExternalStoreDB $externalStoreDB */ $externalStoreDB = ExternalStore::getStoreObject( 'DB' ); - $defaultArray = (array) $wgDefaultExternalStore; + $defaultArray = (array)$wgDefaultExternalStore; $dbws = []; foreach ( $defaultArray as $url ) { if ( strpos( $url, 'DB://' ) === 0 ) { @@ -1217,7 +1217,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { return false; } - $defaultArray = (array) $wgDefaultExternalStore; + $defaultArray = (array)$wgDefaultExternalStore; foreach ( $defaultArray as $url ) { if ( strpos( $url, 'DB://' ) === 0 ) { return true; diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php index 5c4d1c05d7..e491d610c1 100644 --- a/tests/phpunit/includes/FormOptionsTest.php +++ b/tests/phpunit/includes/FormOptionsTest.php @@ -52,6 +52,9 @@ class FormOptionsTest extends MediaWikiTestCase { private function assertGuessString( $data ) { $this->guess( FormOptions::STRING, $data ); } + private function assertGuessArray( $data ) { + $this->guess( FormOptions::ARR, $data ); + } /** Generic helper */ private function guess( $expected, $data ) { @@ -84,15 +87,10 @@ class FormOptionsTest extends MediaWikiTestCase { $this->assertGuessString( '5' ); $this->assertGuessString( '0' ); $this->assertGuessString( '1.5' ); - } - /** - * @expectedException MWException - * @covers FormOptions::guessType - */ - public function testGuessTypeOnArrayThrowException() { - $this->object->guessType( [ 'foo' ] ); + $this->assertGuessArray( [ 'foo' ] ); } + /** * @expectedException MWException * @covers FormOptions::guessType diff --git a/tests/phpunit/includes/GitInfoTest.php b/tests/phpunit/includes/GitInfoTest.php index 9f4a01c9d8..89416f2ff2 100644 --- a/tests/phpunit/includes/GitInfoTest.php +++ b/tests/phpunit/includes/GitInfoTest.php @@ -18,7 +18,6 @@ class GitInfoTest extends MediaWikiTestCase { $this->assertEquals( 'master', $gitInfo->getCurrentBranch() ); $this->assertContains( '0123456789abcdef0123456789abcdef01234567', $gitInfo->getHeadViewUrl() ); - } public function testValidJsonData() { diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php index bc5096639b..e2ee193416 100644 --- a/tests/phpunit/includes/HtmlTest.php +++ b/tests/phpunit/includes/HtmlTest.php @@ -92,7 +92,6 @@ class HtmlTest extends MediaWikiTestCase { * @covers Html::expandAttributes */ public function testExpandAttributesSkipsNullAndFalse() { - # ## EMPTY ######## $this->assertEmpty( Html::expandAttributes( [ 'foo' => null ] ), @@ -190,7 +189,6 @@ class HtmlTest extends MediaWikiTestCase { Html::expandAttributes( [ 'zero' => 0 ] ), 'Number 0 value needs no quotes' ); - } /** @@ -447,7 +445,7 @@ class HtmlTest extends MediaWikiTestCase { /** * List of input element types values introduced by HTML5 - * Full list at http://www.w3.org/TR/html-markup/input.html + * Full list at https://www.w3.org/TR/html-markup/input.html */ public static function provideHtml5InputTypes() { $types = [ diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php index ae0b4bee8b..c3804c65bb 100644 --- a/tests/phpunit/includes/HttpTest.php +++ b/tests/phpunit/includes/HttpTest.php @@ -188,7 +188,7 @@ class HttpTest extends MediaWikiTestCase { /** * Constant values are from PHP 5.3.28 using cURL 7.24.0 - * @see http://php.net/manual/en/curl.constants.php + * @see https://secure.php.net/manual/en/curl.constants.php * * All constant values are present so that developers don’t need to remember * to add them if added at a later date. The commented out constants were diff --git a/tests/phpunit/includes/LinkFilterTest.php b/tests/phpunit/includes/LinkFilterTest.php index 61b165a4dc..428b0129a0 100644 --- a/tests/phpunit/includes/LinkFilterTest.php +++ b/tests/phpunit/includes/LinkFilterTest.php @@ -6,7 +6,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { protected function setUp() { - parent::setUp(); $this->setMwGlobals( 'wgUrlProtocols', [ @@ -26,7 +25,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { 'mms://', '//', ] ); - } /** @@ -38,11 +36,9 @@ class LinkFilterTest extends MediaWikiLangTestCase { * @return string Regex */ function createRegexFromLIKE( $like ) { - $regex = '!^'; foreach ( $like as $item ) { - if ( $item instanceof LikeMatch ) { if ( $item->toString() == '%' ) { $regex .= '.*'; @@ -58,7 +54,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { $regex .= '$!'; return $regex; - } /** @@ -67,7 +62,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { * @return array */ public static function provideValidPatterns() { - return [ // Protocol, Search pattern, URL which matches the pattern [ 'http://', '*.test.com', 'http://www.test.com' ], @@ -164,7 +158,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { // [ '', 'https://*.wikimedia.org/r/#/q/status:open,n,z', // 'https://gerrit.wikimedia.org/XXX/r/#/q/status:open,n,z', false ], ]; - } /** @@ -181,7 +174,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { * @param bool $shouldBeFound Should the URL be found? (defaults true) */ function testMakeLikeArrayWithValidPatterns( $protocol, $pattern, $url, $shouldBeFound = true ) { - $indexes = wfMakeUrlIndexes( $url ); $likeArray = LinkFilter::makeLikeArray( $pattern, $protocol ); @@ -211,7 +203,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { "Search pattern '$protocol$pattern' should not find url '$url' \n$debugmsg" ); } - } /** @@ -220,7 +211,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { * @return array */ public static function provideInvalidPatterns() { - return [ [ '' ], [ '*' ], @@ -240,7 +230,6 @@ class LinkFilterTest extends MediaWikiLangTestCase { [ 'test.com/*/index' ], [ 'test.com/dir/index?arg=*' ], ]; - } /** @@ -253,12 +242,10 @@ class LinkFilterTest extends MediaWikiLangTestCase { * @param string $pattern Invalid search pattern */ function testMakeLikeArrayWithInvalidPatterns( $pattern ) { - $this->assertFalse( LinkFilter::makeLikeArray( $pattern ), "'$pattern' is not a valid pattern and should be rejected" ); - } } diff --git a/tests/phpunit/includes/MediaWikiServicesTest.php b/tests/phpunit/includes/MediaWikiServicesTest.php index a5e2ac0d8b..dc0c64c4f6 100644 --- a/tests/phpunit/includes/MediaWikiServicesTest.php +++ b/tests/phpunit/includes/MediaWikiServicesTest.php @@ -315,6 +315,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase { 'CryptRand' => [ 'CryptRand', CryptRand::class ], 'CryptHKDF' => [ 'CryptHKDF', CryptHKDF::class ], 'MediaHandlerFactory' => [ 'MediaHandlerFactory', MediaHandlerFactory::class ], + 'Parser' => [ 'Parser', Parser::class ], 'GenderCache' => [ 'GenderCache', GenderCache::class ], 'LinkCache' => [ 'LinkCache', LinkCache::class ], 'LinkRenderer' => [ 'LinkRenderer', LinkRenderer::class ], diff --git a/tests/phpunit/includes/MediaWikiTest.php b/tests/phpunit/includes/MediaWikiTest.php index df92012e29..a8d1e33950 100644 --- a/tests/phpunit/includes/MediaWikiTest.php +++ b/tests/phpunit/includes/MediaWikiTest.php @@ -34,7 +34,7 @@ class MediaWikiTest extends MediaWikiTestCase { 'url' => 'http://example.org/w/index.php?title=Foo_Bar', 'query' => [ 'title' => 'Foo_Bar' ], 'title' => 'Foo_Bar', - 'redirect' => 'http://example.org/wiki/Foo_Bar', + 'redirect' => false, ], [ // View: Script path with implicit title from page id @@ -76,21 +76,21 @@ class MediaWikiTest extends MediaWikiTestCase { 'url' => 'http://example.org/w/?title=Foo_Bar', 'query' => [ 'title' => 'Foo_Bar' ], 'title' => 'Foo_Bar', - 'redirect' => 'http://example.org/wiki/Foo_Bar', + 'redirect' => false, ], [ // View: Root path with escaped title 'url' => 'http://example.org/?title=Foo_Bar', 'query' => [ 'title' => 'Foo_Bar' ], 'title' => 'Foo_Bar', - 'redirect' => 'http://example.org/wiki/Foo_Bar', + 'redirect' => false, ], [ // View: Canonical with redundant query 'url' => 'http://example.org/wiki/Foo_Bar?action=view', 'query' => [ 'action' => 'view' ], 'title' => 'Foo_Bar', - 'redirect' => 'http://example.org/wiki/Foo_Bar', + 'redirect' => false, ], [ // Edit: Canonical view url with action query @@ -104,7 +104,7 @@ class MediaWikiTest extends MediaWikiTestCase { 'url' => 'http://example.org/w/index.php?title=Foo_Bar&action=view', 'query' => [ 'title' => 'Foo_Bar', 'action' => 'view' ], 'title' => 'Foo_Bar', - 'redirect' => 'http://example.org/wiki/Foo_Bar', + 'redirect' => false, ], [ // Edit: Index with action query diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 4c689abb04..8390e1fd39 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -18,8 +18,8 @@ class MessageTest extends MediaWikiLangTestCase { public function testConstructor( $expectedLang, $key, $params, $language ) { $message = new Message( $key, $params, $language ); - $this->assertEquals( $key, $message->getKey() ); - $this->assertEquals( $params, $message->getParams() ); + $this->assertSame( $key, $message->getKey() ); + $this->assertSame( $params, $message->getParams() ); $this->assertEquals( $expectedLang, $message->getLanguage() ); $messageSpecifier = $this->getMockForAbstractClass( 'MessageSpecifier' ); @@ -29,8 +29,8 @@ class MessageTest extends MediaWikiLangTestCase { ->method( 'getParams' )->will( $this->returnValue( $params ) ); $message = new Message( $messageSpecifier, [], $language ); - $this->assertEquals( $key, $message->getKey() ); - $this->assertEquals( $params, $message->getParams() ); + $this->assertSame( $key, $message->getKey() ); + $this->assertSame( $params, $message->getParams() ); $this->assertEquals( $expectedLang, $message->getLanguage() ); } @@ -97,7 +97,7 @@ class MessageTest extends MediaWikiLangTestCase { $returned = call_user_func_array( [ $msg, 'params' ], $args ); $this->assertSame( $msg, $returned ); - $this->assertEquals( $expected, $msg->getParams() ); + $this->assertSame( $expected, $msg->getParams() ); } public static function provideConstructorLanguage() { @@ -165,8 +165,8 @@ class MessageTest extends MediaWikiLangTestCase { $msg = new Message( $key ); $this->assertContains( $msg->getKey(), $expected ); - $this->assertEquals( $expected, $msg->getKeysToTry() ); - $this->assertEquals( count( $expected ) > 1, $msg->isMultiKey() ); + $this->assertSame( $expected, $msg->getKeysToTry() ); + $this->assertSame( count( $expected ) > 1, $msg->isMultiKey() ); } /** @@ -190,13 +190,13 @@ class MessageTest extends MediaWikiLangTestCase { * @covers Message::__construct */ public function testWfMessageParams() { - $this->assertEquals( 'Return to $1.', wfMessage( 'returnto' )->text() ); - $this->assertEquals( 'Return to $1.', wfMessage( 'returnto', [] )->text() ); - $this->assertEquals( + $this->assertSame( 'Return to $1.', wfMessage( 'returnto' )->text() ); + $this->assertSame( 'Return to $1.', wfMessage( 'returnto', [] )->text() ); + $this->assertSame( 'You have foo (bar).', wfMessage( 'youhavenewmessages', 'foo', 'bar' )->text() ); - $this->assertEquals( + $this->assertSame( 'You have foo (bar).', wfMessage( 'youhavenewmessages', [ 'foo', 'bar' ] )->text() ); @@ -222,13 +222,13 @@ class MessageTest extends MediaWikiLangTestCase { * @covers Message::toString */ public function testToStringKey() { - $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->text() ); - $this->assertEquals( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->text() ); - $this->assertEquals( '⧼i<dont>exist-evar⧽', wfMessage( 'iexist-evar' )->text() ); - $this->assertEquals( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->plain() ); - $this->assertEquals( '⧼i<dont>exist-evar⧽', wfMessage( 'iexist-evar' )->plain() ); - $this->assertEquals( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->escaped() ); - $this->assertEquals( + $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->text() ); + $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->text() ); + $this->assertSame( '⧼i<dont>exist-evar⧽', wfMessage( 'iexist-evar' )->text() ); + $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->plain() ); + $this->assertSame( '⧼i<dont>exist-evar⧽', wfMessage( 'iexist-evar' )->plain() ); + $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->escaped() ); + $this->assertSame( '⧼i<dont>exist-evar⧽', wfMessage( 'iexist-evar' )->escaped() ); @@ -236,11 +236,14 @@ class MessageTest extends MediaWikiLangTestCase { public static function provideToString() { return [ - [ 'mainpage', 'Main Page' ], - [ 'i-dont-exist-evar', '⧼i-dont-exist-evar⧽' ], - [ 'i-dont-exist-evar', '⧼i-dont-exist-evar⧽', 'escaped' ], - [ 'script>alert(1)alert(1)alert(1)alert(1)$format(); - $this->assertEquals( $expect, $msg->toString() ); - $this->assertEquals( $expect, $msg->__toString() ); + $this->assertSame( $expect, $msg->$format() ); + $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by previous call' ); + $this->assertSame( $expectImplicit, $msg->__toString() ); + $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by __toString' ); + } + + public static function provideToString_raw() { + return [ + [ 'foo', 'parse', 'foo', 'foo' ], + [ 'foo', 'escaped', '<span>foo</span>', + 'foo' ], + [ 'foo', 'plain', 'foo', 'foo' ], + [ '', 'parse', '<script>alert(1)</script>', + '<script>alert(1)</script>' ], + [ '', 'escaped', '<script>alert(1)</script>', + '<script>alert(1)</script>' ], + [ '', 'plain', '', + '<script>alert(1)</script>' ], + ]; + } + + /** + * @covers Message::toString + * @covers Message::__toString + * @dataProvider provideToString_raw + */ + public function testToString_raw( $message, $format, $expect, $expectImplicit ) { + // make the message behave like RawMessage and use the key as-is + $msg = $this->getMockBuilder( Message::class )->setMethods( [ 'fetchMessage' ] ) + ->disableOriginalConstructor() + ->getMock(); + $msg->expects( $this->any() )->method( 'fetchMessage' )->willReturn( $message ); + /** @var Message $msg */ + $this->assertSame( $expect, $msg->$format() ); + $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by previous call' ); + $this->assertSame( $expectImplicit, $msg->__toString() ); + $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by __toString' ); } /** * @covers Message::inLanguage */ public function testInLanguage() { - $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inLanguage( 'en' )->text() ); - $this->assertEquals( 'Заглавная страница', + $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->inLanguage( 'en' )->text() ); + $this->assertSame( 'Заглавная страница', wfMessage( 'mainpage' )->inLanguage( 'ru' )->text() ); // NOTE: make sure internal caching of the message text is reset appropriately $msg = wfMessage( 'mainpage' ); - $this->assertEquals( 'Main Page', $msg->inLanguage( Language::factory( 'en' ) )->text() ); - $this->assertEquals( + $this->assertSame( 'Main Page', $msg->inLanguage( Language::factory( 'en' ) )->text() ); + $this->assertSame( 'Заглавная страница', $msg->inLanguage( Language::factory( 'ru' ) )->text() ); @@ -278,19 +315,19 @@ class MessageTest extends MediaWikiLangTestCase { * @covers Message::rawParams */ public function testRawParams() { - $this->assertEquals( + $this->assertSame( '(Заглавная страница)', wfMessage( 'parentheses', 'Заглавная страница' )->plain() ); - $this->assertEquals( + $this->assertSame( '(Заглавная страница $1)', wfMessage( 'parentheses', 'Заглавная страница $1' )->plain() ); - $this->assertEquals( + $this->assertSame( '(Заглавная страница)', wfMessage( 'parentheses' )->rawParams( 'Заглавная страница' )->plain() ); - $this->assertEquals( + $this->assertSame( '(Заглавная страница $1)', wfMessage( 'parentheses' )->rawParams( 'Заглавная страница $1' )->plain() ); @@ -302,8 +339,8 @@ class MessageTest extends MediaWikiLangTestCase { */ public function testRawMessage() { $msg = new RawMessage( 'example &' ); - $this->assertEquals( 'example &', $msg->plain() ); - $this->assertEquals( 'example &', $msg->escaped() ); + $this->assertSame( 'example &', $msg->plain() ); + $this->assertSame( 'example &', $msg->escaped() ); } /** @@ -315,7 +352,7 @@ class MessageTest extends MediaWikiLangTestCase { $msg = new RawMessage( '$1$2$3$4$5$6$7$8$9$10$11$12' ); // One less than above has placeholders $params = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k' ]; - $this->assertEquals( + $this->assertSame( 'abcdefghijka2', $msg->params( $params )->plain(), 'Params > 9 are replaced correctly' @@ -323,7 +360,7 @@ class MessageTest extends MediaWikiLangTestCase { $msg = new RawMessage( 'Params$*' ); $params = [ 'ab', 'bc', 'cd' ]; - $this->assertEquals( + $this->assertSame( 'Params: ab, bc, cd', $msg->params( $params )->text() ); @@ -337,7 +374,7 @@ class MessageTest extends MediaWikiLangTestCase { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); - $this->assertEquals( + $this->assertSame( $lang->formatNum( 123456.789 ), $msg->inLanguage( $lang )->numParams( 123456.789 )->plain(), 'numParams is handled correctly' @@ -352,7 +389,7 @@ class MessageTest extends MediaWikiLangTestCase { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); - $this->assertEquals( + $this->assertSame( $lang->formatDuration( 1234 ), $msg->inLanguage( $lang )->durationParams( 1234 )->plain(), 'durationParams is handled correctly' @@ -369,7 +406,7 @@ class MessageTest extends MediaWikiLangTestCase { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); - $this->assertEquals( + $this->assertSame( $lang->formatExpiry( wfTimestampNow() ), $msg->inLanguage( $lang )->expiryParams( wfTimestampNow() )->plain(), 'expiryParams is handled correctly' @@ -384,7 +421,7 @@ class MessageTest extends MediaWikiLangTestCase { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); - $this->assertEquals( + $this->assertSame( $lang->formatTimePeriod( 1234 ), $msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(), 'timeperiodParams is handled correctly' @@ -399,7 +436,7 @@ class MessageTest extends MediaWikiLangTestCase { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); - $this->assertEquals( + $this->assertSame( $lang->formatSize( 123456 ), $msg->inLanguage( $lang )->sizeParams( 123456 )->plain(), 'sizeParams is handled correctly' @@ -414,7 +451,7 @@ class MessageTest extends MediaWikiLangTestCase { $lang = Language::factory( 'en' ); $msg = new RawMessage( '$1' ); - $this->assertEquals( + $this->assertSame( $lang->formatBitrate( 123456 ), $msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(), 'bitrateParams is handled correctly' @@ -468,7 +505,7 @@ class MessageTest extends MediaWikiLangTestCase { 'one $2', '
    foo
    [[Bar]] {{Baz}} <', ]; - $this->assertEquals( + $this->assertSame( $expect, $msg->inLanguage( $lang )->plaintextParams( $params )->$format(), "Fail formatting for $format" @@ -509,7 +546,7 @@ class MessageTest extends MediaWikiLangTestCase { */ public function testParser( $expect, $format ) { $msg = new RawMessage( "''&'' " ); - $this->assertEquals( + $this->assertSame( $expect, $msg->inLanguage( 'en' )->$format() ); @@ -523,9 +560,9 @@ class MessageTest extends MediaWikiLangTestCase { // NOTE: make sure internal caching of the message text is reset appropriately $msg = wfMessage( 'mainpage' ); - $this->assertEquals( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" ); - $this->assertEquals( 'Main Page', $msg->inContentLanguage()->plain(), "inContentLanguage()" ); - $this->assertEquals( 'Accueil', $msg->inLanguage( 'fr' )->plain(), "inLanguage( 'fr' )" ); + $this->assertSame( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" ); + $this->assertSame( 'Main Page', $msg->inContentLanguage()->plain(), "inContentLanguage()" ); + $this->assertSame( 'Accueil', $msg->inLanguage( 'fr' )->plain(), "inLanguage( 'fr' )" ); } /** @@ -540,18 +577,18 @@ class MessageTest extends MediaWikiLangTestCase { // NOTE: make sure internal caching of the message text is reset appropriately. // NOTE: wgForceUIMsgAsContentMsg forces the messages *current* language to be used. $msg = wfMessage( 'mainpage' ); - $this->assertEquals( + $this->assertSame( 'Accueil', $msg->inContentLanguage()->plain(), 'inContentLanguage() with ForceUIMsg override enabled' ); - $this->assertEquals( 'Main Page', $msg->inLanguage( 'en' )->plain(), "inLanguage( 'en' )" ); - $this->assertEquals( + $this->assertSame( 'Main Page', $msg->inLanguage( 'en' )->plain(), "inLanguage( 'en' )" ); + $this->assertSame( 'Main Page', $msg->inContentLanguage()->plain(), 'inContentLanguage() with ForceUIMsg override enabled' ); - $this->assertEquals( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" ); + $this->assertSame( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" ); } /** @@ -570,18 +607,18 @@ class MessageTest extends MediaWikiLangTestCase { $msg = new Message( 'parentheses' ); $msg->rawParams( 'foo' ); $msg->title( Title::newFromText( 'Testing' ) ); - $this->assertEquals( '(foo)', $msg->parse(), 'Sanity check' ); + $this->assertSame( '(foo)', $msg->parse(), 'Sanity check' ); $msg = unserialize( serialize( $msg ) ); - $this->assertEquals( '(foo)', $msg->parse() ); + $this->assertSame( '(foo)', $msg->parse() ); $title = TestingAccessWrapper::newFromObject( $msg )->title; $this->assertInstanceOf( 'Title', $title ); - $this->assertEquals( 'Testing', $title->getFullText() ); + $this->assertSame( 'Testing', $title->getFullText() ); $msg = new Message( 'mainpage' ); $msg->inLanguage( 'de' ); - $this->assertEquals( 'Hauptseite', $msg->plain(), 'Sanity check' ); + $this->assertSame( 'Hauptseite', $msg->plain(), 'Sanity check' ); $msg = unserialize( serialize( $msg ) ); - $this->assertEquals( 'Hauptseite', $msg->plain() ); + $this->assertSame( 'Hauptseite', $msg->plain() ); } /** @@ -614,4 +651,3 @@ class MessageTest extends MediaWikiLangTestCase { ]; } } - diff --git a/tests/phpunit/includes/PagePropsTest.php b/tests/phpunit/includes/PagePropsTest.php index cc1708a2b4..29c9e228f2 100644 --- a/tests/phpunit/includes/PagePropsTest.php +++ b/tests/phpunit/includes/PagePropsTest.php @@ -266,11 +266,9 @@ class TestPageProps extends MediaWikiLangTestCase { } protected function setProperties( $pageID, $properties ) { - $rows = []; foreach ( $properties as $propertyName => $propertyValue ) { - $row = [ 'pp_page' => $pageID, 'pp_propname' => $propertyName, @@ -295,11 +293,9 @@ class TestPageProps extends MediaWikiLangTestCase { } protected function setProperty( $pageID, $propertyName, $propertyValue ) { - $properties = []; $properties[$propertyName] = $propertyValue; $this->setProperties( $pageID, $properties ); - } } diff --git a/tests/phpunit/includes/SampleTest.php b/tests/phpunit/includes/SampleTest.php index 7c313845f9..02935a5369 100644 --- a/tests/phpunit/includes/SampleTest.php +++ b/tests/phpunit/includes/SampleTest.php @@ -32,7 +32,7 @@ class TestSample extends MediaWikiLangTestCase { * they run. While MediaWiki isn't strictly an Agile Programming * project, you are encouraged to use the naming described under * "Agile Documentation" at - * http://www.phpunit.de/manual/3.4/en/other-uses-for-tests.html + * https://www.phpunit.de/manual/3.4/en/other-uses-for-tests.html */ public function testTitleObjectStringConversion() { $title = Title::newFromText( "text" ); @@ -45,7 +45,7 @@ class TestSample extends MediaWikiLangTestCase { /** * If you want to run a the same test with a variety of data, use a data provider. - * see: http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html + * see: https://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html */ public static function provideTitles() { return [ @@ -60,7 +60,7 @@ class TestSample extends MediaWikiLangTestCase { // @codingStandardsIgnoreStart Generic.Files.LineLength /** * @dataProvider provideTitles - * See http://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.dataProvider + * See https://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.dataProvider */ // @codingStandardsIgnoreEnd public function testCreateBasicListOfTitles( $titleName, $ns, $text ) { @@ -89,7 +89,7 @@ class TestSample extends MediaWikiLangTestCase { /** * @depends testSetUpMainPageTitleForNextTest - * See http://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.depends + * See https://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.depends */ public function testCheckMainPageTitleIsConsideredLocal( $title ) { $this->assertTrue( $title->isLocal() ); @@ -98,7 +98,7 @@ class TestSample extends MediaWikiLangTestCase { // @codingStandardsIgnoreStart Generic.Files.LineLength /** * @expectedException InvalidArgumentException - * See http://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.expectedException + * See https://phpunit.de/manual/3.7/en/appendixes.annotations.html#appendixes.annotations.expectedException */ // @codingStandardsIgnoreEnd public function testTitleObjectFromObject() { diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index c915b70e1d..12db1a198f 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -134,19 +134,19 @@ class SanitizerTest extends MediaWikiTestCase { 'Self-closing closing div' ], // Make sure special nested HTML5 semantics are not broken - // http://www.whatwg.org/html/text-level-semantics.html#the-kbd-element + // https://html.spec.whatwg.org/multipage/semantics.html#the-kbd-element [ 'Shift+F3', 'Shift+F3', 'Nested .' ], - // http://www.whatwg.org/html/text-level-semantics.html#the-sub-and-sup-elements + // https://html.spec.whatwg.org/multipage/semantics.html#the-sub-and-sup-elements [ 'xi, yi', 'xi, yi', 'Nested .' ], - // http://www.whatwg.org/html/text-level-semantics.html#the-dfn-element + // https://html.spec.whatwg.org/multipage/semantics.html#the-dfn-element [ 'GDO', 'GDO', diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php index 9359568375..152602ac5e 100644 --- a/tests/phpunit/includes/TemplateCategoriesTest.php +++ b/tests/phpunit/includes/TemplateCategoriesTest.php @@ -91,6 +91,5 @@ class TemplateCategoriesTest extends MediaWikiLangTestCase { $title->getParentCategories(), 'Verify that the page is no longer in the category after template deletion' ); - } } diff --git a/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php b/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php index 92446ed950..93687df2d5 100644 --- a/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php +++ b/tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php @@ -180,7 +180,9 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { '(rc_this_oldid=page_latest) OR (rc_type=3)', ], $this->isType( 'string' ), - [], + [ + 'LIMIT' => 3, + ], [ 'watchlist' => [ 'INNER JOIN', @@ -214,12 +216,184 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { 'rc_deleted' => 0, 'wl_notificationtimestamp' => null, ] ), + $this->getFakeRow( [ + 'rc_id' => 3, + 'rc_namespace' => 1, + 'rc_title' => 'Foo3', + 'rc_timestamp' => '20151212010103', + 'rc_type' => RC_NEW, + 'rc_deleted' => 0, + 'wl_notificationtimestamp' => null, + ] ), ] ) ); $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) ); $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 ); - $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user ); + $startFrom = null; + $items = $queryService->getWatchedItemsWithRecentChangeInfo( + $user, [ 'limit' => 2 ], $startFrom + ); + + $this->assertInternalType( 'array', $items ); + $this->assertCount( 2, $items ); + + foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) { + $this->assertInstanceOf( WatchedItem::class, $watchedItem ); + $this->assertInternalType( 'array', $recentChangeInfo ); + } + + $this->assertEquals( + new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ), + $items[0][0] + ); + $this->assertEquals( + [ + 'rc_id' => 1, + 'rc_namespace' => 0, + 'rc_title' => 'Foo1', + 'rc_timestamp' => '20151212010101', + 'rc_type' => RC_NEW, + 'rc_deleted' => 0, + ], + $items[0][1] + ); + + $this->assertEquals( + new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ), + $items[1][0] + ); + $this->assertEquals( + [ + 'rc_id' => 2, + 'rc_namespace' => 1, + 'rc_title' => 'Foo2', + 'rc_timestamp' => '20151212010102', + 'rc_type' => RC_NEW, + 'rc_deleted' => 0, + ], + $items[1][1] + ); + + $this->assertEquals( [ '20151212010103', 3 ], $startFrom ); + } + + public function testGetWatchedItemsWithRecentChangeInfo_extension() { + $mockDb = $this->getMockDb(); + $mockDb->expects( $this->once() ) + ->method( 'select' ) + ->with( + [ 'recentchanges', 'watchlist', 'page', 'extension_dummy_table' ], + [ + 'rc_id', + 'rc_namespace', + 'rc_title', + 'rc_timestamp', + 'rc_type', + 'rc_deleted', + 'wl_notificationtimestamp', + 'rc_cur_id', + 'rc_this_oldid', + 'rc_last_oldid', + 'extension_dummy_field', + ], + [ + 'wl_user' => 1, + '(rc_this_oldid=page_latest) OR (rc_type=3)', + 'extension_dummy_cond', + ], + $this->isType( 'string' ), + [ + 'extension_dummy_option', + ], + [ + 'watchlist' => [ + 'INNER JOIN', + [ + 'wl_namespace=rc_namespace', + 'wl_title=rc_title' + ] + ], + 'page' => [ + 'LEFT JOIN', + 'rc_cur_id=page_id', + ], + 'extension_dummy_join_cond' => [], + ] + ) + ->will( $this->returnValue( [ + $this->getFakeRow( [ + 'rc_id' => 1, + 'rc_namespace' => 0, + 'rc_title' => 'Foo1', + 'rc_timestamp' => '20151212010101', + 'rc_type' => RC_NEW, + 'rc_deleted' => 0, + 'wl_notificationtimestamp' => '20151212010101', + ] ), + $this->getFakeRow( [ + 'rc_id' => 2, + 'rc_namespace' => 1, + 'rc_title' => 'Foo2', + 'rc_timestamp' => '20151212010102', + 'rc_type' => RC_NEW, + 'rc_deleted' => 0, + 'wl_notificationtimestamp' => null, + ] ), + ] ) ); + + $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 ); + + $mockExtension = $this->getMockBuilder( WatchedItemQueryServiceExtension::class ) + ->getMock(); + $mockExtension->expects( $this->once() ) + ->method( 'modifyWatchedItemsWithRCInfoQuery' ) + ->with( + $this->identicalTo( $user ), + $this->isType( 'array' ), + $this->isInstanceOf( IDatabase::class ), + $this->isType( 'array' ), + $this->isType( 'array' ), + $this->isType( 'array' ), + $this->isType( 'array' ), + $this->isType( 'array' ) + ) + ->will( $this->returnCallback( function ( + $user, $options, $db, &$tables, &$fields, &$conds, &$dbOptions, &$joinConds + ) { + $tables[] = 'extension_dummy_table'; + $fields[] = 'extension_dummy_field'; + $conds[] = 'extension_dummy_cond'; + $dbOptions[] = 'extension_dummy_option'; + $joinConds['extension_dummy_join_cond'] = []; + } ) ); + $mockExtension->expects( $this->once() ) + ->method( 'modifyWatchedItemsWithRCInfo' ) + ->with( + $this->identicalTo( $user ), + $this->isType( 'array' ), + $this->isInstanceOf( IDatabase::class ), + $this->isType( 'array' ), + $this->anything(), + $this->anything() // Can't test for null here, PHPUnit applies this after the callback + ) + ->will( $this->returnCallback( function ( $user, $options, $db, &$items, $res, &$startFrom ) { + foreach ( $items as $i => &$item ) { + $item[1]['extension_dummy_field'] = $i; + } + unset( $item ); + + $this->assertNull( $startFrom ); + $startFrom = [ '20160203123456', 42 ]; + } ) ); + + $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) ); + TestingAccessWrapper::newFromObject( $queryService )->extensions = [ $mockExtension ]; + + $startFrom = null; + $items = $queryService->getWatchedItemsWithRecentChangeInfo( + $user, [], $startFrom + ); $this->assertInternalType( 'array', $items ); $this->assertCount( 2, $items ); @@ -241,6 +415,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { 'rc_timestamp' => '20151212010101', 'rc_type' => RC_NEW, 'rc_deleted' => 0, + 'extension_dummy_field' => 0, ], $items[0][1] ); @@ -257,93 +432,110 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { 'rc_timestamp' => '20151212010102', 'rc_type' => RC_NEW, 'rc_deleted' => 0, + 'extension_dummy_field' => 1, ], $items[1][1] ); + + $this->assertEquals( [ '20160203123456', 42 ], $startFrom ); } public function getWatchedItemsWithRecentChangeInfoOptionsProvider() { return [ [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ], + null, [ 'rc_type', 'rc_minor', 'rc_bot' ], [], [], ], [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ], + null, [ 'rc_user_text' ], [], [], ], [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ], + null, [ 'rc_user' ], [], [], ], [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ], + null, [ 'rc_comment' ], [], [], ], [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ], + null, [ 'rc_patrolled', 'rc_log_type' ], [], [], ], [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ], + null, [ 'rc_old_len', 'rc_new_len' ], [], [], ], [ [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ], + null, [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ], [], [], ], [ [ 'namespaceIds' => [ 0, 1 ] ], + null, [], [ 'wl_namespace' => [ 0, 1 ] ], [], ], [ [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ], + null, [], [ 'wl_namespace' => [ 0, 1 ] ], [], ], [ [ 'rcTypes' => [ RC_EDIT, RC_NEW ] ], + null, [], [ 'rc_type' => [ RC_EDIT, RC_NEW ] ], [], ], [ [ 'dir' => WatchedItemQueryService::DIR_OLDER ], + null, [], [], [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ] ], [ [ 'dir' => WatchedItemQueryService::DIR_NEWER ], + null, [], [], [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ] ], [ [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ], + null, [], [ "rc_timestamp <= '20151212010101'" ], [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ] ], [ [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ], + null, [], [ "rc_timestamp >= '20151212010101'" ], [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ] @@ -354,18 +546,21 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { 'start' => '20151212020101', 'end' => '20151212010101' ], + null, [], [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ], [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ] ], [ [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ], + null, [], [ "rc_timestamp >= '20151212010101'" ], [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ] ], [ [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ], + null, [], [ "rc_timestamp <= '20151212010101'" ], [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ] @@ -376,96 +571,112 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { 'start' => '20151212010101', 'end' => '20151212020101' ], + null, [], [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ], [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ] ], [ [ 'limit' => 10 ], + null, [], [], - [ 'LIMIT' => 10 ], + [ 'LIMIT' => 11 ], ], [ [ 'limit' => "10; DROP TABLE watchlist;\n--" ], + null, [], [], - [ 'LIMIT' => 10 ], + [ 'LIMIT' => 11 ], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ], + null, [], [ 'rc_minor != 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ], + null, [], [ 'rc_minor = 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ], + null, [], [ 'rc_bot != 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ], + null, [], [ 'rc_bot = 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ], + null, [], [ 'rc_user = 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ], + null, [], [ 'rc_user != 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ], + null, [], [ 'rc_patrolled != 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ] ], + null, [], [ 'rc_patrolled = 0' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ], + null, [], [ 'rc_timestamp >= wl_notificationtimestamp' ], [], ], [ [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ], + null, [], [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ], [], ], [ [ 'onlyByUser' => 'SomeOtherUser' ], + null, [], [ 'rc_user_text' => 'SomeOtherUser' ], [], ], [ [ 'notByUser' => 'SomeOtherUser' ], + null, [], [ "rc_user_text != 'SomeOtherUser'" ], [], ], [ - [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService::DIR_OLDER ], + [ 'dir' => WatchedItemQueryService::DIR_OLDER ], + [ '20151212010101', 123 ], [], [ "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))" @@ -473,7 +684,8 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ], ], [ - [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService::DIR_NEWER ], + [ 'dir' => WatchedItemQueryService::DIR_NEWER ], + [ '20151212010101', 123 ], [], [ "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))" @@ -481,10 +693,8 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ], ], [ - [ - 'startFrom' => [ '20151212010101', "123; DROP TABLE watchlist;\n--" ], - 'dir' => WatchedItemQueryService::DIR_OLDER - ], + [ 'dir' => WatchedItemQueryService::DIR_OLDER ], + [ '20151212010101', "123; DROP TABLE watchlist;\n--" ], [], [ "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))" @@ -499,6 +709,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { */ public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult( array $options, + $startFrom, array $expectedExtraFields, array $expectedExtraConds, array $expectedDbOptions @@ -552,9 +763,10 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) ); $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 ); - $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options ); + $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom ); $this->assertEmpty( $items ); + $this->assertNull( $startFrom ); } public function filterPatrolledOptionProvider() { @@ -797,53 +1009,62 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { return [ [ [ 'rcTypes' => [ 1337 ] ], + null, 'Bad value for parameter $options[\'rcTypes\']', ], [ [ 'rcTypes' => [ 'edit' ] ], + null, 'Bad value for parameter $options[\'rcTypes\']', ], [ [ 'rcTypes' => [ RC_EDIT, 1337 ] ], + null, 'Bad value for parameter $options[\'rcTypes\']', ], [ [ 'dir' => 'foo' ], + null, 'Bad value for parameter $options[\'dir\']', ], [ [ 'start' => '20151212010101' ], + null, 'Bad value for parameter $options[\'dir\']: must be provided', ], [ [ 'end' => '20151212010101' ], + null, 'Bad value for parameter $options[\'dir\']: must be provided', ], [ - [ 'startFrom' => [ '20151212010101', 123 ] ], + [], + [ '20151212010101', 123 ], 'Bad value for parameter $options[\'dir\']: must be provided', ], [ - [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'startFrom' => '20151212010101' ], - 'Bad value for parameter $options[\'startFrom\']: must be a two-element array', + [ 'dir' => WatchedItemQueryService::DIR_OLDER ], + '20151212010101', + 'Bad value for parameter $startFrom: must be a two-element array', ], [ - [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'startFrom' => [ '20151212010101' ] ], - 'Bad value for parameter $options[\'startFrom\']: must be a two-element array', + [ 'dir' => WatchedItemQueryService::DIR_OLDER ], + [ '20151212010101' ], + 'Bad value for parameter $startFrom: must be a two-element array', ], [ - [ - 'dir' => WatchedItemQueryService::DIR_OLDER, - 'startFrom' => [ '20151212010101', 123, 'foo' ] - ], - 'Bad value for parameter $options[\'startFrom\']: must be a two-element array', + [ 'dir' => WatchedItemQueryService::DIR_OLDER ], + [ '20151212010101', 123, 'foo' ], + 'Bad value for parameter $startFrom: must be a two-element array', ], [ [ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ], + null, 'Bad value for parameter $options[\'watchlistOwnerToken\']', ], [ [ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ], + null, 'Bad value for parameter $options[\'watchlistOwner\']', ], ]; @@ -854,6 +1075,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { */ public function testGetWatchedItemsWithRecentChangeInfo_invalidOptions( array $options, + $startFrom, $expectedInExceptionMessage ) { $mockDb = $this->getMockDb(); @@ -864,7 +1086,7 @@ class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase { $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 ); $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage ); - $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options ); + $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom ); } public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult() { diff --git a/tests/phpunit/includes/api/ApiContinuationManagerTest.php b/tests/phpunit/includes/api/ApiContinuationManagerTest.php index 6da16a0931..3ad16d1322 100644 --- a/tests/phpunit/includes/api/ApiContinuationManagerTest.php +++ b/tests/phpunit/includes/api/ApiContinuationManagerTest.php @@ -195,7 +195,6 @@ class ApiContinuationManagerTest extends MediaWikiTestCase { 'Expected exception' ); } - } } diff --git a/tests/phpunit/includes/api/ApiMessageTest.php b/tests/phpunit/includes/api/ApiMessageTest.php index a45015a7b3..8764b4194f 100644 --- a/tests/phpunit/includes/api/ApiMessageTest.php +++ b/tests/phpunit/includes/api/ApiMessageTest.php @@ -5,17 +5,17 @@ */ class ApiMessageTest extends MediaWikiTestCase { - private function compareMessages( $msg, $msg2 ) { + private function compareMessages( Message $msg, Message $msg2 ) { $this->assertSame( $msg->getKey(), $msg2->getKey(), 'getKey' ); $this->assertSame( $msg->getKeysToTry(), $msg2->getKeysToTry(), 'getKeysToTry' ); $this->assertSame( $msg->getParams(), $msg2->getParams(), 'getParams' ); - $this->assertSame( $msg->getFormat(), $msg2->getFormat(), 'getFormat' ); $this->assertSame( $msg->getLanguage(), $msg2->getLanguage(), 'getLanguage' ); $msg = TestingAccessWrapper::newFromObject( $msg ); $msg2 = TestingAccessWrapper::newFromObject( $msg2 ); $this->assertSame( $msg->interface, $msg2->interface, 'interface' ); $this->assertSame( $msg->useDatabase, $msg2->useDatabase, 'useDatabase' ); + $this->assertSame( $msg->format, $msg2->format, 'format' ); $this->assertSame( $msg->title ? $msg->title->getFullText() : null, $msg2->title ? $msg2->title->getFullText() : null, diff --git a/tests/phpunit/includes/api/ApiResultTest.php b/tests/phpunit/includes/api/ApiResultTest.php index 48472cff91..98e24fb666 100644 --- a/tests/phpunit/includes/api/ApiResultTest.php +++ b/tests/phpunit/includes/api/ApiResultTest.php @@ -1230,7 +1230,6 @@ class ApiResultTest extends MediaWikiTestCase { ], ], ]; - } /** @@ -1380,7 +1379,6 @@ class ApiResultTest extends MediaWikiTestCase { 'two' => 2, ], $arr['foo'] ); } - } class ApiResultTestStringifiableObject { diff --git a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php index 6359983d6e..d8282be159 100644 --- a/tests/phpunit/includes/api/ApiRevisionDeleteTest.php +++ b/tests/phpunit/includes/api/ApiRevisionDeleteTest.php @@ -25,7 +25,6 @@ class ApiRevisionDeleteTest extends ApiTestCase { $this->revs[] = Title::newFromText( self::$page ) ->getLatestRevID( Title::GAID_FOR_UPDATE ); } - } public function testHidingRevisions() { diff --git a/tests/phpunit/includes/auth/AuthManagerTest.php b/tests/phpunit/includes/auth/AuthManagerTest.php index 13486e2c43..f57db11b23 100644 --- a/tests/phpunit/includes/auth/AuthManagerTest.php +++ b/tests/phpunit/includes/auth/AuthManagerTest.php @@ -695,7 +695,6 @@ class AuthManagerTest extends \MediaWikiTestCase { $ex->getMessage() ); } - } public function testBeginAuthentication() { @@ -3265,7 +3264,6 @@ class AuthManagerTest extends \MediaWikiTestCase { $this->manager->removeAuthenticationSessionData( null ); $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) ); $this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) ); - } public function testCanLinkAccounts() { diff --git a/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php index c52c3e9f54..ec4bea11a1 100644 --- a/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php +++ b/tests/phpunit/includes/auth/EmailNotificationSecondaryAuthenticationProviderTest.php @@ -104,6 +104,5 @@ class EmailNotificationSecondaryAuthenticationProviderTest extends \PHPUnit_Fram $authManager->setAuthenticationSessionData( 'no-email', true ); $provider->setManager( $authManager ); $provider->beginSecondaryAccountCreation( $userNotExpectsConfirmation, $creator, [] ); - } } diff --git a/tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php index 088dd00fe8..caf1680e3e 100644 --- a/tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php +++ b/tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php @@ -328,7 +328,6 @@ class LocalPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase AuthenticationResponse::newPass( $userName ), $provider->beginPrimaryAuthentication( $reqs ) ); - } /** @@ -645,7 +644,6 @@ class LocalPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase $this->assertNull( $provider->finishAccountCreation( $user, $user, $res2 ) ); $ret = $provider->beginPrimaryAuthentication( $reqs ); $this->assertEquals( AuthenticationResponse::PASS, $ret->status, 'new password is set' ); - } } diff --git a/tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php b/tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php index bc78c08f2c..d4ebe34a98 100644 --- a/tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php +++ b/tests/phpunit/includes/auth/TemporaryPasswordPrimaryAuthenticationProviderTest.php @@ -347,7 +347,6 @@ class TemporaryPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestC 'wrongpassword', $ret->message->getKey() ); - } /** diff --git a/tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php b/tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php index 7fe335117c..7dea123c12 100644 --- a/tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php +++ b/tests/phpunit/includes/auth/UserDataAuthenticationRequestTest.php @@ -36,7 +36,6 @@ class UserDataAuthenticationRequestTest extends AuthenticationRequestTestCase { $this->assertSame( $email ?: 'default@example.com', $user->getEmail() ); $this->assertSame( $realname ?: 'Fake Name', $user->getRealName() ); } - } public static function providePopulateUser() { diff --git a/tests/phpunit/includes/db/LBFactoryTest.php b/tests/phpunit/includes/db/LBFactoryTest.php index aed2d83a7d..d8773f8ad2 100644 --- a/tests/phpunit/includes/db/LBFactoryTest.php +++ b/tests/phpunit/includes/db/LBFactoryTest.php @@ -58,17 +58,18 @@ class LBFactoryTest extends MediaWikiTestCase { } public function testLBFactorySimpleServer() { - global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype; + global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir; $servers = [ [ - 'host' => $wgDBserver, - 'dbname' => $wgDBname, - 'user' => $wgDBuser, - 'password' => $wgDBpassword, - 'type' => $wgDBtype, - 'load' => 0, - 'flags' => DBO_TRX // REPEATABLE-READ for consistency + 'host' => $wgDBserver, + 'dbname' => $wgDBname, + 'user' => $wgDBuser, + 'password' => $wgDBpassword, + 'type' => $wgDBtype, + 'dbDirectory' => $wgSQLiteDataDir, + 'load' => 0, + 'flags' => DBO_TRX // REPEATABLE-READ for consistency ], ]; @@ -86,26 +87,28 @@ class LBFactoryTest extends MediaWikiTestCase { } public function testLBFactorySimpleServers() { - global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype; + global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir; $servers = [ [ // master - 'host' => $wgDBserver, - 'dbname' => $wgDBname, - 'user' => $wgDBuser, - 'password' => $wgDBpassword, - 'type' => $wgDBtype, - 'load' => 0, - 'flags' => DBO_TRX // REPEATABLE-READ for consistency + 'host' => $wgDBserver, + 'dbname' => $wgDBname, + 'user' => $wgDBuser, + 'password' => $wgDBpassword, + 'type' => $wgDBtype, + 'dbDirectory' => $wgSQLiteDataDir, + 'load' => 0, + 'flags' => DBO_TRX // REPEATABLE-READ for consistency ], [ // emulated slave - 'host' => $wgDBserver, - 'dbname' => $wgDBname, - 'user' => $wgDBuser, - 'password' => $wgDBpassword, - 'type' => $wgDBtype, - 'load' => 100, - 'flags' => DBO_TRX // REPEATABLE-READ for consistency + 'host' => $wgDBserver, + 'dbname' => $wgDBname, + 'user' => $wgDBuser, + 'password' => $wgDBpassword, + 'type' => $wgDBtype, + 'dbDirectory' => $wgSQLiteDataDir, + 'load' => 100, + 'flags' => DBO_TRX // REPEATABLE-READ for consistency ] ]; @@ -118,19 +121,23 @@ class LBFactoryTest extends MediaWikiTestCase { $dbw = $lb->getConnection( DB_MASTER ); $this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' ); $this->assertEquals( - $wgDBserver, $dbw->getLBInfo( 'clusterMasterHost' ), 'cluster master set' ); + ( $wgDBserver != '' ) ? $wgDBserver : 'localhost', + $dbw->getLBInfo( 'clusterMasterHost' ), + 'cluster master set' ); $dbr = $lb->getConnection( DB_SLAVE ); $this->assertTrue( $dbr->getLBInfo( 'replica' ), 'slave shows as slave' ); $this->assertEquals( - $wgDBserver, $dbr->getLBInfo( 'clusterMasterHost' ), 'cluster master set' ); + ( $wgDBserver != '' ) ? $wgDBserver : 'localhost', + $dbr->getLBInfo( 'clusterMasterHost' ), + 'cluster master set' ); $factory->shutdown(); $lb->closeAll(); } public function testLBFactoryMulti() { - global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype; + global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir; $factory = new LBFactoryMulti( [ 'sectionsByDB' => [], @@ -145,6 +152,7 @@ class LBFactoryTest extends MediaWikiTestCase { 'user' => $wgDBuser, 'password' => $wgDBpassword, 'type' => $wgDBtype, + 'dbDirectory' => $wgSQLiteDataDir, 'flags' => DBO_DEFAULT ], 'hostsByName' => [ @@ -233,7 +241,7 @@ class LBFactoryTest extends MediaWikiTestCase { } private function newLBFactoryMulti( array $baseOverride = [], array $serverOverride = [] ) { - global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype; + global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgSQLiteDataDir; return new LBFactoryMulti( $baseOverride + [ 'sectionsByDB' => [], @@ -247,6 +255,7 @@ class LBFactoryTest extends MediaWikiTestCase { 'user' => $wgDBuser, 'password' => $wgDBpassword, 'type' => $wgDBtype, + 'dbDirectory' => $wgSQLiteDataDir, 'flags' => DBO_DEFAULT ], 'hostsByName' => [ @@ -258,17 +267,32 @@ class LBFactoryTest extends MediaWikiTestCase { } public function testNiceDomains() { - global $wgDBname; + global $wgDBname, $wgDBtype; + + if ( $wgDBtype === 'sqlite' ) { + $tmpDir = $this->getNewTempDirectory(); + $dbPath = "$tmpDir/unit_test_db.sqlite"; + file_put_contents( $dbPath, '' ); + $tempFsFile = new TempFSFile( $dbPath ); + $tempFsFile->autocollect(); + } else { + $dbPath = null; + } - $factory = $this->newLBFactoryMulti(); + $factory = $this->newLBFactoryMulti( + [], + [ 'dbFilePath' => $dbPath ] + ); $lb = $factory->getMainLB(); - $db = $lb->getConnectionRef( DB_MASTER ); - $this->assertEquals( - $wgDBname, - $db->getDomainID() - ); - unset( $db ); + if ( $wgDBtype !== 'sqlite' ) { + $db = $lb->getConnectionRef( DB_MASTER ); + $this->assertEquals( + $wgDBname, + $db->getDomainID() + ); + unset( $db ); + } /** @var Database $db */ $db = $lb->getConnection( DB_MASTER, [], '' ); @@ -280,19 +304,19 @@ class LBFactoryTest extends MediaWikiTestCase { ); $this->assertEquals( - $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'page' ), $db->tableName( 'page' ), "Correct full table name" ); $this->assertEquals( - $db->addIdentifierQuotes( $wgDBname ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, $wgDBname ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( "$wgDBname.page" ), "Correct full table name" ); $this->assertEquals( - $db->addIdentifierQuotes( 'nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( 'nice_db.page' ), "Correct full table name" ); @@ -303,12 +327,12 @@ class LBFactoryTest extends MediaWikiTestCase { $db->getDomainID() ); $this->assertEquals( - $db->addIdentifierQuotes( 'my_page' ), + $this->quoteTable( $db, 'my_page' ), $db->tableName( 'page' ), "Correct full table name" ); $this->assertEquals( - $db->addIdentifierQuotes( 'other_nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( 'other_nice_db.page' ), "Correct full table name" ); @@ -318,9 +342,23 @@ class LBFactoryTest extends MediaWikiTestCase { } public function testTrickyDomain() { + global $wgDBtype; + + if ( $wgDBtype === 'sqlite' ) { + $tmpDir = $this->getNewTempDirectory(); + $dbPath = "$tmpDir/unit_test_db.sqlite"; + file_put_contents( $dbPath, '' ); + $tempFsFile = new TempFSFile( $dbPath ); + $tempFsFile->autocollect(); + } else { + $dbPath = null; + } + $dbname = 'unittest-domain'; $factory = $this->newLBFactoryMulti( - [ 'localDomain' => $dbname ], [ 'dbname' => $dbname ] ); + [ 'localDomain' => $dbname ], + [ 'dbname' => $dbname, 'dbFilePath' => $dbPath ] + ); $lb = $factory->getMainLB(); /** @var Database $db */ $db = $lb->getConnection( DB_MASTER, [], '' ); @@ -332,19 +370,19 @@ class LBFactoryTest extends MediaWikiTestCase { ); $this->assertEquals( - $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'page' ), $db->tableName( 'page' ), "Correct full table name" ); $this->assertEquals( - $db->addIdentifierQuotes( $dbname ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, $dbname ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( "$dbname.page" ), "Correct full table name" ); $this->assertEquals( - $db->addIdentifierQuotes( 'nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'nice_db' ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( 'nice_db.page' ), "Correct full table name" ); @@ -352,12 +390,12 @@ class LBFactoryTest extends MediaWikiTestCase { $factory->setDomainPrefix( 'my_' ); $this->assertEquals( - $db->addIdentifierQuotes( 'my_page' ), + $this->quoteTable( $db, 'my_page' ), $db->tableName( 'page' ), "Correct full table name" ); $this->assertEquals( - $db->addIdentifierQuotes( 'other_nice_db' ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'other_nice_db' ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( 'other_nice_db.page' ), "Correct full table name" ); @@ -367,7 +405,7 @@ class LBFactoryTest extends MediaWikiTestCase { \MediaWiki\restoreWarnings(); $this->assertEquals( - $db->addIdentifierQuotes( 'garbage-db' ) . '.' . $db->addIdentifierQuotes( 'page' ), + $this->quoteTable( $db, 'garbage-db' ) . '.' . $this->quoteTable( $db, 'page' ), $db->tableName( 'garbage-db.page' ), "Correct full table name" ); @@ -375,4 +413,12 @@ class LBFactoryTest extends MediaWikiTestCase { $factory->closeAll(); $factory->destroy(); } + + private function quoteTable( Database $db, $table ) { + if ( $db->getType() === 'sqlite' ) { + return $table; + } else { + return $db->addIdentifierQuotes( $table ); + } + } } diff --git a/tests/phpunit/includes/exception/MWExceptionTest.php b/tests/phpunit/includes/exception/MWExceptionTest.php index 0e87ffa42f..7c36f7d13d 100644 --- a/tests/phpunit/includes/exception/MWExceptionTest.php +++ b/tests/phpunit/includes/exception/MWExceptionTest.php @@ -173,7 +173,6 @@ class MWExceptionTest extends MediaWikiTestCase { * @dataProvider provideJsonSerializedKeys */ public function testJsonserializeexceptionKeys( $expectedKeyType, $exClass, $key ) { - # Make sure we log a backtrace: $this->setMwGlobals( [ 'wgLogExceptionBacktrace' => true ] ); @@ -235,7 +234,6 @@ class MWExceptionTest extends MediaWikiTestCase { MWExceptionHandler::jsonSerializeException( new Exception() ) ); $this->assertObjectNotHasAttribute( 'backtrace', $json ); - } } diff --git a/tests/phpunit/includes/installer/DatabaseUpdaterTest.php b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php index 22d52f0744..2a75cf4020 100644 --- a/tests/phpunit/includes/installer/DatabaseUpdaterTest.php +++ b/tests/phpunit/includes/installer/DatabaseUpdaterTest.php @@ -103,7 +103,7 @@ class FakeDatabase extends Database { /** * Get the number of fields in a result object - * @see http://www.php.net/mysql_num_fields + * @see https://secure.php.net/mysql_num_fields * * @param mixed $res A SQL result * @return int @@ -114,7 +114,7 @@ class FakeDatabase extends Database { /** * Get a field name in a result object - * @see http://www.php.net/mysql_field_name + * @see https://secure.php.net/mysql_field_name * * @param mixed $res A SQL result * @param int $n @@ -142,7 +142,7 @@ class FakeDatabase extends Database { /** * Change the position of the cursor in a result object - * @see http://www.php.net/mysql_data_seek + * @see https://secure.php.net/mysql_data_seek * * @param mixed $res A SQL result * @param int $row @@ -153,7 +153,7 @@ class FakeDatabase extends Database { /** * Get the last error number - * @see http://www.php.net/mysql_errno + * @see https://secure.php.net/mysql_errno * * @return int */ @@ -163,7 +163,7 @@ class FakeDatabase extends Database { /** * Get a description of the last error - * @see http://www.php.net/mysql_error + * @see https://secure.php.net/mysql_error * * @return string */ @@ -197,7 +197,7 @@ class FakeDatabase extends Database { /** * Get the number of rows affected by the last write query - * @see http://www.php.net/mysql_affected_rows + * @see https://secure.php.net/mysql_affected_rows * * @return int */ @@ -217,7 +217,7 @@ class FakeDatabase extends Database { /** * Returns a wikitext link to the DB's website, e.g., - * return "[http://www.mysql.com/ MySQL]"; + * return "[https://www.mysql.com/ MySQL]"; * Should at least contain plain text, if for some reason * your database has no website. * diff --git a/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php b/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php new file mode 100644 index 0000000000..4754b040f9 --- /dev/null +++ b/tests/phpunit/includes/interwiki/InterwikiLookupAdapterTest.php @@ -0,0 +1,117 @@ +interwikiLookup = new InterwikiLookupAdapter( + $this->getSiteLookup( $this->getSites() ) + ); + } + + public function testIsValidInterwiki() { + $this->assertTrue( + $this->interwikiLookup->isValidInterwiki( 'enwt' ), + 'enwt known prefix is valid' + ); + $this->assertTrue( + $this->interwikiLookup->isValidInterwiki( 'foo' ), + 'foo site known prefix is valid' + ); + $this->assertFalse( + $this->interwikiLookup->isValidInterwiki( 'xyz' ), + 'unknown prefix is not valid' + ); + } + + public function testFetch() { + + $interwiki = $this->interwikiLookup->fetch( '' ); + $this->assertNull( $interwiki ); + + $interwiki = $this->interwikiLookup->fetch( 'xyz' ); + $this->assertFalse( $interwiki ); + + $interwiki = $this->interwikiLookup->fetch( 'foo' ); + $this->assertInstanceOf( Interwiki::class, $interwiki ); + $this->assertSame( 'foobar', $interwiki->getWikiID() ); + + $interwiki = $this->interwikiLookup->fetch( 'enwt' ); + $this->assertInstanceOf( Interwiki::class, $interwiki ); + + $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' ); + $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' ); + $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' ); + $this->assertTrue( $interwiki->isLocal(), 'isLocal' ); + } + + public function testGetAllPrefixes() { + $this->assertEquals( + [ 'foo', 'enwt' ], + $this->interwikiLookup->getAllPrefixes(), + 'getAllPrefixes()' + ); + + $this->assertEquals( + [ 'foo' ], + $this->interwikiLookup->getAllPrefixes( false ), + 'get external prefixes' + ); + + $this->assertEquals( + [ 'enwt' ], + $this->interwikiLookup->getAllPrefixes( true ), + 'get local prefixes' + ); + } + + private function getSiteLookup( SiteList $sites ) { + $siteLookup = $this->getMockBuilder( SiteLookup::class ) + ->disableOriginalConstructor() + ->getMock(); + + $siteLookup->expects( $this->any() ) + ->method( 'getSites' ) + ->will( $this->returnValue( $sites ) ); + + return $siteLookup; + } + + private function getSites() { + $sites = []; + + $site = new Site(); + $site->setGlobalId( 'foobar' ); + $site->addInterwikiId( 'foo' ); + $site->setSource( 'external' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'enwiktionary' ); + $site->addInterwikiId( 'enwt' ); + $site->setSource( 'local' ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" ); + $sites[] = $site; + + return new SiteList( $sites ); + } + +} diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php index 01b575ca93..d252c80732 100644 --- a/tests/phpunit/includes/json/FormatJsonTest.php +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -146,7 +146,7 @@ class FormatJsonTest extends MediaWikiTestCase { * @return stdClass|string|bool|int|float|null */ public static function toObject( $value ) { - return !is_array( $value ) ? $value : (object) array_map( __METHOD__, $value ); + return !is_array( $value ) ? $value : (object)array_map( __METHOD__, $value ); } /** diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index 5f5a1e8f01..366714b193 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -129,8 +129,8 @@ class CSSMinTest extends MediaWikiTestCase { * @covers CSSMin::remap */ public function testRemapRemapping( $message, $input, $expectedOutput ) { - $localPath = __DIR__ . '/../../data/cssmin/'; - $remotePath = 'http://localhost/w/'; + $localPath = __DIR__ . '/../../data/cssmin'; + $remotePath = 'http://localhost/w'; $realOutput = CSSMin::remap( $input, $localPath, $remotePath ); $this->assertEquals( $expectedOutput, $realOutput, "CSSMin::remap: $message" ); diff --git a/tests/phpunit/includes/linker/LinkRendererTest.php b/tests/phpunit/includes/linker/LinkRendererTest.php index 70c0ece215..6d096c208b 100644 --- a/tests/phpunit/includes/linker/LinkRendererTest.php +++ b/tests/phpunit/includes/linker/LinkRendererTest.php @@ -24,7 +24,6 @@ class LinkRendererTest extends MediaWikiLangTestCase { 'wgScript' => '/w/index.php', ] ); $this->factory = MediaWikiServices::getInstance()->getLinkRendererFactory(); - } public function testMergeAttribs() { diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index f70b42de42..47ed67bdb1 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -17,7 +17,6 @@ class ExifBitmapTest extends MediaWikiMediaTestCase { $this->setMwGlobals( 'wgShowEXIF', true ); $this->handler = new ExifBitmapHandler; - } /** diff --git a/tests/phpunit/includes/session/SessionProviderTest.php b/tests/phpunit/includes/session/SessionProviderTest.php index 4cbeeb954c..8284d05e97 100644 --- a/tests/phpunit/includes/session/SessionProviderTest.php +++ b/tests/phpunit/includes/session/SessionProviderTest.php @@ -138,7 +138,6 @@ class SessionProviderTest extends MediaWikiTestCase { $ex->getMessage() ); } - } public function testHashToSessionId() { diff --git a/tests/phpunit/includes/session/SessionTest.php b/tests/phpunit/includes/session/SessionTest.php index 3815416c31..e6a6ad351f 100644 --- a/tests/phpunit/includes/session/SessionTest.php +++ b/tests/phpunit/includes/session/SessionTest.php @@ -299,7 +299,6 @@ class SessionTest extends MediaWikiTestCase { $session->resetAllTokens(); $this->assertArrayNotHasKey( 'wsTokenSecrets', $backend->data ); - } /** diff --git a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php index 145ffb3547..f79f6e48c5 100644 --- a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php +++ b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php @@ -57,17 +57,19 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $specialPageTestHelper = new SpecialPageTestHelper(); return [ - 'class name' => [ 'SpecialAllPages' ], + 'class name' => [ 'SpecialAllPages', false ], 'closure' => [ function () { return new SpecialAllPages(); - } ], - 'function' => [ [ $this, 'newSpecialAllPages' ] ], - 'callback string' => [ 'SpecialPageTestHelper::newSpecialAllPages' ], + }, false ], + 'function' => [ [ $this, 'newSpecialAllPages' ], false ], + 'callback string' => [ 'SpecialPageTestHelper::newSpecialAllPages', false ], 'callback with object' => [ - [ $specialPageTestHelper, 'newSpecialAllPages' ] + [ $specialPageTestHelper, 'newSpecialAllPages' ], + false ], 'callback array' => [ - [ 'SpecialPageTestHelper', 'newSpecialAllPages' ] + [ 'SpecialPageTestHelper', 'newSpecialAllPages' ], + false ] ]; } @@ -76,7 +78,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { * @covers SpecialPageFactory::getPage * @dataProvider specialPageProvider */ - public function testGetPage( $spec ) { + public function testGetPage( $spec, $shouldReuseInstance ) { $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => $spec ] ); SpecialPageFactory::resetList(); @@ -84,7 +86,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase { $this->assertInstanceOf( 'SpecialPage', $page ); $page2 = SpecialPageFactory::getPage( 'testdummy' ); - $this->assertEquals( true, $page2 === $page, "Should re-use instance:" ); + $this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" ); } /** diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php index 061e59823a..c1083afdbc 100644 --- a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php +++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php @@ -22,7 +22,7 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { * Pages whose query use the same DB table more than once. * This is used to skip testing those pages when run against a MySQL backend * which does not support reopening a temporary table. See upstream bug: - * http://bugs.mysql.com/bug.php?id=10327 + * https://bugs.mysql.com/bug.php?id=10327 */ protected $reopensTempTable = [ 'BrokenRedirects', @@ -51,7 +51,7 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { foreach ( $this->queryPages as $page ) { // With MySQL, skips special pages reopening a temporary table - // See http://bugs.mysql.com/bug.php?id=10327 + // See https://bugs.mysql.com/bug.php?id=10327 if ( $wgDBtype === 'mysql' && in_array( $page->getName(), $this->reopensTempTable ) diff --git a/tests/phpunit/includes/upload/UploadBaseTest.php b/tests/phpunit/includes/upload/UploadBaseTest.php index a44926b208..6be272fb61 100644 --- a/tests/phpunit/includes/upload/UploadBaseTest.php +++ b/tests/phpunit/includes/upload/UploadBaseTest.php @@ -349,7 +349,7 @@ class UploadBaseTest extends MediaWikiTestCase { ], [ // This currently doesn't seem to work in any browsers, but in case - // http://www.w3.org/TR/css3-images/ is implemented for SVG files + // https://www.w3.org/TR/css3-images/ is implemented for SVG files ' ', true, true, diff --git a/tests/phpunit/includes/user/UserTest.php b/tests/phpunit/includes/user/UserTest.php index 34548c0481..199fc8f1bc 100644 --- a/tests/phpunit/includes/user/UserTest.php +++ b/tests/phpunit/includes/user/UserTest.php @@ -269,7 +269,6 @@ class UserTest extends MediaWikiTestCase { // let the user have a few (3) edits $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) ); for ( $i = 0; $i < 3; $i++ ) { - $page->doEditContent( ContentHandler::makeContent( (string)$i, $page->getTitle() ), 'test', @@ -505,7 +504,6 @@ class UserTest extends MediaWikiTestCase { public function testGetId() { $user = static::getTestUser()->getUser(); $this->assertTrue( $user->getId() > 0 ); - } /** diff --git a/tests/phpunit/includes/utils/BatchRowUpdateTest.php b/tests/phpunit/includes/utils/BatchRowUpdateTest.php index ce6894e04b..cb1b3d2af6 100644 --- a/tests/phpunit/includes/utils/BatchRowUpdateTest.php +++ b/tests/phpunit/includes/utils/BatchRowUpdateTest.php @@ -81,7 +81,7 @@ class BatchRowUpdateTest extends MediaWikiTestCase { */ public function testReaderGetPrimaryKey( $message, array $expected, array $row ) { $reader = new BatchRowIterator( $this->mockDb(), 'some_table', array_keys( $expected ), 8675309 ); - $this->assertEquals( $expected, $reader->extractPrimaryKeys( (object) $row ), $message ); + $this->assertEquals( $expected, $reader->extractPrimaryKeys( (object)$row ), $message ); } public static function provider_readerSetFetchColumns() { @@ -226,7 +226,7 @@ class BatchRowUpdateTest extends MediaWikiTestCase { for ( $i = 0; $i < $numRows; $i += $batchSize ) { $rows = []; for ( $j = 0; $j < $batchSize && $i + $j < $numRows; $j++ ) { - $rows [] = (object) call_user_func( $rowGenerator ); + $rows [] = (object)call_user_func( $rowGenerator ); } $res[] = $rows; } diff --git a/tests/phpunit/structure/ApiDocumentationTest.php b/tests/phpunit/structure/ApiDocumentationTest.php index 2049e38ba0..bc5a6bd6c7 100644 --- a/tests/phpunit/structure/ApiDocumentationTest.php +++ b/tests/phpunit/structure/ApiDocumentationTest.php @@ -137,6 +137,8 @@ class ApiDocumentationTest extends MediaWikiTestCase { // Messages for examples. foreach ( $module->getExamplesMessages() as $qs => $msg ) { + $this->assertStringStartsNotWith( 'api.php?', $qs, + "Query string must not begin with 'api.php?'" ); $this->checkMessage( $msg, "Example $qs" ); } } diff --git a/tests/phpunit/structure/ExtensionJsonValidationTest.php b/tests/phpunit/structure/ExtensionJsonValidationTest.php index ad61284120..e11fd8a548 100644 --- a/tests/phpunit/structure/ExtensionJsonValidationTest.php +++ b/tests/phpunit/structure/ExtensionJsonValidationTest.php @@ -92,7 +92,7 @@ class ExtensionJsonValidationTest extends PHPUnit_Framework_TestCase { } $validator = new Validator; - $validator->check( $data, (object) [ '$ref' => 'file://' . $schemaPath ] ); + $validator->check( $data, (object)[ '$ref' => 'file://' . $schemaPath ] ); if ( $validator->isValid() && !$licenseError ) { // All good. $this->assertTrue( true ); diff --git a/tests/phpunit/structure/ResourcesTest.php b/tests/phpunit/structure/ResourcesTest.php index 86ce53f339..2e6bf37db8 100644 --- a/tests/phpunit/structure/ResourcesTest.php +++ b/tests/phpunit/structure/ResourcesTest.php @@ -91,7 +91,6 @@ class ResourcesTest extends MediaWikiTestCase { */ public function testMissingMessages() { $data = self::getAllModules(); - $validDeps = array_keys( $data['modules'] ); $lang = Language::factory( 'en' ); /** @var ResourceLoaderModule $module */ @@ -114,7 +113,6 @@ class ResourcesTest extends MediaWikiTestCase { */ public function testUnsatisfiableDependencies() { $data = self::getAllModules(); - $validDeps = array_keys( $data['modules'] ); /** @var ResourceLoaderModule $module */ foreach ( $data['modules'] as $moduleName => $module ) { diff --git a/tests/phpunit/suites/ParserTestTopLevelSuite.php b/tests/phpunit/suites/ParserTestTopLevelSuite.php index 5a7fc4804f..5d5d693571 100644 --- a/tests/phpunit/suites/ParserTestTopLevelSuite.php +++ b/tests/phpunit/suites/ParserTestTopLevelSuite.php @@ -108,7 +108,7 @@ class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite { $testsName = $extensionName . '__' . basename( $fileName, '.txt' ); $parserTestClassName = ucfirst( $testsName ); - // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php + // Official spec for class names: https://secure.php.net/manual/en/language.oop5.basic.php // Prepend 'ParserTest_' to be paranoid about it not starting with a number $parserTestClassName = 'ParserTest_' . preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index 1518a80cad..6ee49013c6 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -239,9 +239,7 @@ goodbye = mw.message( 'goodbye' ); assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); - assertMultipleFormats( [ 'goodbye' ], [ 'plain', 'text' ], '', 'Message.toString returns if key does not exist' ); - // bug 30684 - assertMultipleFormats( [ 'goodbye' ], [ 'parse', 'escaped' ], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); + assertMultipleFormats( [ 'good<>bye' ], [ 'plain', 'text', 'parse', 'escaped' ], '⧼good<>bye⧽', 'Message.toString returns ⧽key⧽ if key does not exist' ); assert.ok( mw.messages.set( 'plural-test-msg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); assertMultipleFormats( [ 'plural-test-msg', 6 ], [ 'text', 'parse', 'escaped' ], 'There are 6 results', 'plural get resolved' ); @@ -320,7 +318,7 @@ QUnit.test( 'mw.msg', 14, function ( assert ) { assert.ok( mw.messages.set( 'hello', 'Hello awesome world' ), 'mw.messages.set: Register' ); assert.equal( mw.msg( 'hello' ), 'Hello awesome world', 'Gets message with default options (existing message)' ); - assert.equal( mw.msg( 'goodbye' ), '', 'Gets message with default options (nonexistent message)' ); + assert.equal( mw.msg( 'goodbye' ), '⧼goodbye⧽', 'Gets message with default options (nonexistent message)' ); assert.ok( mw.messages.set( 'plural-item', 'Found $1 {{PLURAL:$1|item|items}}' ), 'mw.messages.set: Register' ); assert.equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 4eac362038..6dd17f11e1 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -136,7 +136,7 @@ } ); } ); - QUnit.test( 'getUrl', 13, function ( assert ) { + QUnit.test( 'getUrl', 14, function ( assert ) { var href; mw.config.set( { wgScript: '/w/index.php', @@ -150,6 +150,10 @@ href = mw.util.getUrl( 'Foo:Sandbox? 5+5=10! (test)/sub ' ); assert.equal( href, '/wiki/Foo:Sandbox%3F_5%2B5%3D10!_(test)/sub_', 'complex title' ); + // T149767 + href = mw.util.getUrl( 'My$$test$$$$$title' ); + assert.equal( href, '/wiki/My$$test$$$$$title', 'title with multiple consecutive dollar signs' ); + href = mw.util.getUrl(); assert.equal( href, '/wiki/Foobar', 'default title' ); diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js index 2934b39f3c..045b633695 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -10,6 +10,7 @@ 'Mozilla/5.0 (Windows NT 6.1.1; rv:5.0) Gecko/20100101 Firefox/5.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0', 'Mozilla/5.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4) Gecko/2012010317 Firefox/10.0a4', + 'Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0', 'Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0', 'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1', // Kindle Fire @@ -46,6 +47,8 @@ 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3', // Android 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17', + // UC Mini (speed mode off) + 'Mozilla/5.0 (Linux; U; Android 6.0.1; en-US; Nexus_5 Build/MMB29S) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 UCBrowser/10.7.6.805 Mobile', /* Grade C */ @@ -99,12 +102,18 @@ 'Wget/1.10.1 (Red Hat modified)', // Unknown 'I\'m an unknown browser', + 'I\'m an unknown Glass browser', // Empty '' ], blacklisted: [ /* Grade C */ + // PlayStation + 'Mozilla/5.0 (PLAYSTATION 3; 1.10)', + 'Mozilla/5.0 (PLAYSTATION 3; 3.55)', + 'Mozilla/5.0 (PLAYSTATION 3 4.21) AppleWebKit/531.22.8 (KHTML, like Gecko)', + 'Mozilla/5.0 (PlayStation 4 1.70) AppleWebKit/536.26 (KHTML, like Gecko)', // Open WebOS < 1.5 (Palm Pre, Palm Pixi) 'Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0', 'Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pixi/1.1 ', @@ -128,7 +137,9 @@ // Google Glass 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Glass 1 Build/IMM76L; XE11) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', // MeeGo - 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13' + 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13', + // UC Mini (speed mode on) + 'Mozilla/5.0 (X11; U; Linux i686; zh-CN; r:1.2.3.4) Gecko/' ] };