From: jenkins-bot Date: Wed, 16 Nov 2016 02:46:31 +0000 (+0000) Subject: Merge "Make NumericUppercaseCollation use localized digit transforms" X-Git-Tag: 1.31.0-rc.0~4850 X-Git-Url: http://git.cyclocoop.org/%22.%20generer_url_ecrire%28%22sites_tous%22%2C%22%22%29.%20%22?a=commitdiff_plain;h=ea42d90053b36cef47f318a1d50c18dfafc6b7b8;hp=e7464f34818f20377ca73fab72c7b3214e0f5e1c;p=lhc%2Fweb%2Fwiklou.git Merge "Make NumericUppercaseCollation use localized digit transforms" --- diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..135d3b95f3 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "extends": "wikimedia", + "env": { + "browser": true, + "jquery": true, + "qunit": true + }, + "globals": { + "require": false, + "module": false, + "mediaWiki": false, + "mwPerformance": false, + "OO": false + }, + "rules": { + "dot-notation": 0, + "valid-jsdoc": 0 + } +} diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 3f7e90d209..0000000000 --- a/.jscsrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "preset": "wikimedia", - "es3": true, - - "requireVarDeclFirst": null, - - "requireDotNotation": { "allExcept": [ "keywords" ] }, - "jsDoc": { - "checkAnnotations": { - "preset": "jsduck5", - "extra": { - "context": "some", - "see": "some" - } - }, - "checkParamNames": true, - "checkRedundantAccess": true, - "checkRedundantReturns": true, - "checkTypes": "strictNativeCase", - "requireNewlineAfterDescription": true, - "requireParamTypes": true, - "requireReturnTypes": true - }, - - "excludeFiles": [ - "docs/**", - "extensions/**", - "node_modules/**", - "resources/lib/**", - "resources/src/jquery.tipsy/**", - "resources/src/jquery/jquery.farbtastic.js", - "resources/src/mediawiki.libs/**", - "skins/**", - "vendor/**" - ] -} diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index fdde7d054d..0000000000 --- a/.jshintignore +++ /dev/null @@ -1,12 +0,0 @@ -# Generated documentation -docs/** - -# third-party libs -extensions/** -node_modules/** -resources/lib/** -resources/src/jquery.tipsy/** -resources/src/jquery/jquery.farbtastic.js -resources/src/mediawiki.libs/** -skins/** -vendor/** diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 441c4e310b..0000000000 --- a/.jshintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - // Enforcing - "bitwise": true, - "eqeqeq": true, - "esversion": 3, - "freeze": true, - "futurehostile": true, - "latedef": "nofunc", - "noarg": true, - "nonew": true, - "strict": false, - "undef": true, - "unused": true, - - // Relaxing - "laxbreak": true, - "multistr": true, - - // Environment - "browser": true, - - "globals": { - "require": false, - "module": false, - "mediaWiki": true, - "JSON": true, - "OO": true, - "mwPerformance": true, - "jQuery": false, - "QUnit": false, - "sinon": false - } -} 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/Gruntfile.js b/Gruntfile.js index a08db5c780..b38bc9adfc 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,33 +1,44 @@ -/*jshint node:true */ +/* eslint-env node */ + module.exports = function ( grunt ) { - grunt.loadNpmTasks( 'grunt-contrib-copy' ); - grunt.loadNpmTasks( 'grunt-contrib-jshint' ); - grunt.loadNpmTasks( 'grunt-stylelint' ); - grunt.loadNpmTasks( 'grunt-contrib-watch' ); - grunt.loadNpmTasks( 'grunt-banana-checker' ); - grunt.loadNpmTasks( 'grunt-jscs' ); - grunt.loadNpmTasks( 'grunt-jsonlint' ); - grunt.loadNpmTasks( 'grunt-karma' ); var wgServer = process.env.MW_SERVER, wgScriptPath = process.env.MW_SCRIPT_PATH, karmaProxy = {}; + grunt.loadNpmTasks( 'grunt-banana-checker' ); + grunt.loadNpmTasks( 'grunt-contrib-copy' ); + grunt.loadNpmTasks( 'grunt-contrib-watch' ); + grunt.loadNpmTasks( 'grunt-eslint' ); + grunt.loadNpmTasks( 'grunt-jsonlint' ); + grunt.loadNpmTasks( 'grunt-karma' ); + grunt.loadNpmTasks( 'grunt-stylelint' ); + karmaProxy[ wgScriptPath ] = wgServer + wgScriptPath; grunt.initConfig( { - jshint: { - options: { - jshintrc: true - }, - all: '.' - }, - jscs: { - all: '.' + eslint: { + all: [ + '**/*.js', + '!docs/**', + '!tests/**', + '!extensions/**', + '!node_modules/**', + '!resources/lib/**', + '!resources/src/jquery.tipsy/**', + '!resources/src/jquery/jquery.farbtastic.js', + '!resources/src/mediawiki.libs/**', + '!skins/**', + '!vendor/**', + // Skip functions aren't even parseable + '!resources/src/dom-level2-skip.js', + '!resources/src/es5-skip.js', + '!resources/src/json-skip.js', + '!resources/src/mediawiki.hidpi-skip.js' + ] }, jsonlint: { all: [ - '.jscsrc', '**/*.json', '!{docs/js,extensions,node_modules,skins,vendor}/**' ] @@ -48,7 +59,7 @@ module.exports = function ( grunt ) { }, watch: { files: [ - '.{stylelintrc,jscsrc,jshintignore,jshintrc}', + '.{stylelintrc,eslintrc.json}', '**/*', '!{docs,extensions,node_modules,skins,vendor}/**' ], @@ -103,7 +114,7 @@ module.exports = function ( grunt ) { return !!( process.env.MW_SERVER && process.env.MW_SCRIPT_PATH ); } ); - grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'jsonlint', 'banana', 'stylelint' ] ); + grunt.registerTask( 'lint', [ 'eslint', 'banana', 'stylelint' ] ); grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:main' ] ); grunt.registerTask( 'test', [ 'lint' ] ); 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..b8d92956ef 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. @@ -2453,6 +2464,13 @@ $revId: ID of the revision that was parsed to create $parserOutput 'ParserCloned': Called when the parser is cloned. $parser: Newly-cloned Parser object +'ParserFetchTemplate': Called when the parser fetches a template +$parser: Parser Parser object or false +$title: Title object of the template to be fetched +$rev: Revision object of the template +&$text: Transclusion text of the template or false or null +&$deps: Array of template dependencies with 'title', 'page_id', 'rev_id' keys + 'ParserFirstCallInit': Called when the parser initialises for the first time. &$parser: Parser object being cleared @@ -2475,12 +2493,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 +2539,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 +3375,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 +3761,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..63b599b07a 100644 --- a/resources/src/jquery/jquery.accessKeyLabel.js +++ b/resources/src/jquery/jquery.accessKeyLabel.js @@ -5,217 +5,223 @@ */ ( function ( $, mw ) { -// Cached access key modifiers for used browser -var cachedAccessKeyModifiers, - - // Whether to use 'test-' instead of correct prefix (used for testing) - useTestPrefix = false, - - // tag names which can have a label tag - // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content - labelable = 'button, input, textarea, keygen, meter, output, progress, select'; - -/** - * Find the modifier keys that need to be pressed together with the accesskey to trigger the input. - * - * The result is dependant on the ua paramater or the current platform. - * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here. - * Valid key values that are returned can be: ctrl, alt, option, shift, esc - * - * @private - * @param {Object} [ua] An object with a 'userAgent' and 'platform' property. - * @return {Array} Array with 0 or more of the string values: ctrl, option, alt, shift, esc - */ -function getAccessKeyModifiers( ua ) { - // use cached prefix if possible - if ( !ua && cachedAccessKeyModifiers ) { - return cachedAccessKeyModifiers; - } + // Cached access key modifiers for used browser + var cachedAccessKeyModifiers, + + // Whether to use 'test-' instead of correct prefix (used for testing) + useTestPrefix = false, + + // tag names which can have a label tag + // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content + labelable = 'button, input, textarea, keygen, meter, output, progress, select'; + + /** + * Find the modifier keys that need to be pressed together with the accesskey to trigger the input. + * + * The result is dependant on the ua paramater or the current platform. + * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here. + * Valid key values that are returned can be: ctrl, alt, option, shift, esc + * + * @private + * @param {Object} [ua] An object with a 'userAgent' and 'platform' property. + * @return {Array} Array with 0 or more of the string values: ctrl, option, alt, shift, esc + */ + function getAccessKeyModifiers( ua ) { + var profile, accessKeyModifiers; + + // use cached prefix if possible + if ( !ua && cachedAccessKeyModifiers ) { + return cachedAccessKeyModifiers; + } - var profile = $.client.profile( ua ), + profile = $.client.profile( ua ); accessKeyModifiers = [ 'alt' ]; - // Classic Opera on any platform - if ( profile.name === 'opera' && profile.versionNumber < 15 ) { - accessKeyModifiers = [ 'shift', 'esc' ]; - - // Chrome and modern Opera on any platform - } else if ( profile.name === 'chrome' || profile.name === 'opera' ) { - accessKeyModifiers = ( - profile.platform === 'mac' - // Chrome on Mac - ? [ 'ctrl', 'option' ] - // Chrome on Windows or Linux - // (both alt- and alt-shift work, but alt with E, D, F etc does not - // work since they are browser shortcuts) - : [ 'alt', 'shift' ] - ); - - // Non-Windows Safari with webkit_version > 526 - } else if ( profile.platform !== 'win' - && profile.name === 'safari' - && profile.layoutVersion > 526 - ) { - accessKeyModifiers = [ 'ctrl', 'alt' ]; - - // Safari/Konqueror on any platform, or any browser on Mac - // (but not Safari on Windows) - } else if ( !( profile.platform === 'win' && profile.name === 'safari' ) - && ( profile.name === 'safari' - || profile.platform === 'mac' - || profile.name === 'konqueror' ) - ) { - accessKeyModifiers = [ 'ctrl' ]; - - // Firefox/Iceweasel 2.x and later - } else if ( ( profile.name === 'firefox' || profile.name === 'iceweasel' ) - && profile.versionBase > '1' - ) { - accessKeyModifiers = [ 'alt', 'shift' ]; - } + // Classic Opera on any platform + if ( profile.name === 'opera' && profile.versionNumber < 15 ) { + accessKeyModifiers = [ 'shift', 'esc' ]; + + // Chrome and modern Opera on any platform + } else if ( profile.name === 'chrome' || profile.name === 'opera' ) { + accessKeyModifiers = ( + profile.platform === 'mac' ? + // Chrome on Mac + [ 'ctrl', 'option' ] : + // Chrome on Windows or Linux + // (both alt- and alt-shift work, but alt with E, D, F etc does not + // work since they are browser shortcuts) + [ 'alt', 'shift' ] + ); + + // Non-Windows Safari with webkit_version > 526 + } else if ( profile.platform !== 'win' && + profile.name === 'safari' && + profile.layoutVersion > 526 + ) { + accessKeyModifiers = [ 'ctrl', 'alt' ]; + + // Safari/Konqueror on any platform, or any browser on Mac + // (but not Safari on Windows) + } else if ( + !( profile.platform === 'win' && profile.name === 'safari' ) && + ( + profile.name === 'safari' || + profile.platform === 'mac' || + profile.name === 'konqueror' + ) + ) { + accessKeyModifiers = [ 'ctrl' ]; + + // Firefox/Iceweasel 2.x and later + } else if ( + ( profile.name === 'firefox' || profile.name === 'iceweasel' ) && + profile.versionBase > '1' + ) { + accessKeyModifiers = [ 'alt', 'shift' ]; + } - // cache modifiers - if ( !ua ) { - cachedAccessKeyModifiers = accessKeyModifiers; + // cache modifiers + if ( !ua ) { + cachedAccessKeyModifiers = accessKeyModifiers; + } + return accessKeyModifiers; } - return accessKeyModifiers; -} -/** - * Get the access key label for an element. - * - * Will use native accessKeyLabel if available (currently only in Firefox 8+), - * falls back to #getAccessKeyModifiers. - * - * @private - * @param {HTMLElement} element Element to get the label for - * @return {string} Access key label - */ -function getAccessKeyLabel( element ) { - // abort early if no access key - if ( !element.accessKey ) { - return ''; - } - // use accessKeyLabel if possible - // http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#dom-accesskeylabel - if ( !useTestPrefix && element.accessKeyLabel ) { - return element.accessKeyLabel; + /** + * Get the access key label for an element. + * + * Will use native accessKeyLabel if available (currently only in Firefox 8+), + * falls back to #getAccessKeyModifiers. + * + * @private + * @param {HTMLElement} element Element to get the label for + * @return {string} Access key label + */ + function getAccessKeyLabel( element ) { + // abort early if no access key + if ( !element.accessKey ) { + return ''; + } + // use accessKeyLabel if possible + // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel + if ( !useTestPrefix && element.accessKeyLabel ) { + return element.accessKeyLabel; + } + return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey; } - return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey; -} - -/** - * Update the title for an element (on the element with the access key or it's label) to show - * the correct access key label. - * - * @private - * @param {HTMLElement} element Element with the accesskey - * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`) - */ -function updateTooltipOnElement( element, titleElement ) { - var oldTitle, parts, regexp, newTitle, accessKeyLabel; - oldTitle = titleElement.title; - if ( !oldTitle ) { - // don't add a title if the element didn't have one before - return; - } + /** + * Update the title for an element (on the element with the access key or it's label) to show + * the correct access key label. + * + * @private + * @param {HTMLElement} element Element with the accesskey + * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`) + */ + function updateTooltipOnElement( element, titleElement ) { + var oldTitle, parts, regexp, newTitle, accessKeyLabel; + + oldTitle = titleElement.title; + if ( !oldTitle ) { + // don't add a title if the element didn't have one before + return; + } - parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' ); - regexp = new RegExp( $.map( parts, mw.RegExp.escape ).join( '.*?' ) + '$' ); - newTitle = oldTitle.replace( regexp, '' ); - accessKeyLabel = getAccessKeyLabel( element ); + parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' ); + regexp = new RegExp( $.map( parts, mw.RegExp.escape ).join( '.*?' ) + '$' ); + newTitle = oldTitle.replace( regexp, '' ); + accessKeyLabel = getAccessKeyLabel( element ); - if ( accessKeyLabel ) { - // Should be build the same as in Linker::titleAttrib - newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel ); - } - if ( oldTitle !== newTitle ) { - titleElement.title = newTitle; + if ( accessKeyLabel ) { + // Should be build the same as in Linker::titleAttrib + newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel ); + } + if ( oldTitle !== newTitle ) { + titleElement.title = newTitle; + } } -} -/** - * Update the title for an element to show the correct access key label. - * - * @private - * @param {HTMLElement} element Element with the accesskey - */ -function updateTooltip( element ) { - var id, $element, $label, $labelParent; - updateTooltipOnElement( element, element ); - - // update associated label if there is one - $element = $( element ); - if ( $element.is( labelable ) ) { - // Search it using 'for' attribute - id = element.id.replace( /"/g, '\\"' ); - if ( id ) { - $label = $( 'label[for="' + id + '"]' ); - if ( $label.length === 1 ) { - updateTooltipOnElement( element, $label[ 0 ] ); + /** + * Update the title for an element to show the correct access key label. + * + * @private + * @param {HTMLElement} element Element with the accesskey + */ + function updateTooltip( element ) { + var id, $element, $label, $labelParent; + updateTooltipOnElement( element, element ); + + // update associated label if there is one + $element = $( element ); + if ( $element.is( labelable ) ) { + // Search it using 'for' attribute + id = element.id.replace( /"/g, '\\"' ); + if ( id ) { + $label = $( 'label[for="' + id + '"]' ); + if ( $label.length === 1 ) { + updateTooltipOnElement( element, $label[ 0 ] ); + } } - } - // Search it as parent, because the form control can also be inside the label element itself - $labelParent = $element.parents( 'label' ); - if ( $labelParent.length === 1 ) { - updateTooltipOnElement( element, $labelParent[ 0 ] ); + // Search it as parent, because the form control can also be inside the label element itself + $labelParent = $element.parents( 'label' ); + if ( $labelParent.length === 1 ) { + updateTooltipOnElement( element, $labelParent[ 0 ] ); + } } } -} - -/** - * Update the titles for all elements in a jQuery selection. - * - * @return {jQuery} - * @chainable - */ -$.fn.updateTooltipAccessKeys = function () { - return this.each( function () { - updateTooltip( this ); - } ); -}; -/** - * getAccessKeyModifiers - * - * @method updateTooltipAccessKeys_getAccessKeyModifiers - * @inheritdoc #getAccessKeyModifiers - */ -$.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers; - -/** - * getAccessKeyLabel - * - * @method updateTooltipAccessKeys_getAccessKeyLabel - * @inheritdoc #getAccessKeyLabel - */ -$.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel; - -/** - * getAccessKeyPrefix - * - * @method updateTooltipAccessKeys_getAccessKeyPrefix - * @deprecated since 1.27 Use #getAccessKeyModifiers - */ -$.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) { - return getAccessKeyModifiers( ua ).join( '-' ) + '-'; -}; - -/** - * Switch test mode on and off. - * - * @method updateTooltipAccessKeys_setTestMode - * @param {boolean} mode New mode - */ -$.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) { - useTestPrefix = mode; -}; - -/** - * @class jQuery - * @mixins jQuery.plugin.accessKeyLabel - */ + /** + * Update the titles for all elements in a jQuery selection. + * + * @return {jQuery} + * @chainable + */ + $.fn.updateTooltipAccessKeys = function () { + return this.each( function () { + updateTooltip( this ); + } ); + }; + + /** + * getAccessKeyModifiers + * + * @method updateTooltipAccessKeys_getAccessKeyModifiers + * @inheritdoc #getAccessKeyModifiers + */ + $.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers; + + /** + * getAccessKeyLabel + * + * @method updateTooltipAccessKeys_getAccessKeyLabel + * @inheritdoc #getAccessKeyLabel + */ + $.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel; + + /** + * getAccessKeyPrefix + * + * @method updateTooltipAccessKeys_getAccessKeyPrefix + * @deprecated since 1.27 Use #getAccessKeyModifiers + */ + $.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) { + return getAccessKeyModifiers( ua ).join( '-' ) + '-'; + }; + + /** + * Switch test mode on and off. + * + * @method updateTooltipAccessKeys_setTestMode + * @param {boolean} mode New mode + */ + $.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) { + useTestPrefix = mode; + }; + + /** + * @class jQuery + * @mixins jQuery.plugin.accessKeyLabel + */ }( jQuery, mediaWiki ) ); diff --git a/resources/src/jquery/jquery.autoEllipsis.js b/resources/src/jquery/jquery.autoEllipsis.js index fd7e8d1e39..8716b69405 100644 --- a/resources/src/jquery/jquery.autoEllipsis.js +++ b/resources/src/jquery/jquery.autoEllipsis.js @@ -3,169 +3,169 @@ */ ( function ( $ ) { -var - // Cache ellipsed substrings for every string-width-position combination - cache = {}, + var + // Cache ellipsed substrings for every string-width-position combination + cache = {}, - // Use a separate cache when match highlighting is enabled - matchTextCache = {}; + // Use a separate cache when match highlighting is enabled + matchTextCache = {}; -// Due to -// jscs:disable jsDoc -/** - * Automatically truncate the plain text contents of an element and add an ellipsis - * - * @param {Object} options - * @param {'left'|'center'|'right'} [options.position='center'] Where to remove text. - * @param {boolean} [options.tooltip=false] Whether to show a tooltip with the remainder - * of the text. - * @param {boolean} [options.restoreText=false] Whether to save the text for restoring - * later. - * @param {boolean} [options.hasSpan=false] Whether the element is already a container, - * or if the library should create a new container for it. - * @param {string|null} [options.matchText=null] Text to highlight, e.g. search terms. - * @return {jQuery} - * @chainable - */ -$.fn.autoEllipsis = function ( options ) { - options = $.extend( { - position: 'center', - tooltip: false, - restoreText: false, - hasSpan: false, - matchText: null - }, options ); + // Due to + // jscs:disable jsDoc + /** + * Automatically truncate the plain text contents of an element and add an ellipsis + * + * @param {Object} options + * @param {'left'|'center'|'right'} [options.position='center'] Where to remove text. + * @param {boolean} [options.tooltip=false] Whether to show a tooltip with the remainder + * of the text. + * @param {boolean} [options.restoreText=false] Whether to save the text for restoring + * later. + * @param {boolean} [options.hasSpan=false] Whether the element is already a container, + * or if the library should create a new container for it. + * @param {string|null} [options.matchText=null] Text to highlight, e.g. search terms. + * @return {jQuery} + * @chainable + */ + $.fn.autoEllipsis = function ( options ) { + options = $.extend( { + position: 'center', + tooltip: false, + restoreText: false, + hasSpan: false, + matchText: null + }, options ); - return this.each( function () { - var $trimmableText, - text, trimmableText, w, pw, - l, r, i, side, m, - // container element - used for measuring against - $container = $( this ); + return this.each( function () { + var $trimmableText, + text, trimmableText, w, pw, + l, r, i, side, m, + // container element - used for measuring against + $container = $( this ); - if ( options.restoreText ) { - if ( !$container.data( 'autoEllipsis.originalText' ) ) { - $container.data( 'autoEllipsis.originalText', $container.text() ); - } else { - $container.text( $container.data( 'autoEllipsis.originalText' ) ); + if ( options.restoreText ) { + if ( !$container.data( 'autoEllipsis.originalText' ) ) { + $container.data( 'autoEllipsis.originalText', $container.text() ); + } else { + $container.text( $container.data( 'autoEllipsis.originalText' ) ); + } } - } - // trimmable text element - only the text within this element will be trimmed - if ( options.hasSpan ) { - $trimmableText = $container.children( options.selector ); - } else { - $trimmableText = $( '' ) - .css( 'whiteSpace', 'nowrap' ) - .text( $container.text() ); - $container - .empty() - .append( $trimmableText ); - } + // trimmable text element - only the text within this element will be trimmed + if ( options.hasSpan ) { + $trimmableText = $container.children( options.selector ); + } else { + $trimmableText = $( '' ) + .css( 'whiteSpace', 'nowrap' ) + .text( $container.text() ); + $container + .empty() + .append( $trimmableText ); + } - text = $container.text(); - trimmableText = $trimmableText.text(); - w = $container.width(); - pw = 0; + text = $container.text(); + trimmableText = $trimmableText.text(); + w = $container.width(); + pw = 0; - // Try cache - if ( options.matchText ) { - if ( !( text in matchTextCache ) ) { - matchTextCache[ text ] = {}; - } - if ( !( options.matchText in matchTextCache[ text ] ) ) { - matchTextCache[ text ][ options.matchText ] = {}; - } - if ( !( w in matchTextCache[ text ][ options.matchText ] ) ) { - matchTextCache[ text ][ options.matchText ][ w ] = {}; - } - if ( options.position in matchTextCache[ text ][ options.matchText ][ w ] ) { - $container.html( matchTextCache[ text ][ options.matchText ][ w ][ options.position ] ); - if ( options.tooltip ) { - $container.attr( 'title', text ); + // Try cache + if ( options.matchText ) { + if ( !( text in matchTextCache ) ) { + matchTextCache[ text ] = {}; } - return; - } - } else { - if ( !( text in cache ) ) { - cache[ text ] = {}; - } - if ( !( w in cache[ text ] ) ) { - cache[ text ][ w ] = {}; - } - if ( options.position in cache[ text ][ w ] ) { - $container.html( cache[ text ][ w ][ options.position ] ); - if ( options.tooltip ) { - $container.attr( 'title', text ); + if ( !( options.matchText in matchTextCache[ text ] ) ) { + matchTextCache[ text ][ options.matchText ] = {}; + } + if ( !( w in matchTextCache[ text ][ options.matchText ] ) ) { + matchTextCache[ text ][ options.matchText ][ w ] = {}; + } + if ( options.position in matchTextCache[ text ][ options.matchText ][ w ] ) { + $container.html( matchTextCache[ text ][ options.matchText ][ w ][ options.position ] ); + if ( options.tooltip ) { + $container.attr( 'title', text ); + } + return; + } + } else { + if ( !( text in cache ) ) { + cache[ text ] = {}; + } + if ( !( w in cache[ text ] ) ) { + cache[ text ][ w ] = {}; + } + if ( options.position in cache[ text ][ w ] ) { + $container.html( cache[ text ][ w ][ options.position ] ); + if ( options.tooltip ) { + $container.attr( 'title', text ); + } + return; } - return; } - } - if ( $trimmableText.width() + pw > w ) { - switch ( options.position ) { - case 'right': - // Use binary search-like technique for efficiency - l = 0; - r = trimmableText.length; - do { - m = Math.ceil( ( l + r ) / 2 ); - $trimmableText.text( trimmableText.slice( 0, m ) + '...' ); - if ( $trimmableText.width() + pw > w ) { - // Text is too long - r = m - 1; - } else { - l = m; + if ( $trimmableText.width() + pw > w ) { + switch ( options.position ) { + case 'right': + // Use binary search-like technique for efficiency + l = 0; + r = trimmableText.length; + do { + m = Math.ceil( ( l + r ) / 2 ); + $trimmableText.text( trimmableText.slice( 0, m ) + '...' ); + if ( $trimmableText.width() + pw > w ) { + // Text is too long + r = m - 1; + } else { + l = m; + } + } while ( l < r ); + $trimmableText.text( trimmableText.slice( 0, l ) + '...' ); + break; + case 'center': + // TODO: Use binary search like for 'right' + i = [ Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 ) ]; + // Begin with making the end shorter + side = 1; + while ( $trimmableText.outerWidth() + pw > w && i[ 0 ] > 0 ) { + $trimmableText.text( trimmableText.slice( 0, i[ 0 ] ) + '...' + trimmableText.slice( i[ 1 ] ) ); + // Alternate between trimming the end and begining + if ( side === 0 ) { + // Make the begining shorter + i[ 0 ]--; + side = 1; + } else { + // Make the end shorter + i[ 1 ]++; + side = 0; + } } - } while ( l < r ); - $trimmableText.text( trimmableText.slice( 0, l ) + '...' ); - break; - case 'center': - // TODO: Use binary search like for 'right' - i = [ Math.round( trimmableText.length / 2 ), Math.round( trimmableText.length / 2 ) ]; - // Begin with making the end shorter - side = 1; - while ( $trimmableText.outerWidth() + pw > w && i[ 0 ] > 0 ) { - $trimmableText.text( trimmableText.slice( 0, i[ 0 ] ) + '...' + trimmableText.slice( i[ 1 ] ) ); - // Alternate between trimming the end and begining - if ( side === 0 ) { - // Make the begining shorter - i[ 0 ]--; - side = 1; - } else { - // Make the end shorter - i[ 1 ]++; - side = 0; + break; + case 'left': + // TODO: Use binary search like for 'right' + r = 0; + while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) { + $trimmableText.text( '...' + trimmableText.slice( r ) ); + r++; } - } - break; - case 'left': - // TODO: Use binary search like for 'right' - r = 0; - while ( $trimmableText.outerWidth() + pw > w && r < trimmableText.length ) { - $trimmableText.text( '...' + trimmableText.slice( r ) ); - r++; - } - break; + break; + } + } + if ( options.tooltip ) { + $container.attr( 'title', text ); + } + if ( options.matchText ) { + $container.highlightText( options.matchText ); + matchTextCache[ text ][ options.matchText ][ w ][ options.position ] = $container.html(); + } else { + cache[ text ][ w ][ options.position ] = $container.html(); } - } - if ( options.tooltip ) { - $container.attr( 'title', text ); - } - if ( options.matchText ) { - $container.highlightText( options.matchText ); - matchTextCache[ text ][ options.matchText ][ w ][ options.position ] = $container.html(); - } else { - cache[ text ][ w ][ options.position ] = $container.html(); - } - } ); -}; -// jscs:enable jsDoc + } ); + }; + // jscs:enable jsDoc -/** - * @class jQuery - * @mixins jQuery.plugin.autoEllipsis - */ + /** + * @class jQuery + * @mixins jQuery.plugin.autoEllipsis + */ }( jQuery ) ); diff --git a/resources/src/jquery/jquery.byteLimit.js b/resources/src/jquery/jquery.byteLimit.js index dd71a2bc4d..567bec8ad6 100644 --- a/resources/src/jquery/jquery.byteLimit.js +++ b/resources/src/jquery/jquery.byteLimit.js @@ -3,6 +3,17 @@ */ ( function ( $ ) { + var eventKeys = [ + 'keyup.byteLimit', + 'keydown.byteLimit', + 'change.byteLimit', + 'mouseup.byteLimit', + 'cut.byteLimit', + 'paste.byteLimit', + 'focus.byteLimit', + 'blur.byteLimit' + ].join( ' ' ); + /** * Utility function to trim down a string, based on byteLimit * and given a safe start position. It supports insertion anywhere @@ -94,17 +105,6 @@ }; }; - var eventKeys = [ - 'keyup.byteLimit', - 'keydown.byteLimit', - 'change.byteLimit', - 'mouseup.byteLimit', - 'cut.byteLimit', - 'paste.byteLimit', - 'focus.byteLimit', - 'blur.byteLimit' - ].join( ' ' ); - /** * Enforces a byte limit on an input field, so that UTF-8 entries are counted as well, * when, for example, a database field has a byte limit rather than a character limit. @@ -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/jquery/jquery.color.js b/resources/src/jquery/jquery.color.js index a3cc8fc3af..70dc1057a6 100644 --- a/resources/src/jquery/jquery.color.js +++ b/resources/src/jquery/jquery.color.js @@ -10,7 +10,6 @@ ( function ( $ ) { function getColor( elem, attr ) { - /*jshint boss:true */ var color; do { @@ -22,6 +21,7 @@ } attr = 'backgroundColor'; + // eslint-disable-next-line no-cond-assign } while ( elem = elem.parentNode ); return $.colorUtil.getRGB( color ); diff --git a/resources/src/jquery/jquery.colorUtil.js b/resources/src/jquery/jquery.colorUtil.js index c14f2c861c..c53ec3b147 100644 --- a/resources/src/jquery/jquery.colorUtil.js +++ b/resources/src/jquery/jquery.colorUtil.js @@ -23,7 +23,6 @@ * @return {Array} */ getRGB: function ( color ) { - /*jshint boss:true */ var result; // Check if we're already dealing with an array of colors @@ -32,6 +31,7 @@ } // Look for rgb(num,num,num) + // eslint-disable-next-line no-cond-assign if ( result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec( color ) ) { return [ parseInt( result[ 1 ], 10 ), @@ -41,6 +41,7 @@ } // Look for rgb(num%,num%,num%) + // eslint-disable-next-line no-cond-assign if ( result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec( color ) ) { return [ parseFloat( result[ 1 ] ) * 2.55, @@ -50,6 +51,7 @@ } // Look for #a0b1c2 + // eslint-disable-next-line no-cond-assign if ( result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec( color ) ) { return [ parseInt( result[ 1 ], 16 ), @@ -59,6 +61,7 @@ } // Look for #fff + // eslint-disable-next-line no-cond-assign if ( result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec( color ) ) { return [ parseInt( result[ 1 ] + result[ 1 ], 16 ), @@ -68,6 +71,7 @@ } // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 + // eslint-disable-next-line no-cond-assign if ( result = /rgba\(0, 0, 0, 0\)/.exec( color ) ) { return $.colorUtil.colors.transparent; } @@ -148,16 +152,15 @@ * @return {number[]} The HSL representation */ rgbToHsl: function ( r, g, b ) { + var d, h, s, l, min, max; + r = r / 255; g = g / 255; b = b / 255; - var d, - max = Math.max( r, g, b ), - min = Math.min( r, g, b ), - h, - s, - l = ( max + min ) / 2; + max = Math.max( r, g, b ); + min = Math.min( r, g, b ); + l = ( max + min ) / 2; if ( max === min ) { // achromatic diff --git a/resources/src/jquery/jquery.hidpi.js b/resources/src/jquery/jquery.hidpi.js index 7d308f8e84..ed3124049f 100644 --- a/resources/src/jquery/jquery.hidpi.js +++ b/resources/src/jquery/jquery.hidpi.js @@ -11,166 +11,166 @@ */ ( function ( $ ) { -/** - * Get reported or approximate device pixel ratio. - * - * - 1.0 means 1 CSS pixel is 1 hardware pixel - * - 2.0 means 1 CSS pixel is 2 hardware pixels - * - etc. - * - * Uses `window.devicePixelRatio` if available, or CSS media queries on IE. - * - * @static - * @inheritable - * @return {number} Device pixel ratio - */ -$.devicePixelRatio = function () { - if ( window.devicePixelRatio !== undefined ) { - // Most web browsers: - // * WebKit/Blink (Safari, Chrome, Android browser, etc) - // * Opera - // * Firefox 18+ - // * Microsoft Edge (Windows 10) - return window.devicePixelRatio; - } else if ( window.msMatchMedia !== undefined ) { - // Windows 8 desktops / tablets, probably Windows Phone 8 - // - // IE 10/11 doesn't report pixel ratio directly, but we can get the - // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for - // simplicity, but you may get different values depending on zoom - // factor, size of screen and orientation in Metro IE. - if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) { + /** + * Get reported or approximate device pixel ratio. + * + * - 1.0 means 1 CSS pixel is 1 hardware pixel + * - 2.0 means 1 CSS pixel is 2 hardware pixels + * - etc. + * + * Uses `window.devicePixelRatio` if available, or CSS media queries on IE. + * + * @static + * @inheritable + * @return {number} Device pixel ratio + */ + $.devicePixelRatio = function () { + if ( window.devicePixelRatio !== undefined ) { + // Most web browsers: + // * WebKit/Blink (Safari, Chrome, Android browser, etc) + // * Opera + // * Firefox 18+ + // * Microsoft Edge (Windows 10) + return window.devicePixelRatio; + } else if ( window.msMatchMedia !== undefined ) { + // Windows 8 desktops / tablets, probably Windows Phone 8 + // + // IE 10/11 doesn't report pixel ratio directly, but we can get the + // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for + // simplicity, but you may get different values depending on zoom + // factor, size of screen and orientation in Metro IE. + if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) { + return 2; + } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) { + return 1.5; + } else { + return 1; + } + } else { + // Legacy browsers... + // Assume 1 if unknown. + return 1; + } + }; + + /** + * Bracket a given device pixel ratio to one of [1, 1.5, 2]. + * + * This is useful for grabbing images on the fly with sizes based on the display + * density, without causing slowdown and extra thumbnail renderings on devices + * that are slightly different from the most common sizes. + * + * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails, + * so will be consistent with default renderings. + * + * @static + * @inheritable + * @return {number} Device pixel ratio + */ + $.bracketDevicePixelRatio = function ( baseRatio ) { + if ( baseRatio > 1.5 ) { return 2; - } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) { + } else if ( baseRatio > 1 ) { return 1.5; } else { return 1; } - } else { - // Legacy browsers... - // Assume 1 if unknown. - return 1; - } -}; - -/** - * Bracket a given device pixel ratio to one of [1, 1.5, 2]. - * - * This is useful for grabbing images on the fly with sizes based on the display - * density, without causing slowdown and extra thumbnail renderings on devices - * that are slightly different from the most common sizes. - * - * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails, - * so will be consistent with default renderings. - * - * @static - * @inheritable - * @return {number} Device pixel ratio - */ -$.bracketDevicePixelRatio = function ( baseRatio ) { - if ( baseRatio > 1.5 ) { - return 2; - } else if ( baseRatio > 1 ) { - return 1.5; - } else { - return 1; - } -}; + }; -/** - * Get reported or approximate device pixel ratio, bracketed to [1, 1.5, 2]. - * - * This is useful for grabbing images on the fly with sizes based on the display - * density, without causing slowdown and extra thumbnail renderings on devices - * that are slightly different from the most common sizes. - * - * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails, - * so will be consistent with default renderings. - * - * - 1.0 means 1 CSS pixel is 1 hardware pixel - * - 1.5 means 1 CSS pixel is 1.5 hardware pixels - * - 2.0 means 1 CSS pixel is 2 hardware pixels - * - * @static - * @inheritable - * @return {number} Device pixel ratio - */ -$.bracketedDevicePixelRatio = function () { - return $.bracketDevicePixelRatio( $.devicePixelRatio() ); -}; + /** + * Get reported or approximate device pixel ratio, bracketed to [1, 1.5, 2]. + * + * This is useful for grabbing images on the fly with sizes based on the display + * density, without causing slowdown and extra thumbnail renderings on devices + * that are slightly different from the most common sizes. + * + * The bracketed ratios match the default 'srcset' output on MediaWiki thumbnails, + * so will be consistent with default renderings. + * + * - 1.0 means 1 CSS pixel is 1 hardware pixel + * - 1.5 means 1 CSS pixel is 1.5 hardware pixels + * - 2.0 means 1 CSS pixel is 2 hardware pixels + * + * @static + * @inheritable + * @return {number} Device pixel ratio + */ + $.bracketedDevicePixelRatio = function () { + return $.bracketDevicePixelRatio( $.devicePixelRatio() ); + }; -/** - * Implement responsive images based on srcset attributes, if browser has no - * native srcset support. - * - * @return {jQuery} This selection - * @chainable - */ -$.fn.hidpi = function () { - var $target = this, - // TODO add support for dpi media query checks on Firefox, IE - devicePixelRatio = $.devicePixelRatio(), - testImage = new Image(); + /** + * Implement responsive images based on srcset attributes, if browser has no + * native srcset support. + * + * @return {jQuery} This selection + * @chainable + */ + $.fn.hidpi = function () { + var $target = this, + // TODO add support for dpi media query checks on Firefox, IE + devicePixelRatio = $.devicePixelRatio(), + testImage = new Image(); - if ( devicePixelRatio > 1 && testImage.srcset === undefined ) { - // No native srcset support. - $target.find( 'img' ).each( function () { - var $img = $( this ), - srcset = $img.attr( 'srcset' ), - match; - if ( typeof srcset === 'string' && srcset !== '' ) { - match = $.matchSrcSet( devicePixelRatio, srcset ); - if ( match !== null ) { - $img.attr( 'src', match ); + if ( devicePixelRatio > 1 && testImage.srcset === undefined ) { + // No native srcset support. + $target.find( 'img' ).each( function () { + var $img = $( this ), + srcset = $img.attr( 'srcset' ), + match; + if ( typeof srcset === 'string' && srcset !== '' ) { + match = $.matchSrcSet( devicePixelRatio, srcset ); + if ( match !== null ) { + $img.attr( 'src', match ); + } } - } - } ); - } + } ); + } - return $target; -}; + return $target; + }; -/** - * Match a srcset entry for the given device pixel ratio - * - * Exposed for testing. - * - * @private - * @static - * @param {number} devicePixelRatio - * @param {string} srcset - * @return {Mixed} null or the matching src string - */ -$.matchSrcSet = function ( devicePixelRatio, srcset ) { - var candidates, - candidate, - bits, - src, - i, - ratioStr, - ratio, - selectedRatio = 1, - selectedSrc = null; - candidates = srcset.split( / *, */ ); - for ( i = 0; i < candidates.length; i++ ) { - candidate = candidates[ i ]; - bits = candidate.split( / +/ ); - src = bits[ 0 ]; - if ( bits.length > 1 && bits[ 1 ].charAt( bits[ 1 ].length - 1 ) === 'x' ) { - ratioStr = bits[ 1 ].slice( 0, -1 ); - ratio = parseFloat( ratioStr ); - if ( ratio <= devicePixelRatio && ratio > selectedRatio ) { - selectedRatio = ratio; - selectedSrc = src; + /** + * Match a srcset entry for the given device pixel ratio + * + * Exposed for testing. + * + * @private + * @static + * @param {number} devicePixelRatio + * @param {string} srcset + * @return {Mixed} null or the matching src string + */ + $.matchSrcSet = function ( devicePixelRatio, srcset ) { + var candidates, + candidate, + bits, + src, + i, + ratioStr, + ratio, + selectedRatio = 1, + selectedSrc = null; + candidates = srcset.split( / *, */ ); + for ( i = 0; i < candidates.length; i++ ) { + candidate = candidates[ i ]; + bits = candidate.split( / +/ ); + src = bits[ 0 ]; + if ( bits.length > 1 && bits[ 1 ].charAt( bits[ 1 ].length - 1 ) === 'x' ) { + ratioStr = bits[ 1 ].slice( 0, -1 ); + ratio = parseFloat( ratioStr ); + if ( ratio <= devicePixelRatio && ratio > selectedRatio ) { + selectedRatio = ratio; + selectedSrc = src; + } } } - } - return selectedSrc; -}; + return selectedSrc; + }; -/** - * @class jQuery - * @mixins jQuery.plugin.hidpi - */ + /** + * @class jQuery + * @mixins jQuery.plugin.hidpi + */ }( jQuery ) ); diff --git a/resources/src/jquery/jquery.highlightText.js b/resources/src/jquery/jquery.highlightText.js index e37f19b04a..3feca81cc1 100644 --- a/resources/src/jquery/jquery.highlightText.js +++ b/resources/src/jquery/jquery.highlightText.js @@ -45,12 +45,14 @@ // replace the matched node, with our span-wrapped clone of the matched node middlebit.parentNode.replaceChild( spannode, middlebit ); } - } else if ( node.nodeType === Node.ELEMENT_NODE + } else if ( + node.nodeType === Node.ELEMENT_NODE && // element with childnodes, and not a script, style or an element we created - && node.childNodes - && !/(script|style)/i.test( node.tagName ) - && !( node.tagName.toLowerCase() === 'span' - && node.className.match( /\bhighlight/ ) + node.childNodes && + !/(script|style)/i.test( node.tagName ) && + !( + node.tagName.toLowerCase() === 'span' && + node.className.match( /\bhighlight/ ) ) ) { for ( i = 0; i < node.childNodes.length; ++i ) { diff --git a/resources/src/jquery/jquery.localize.js b/resources/src/jquery/jquery.localize.js index f5932b246f..05b3891dea 100644 --- a/resources/src/jquery/jquery.localize.js +++ b/resources/src/jquery/jquery.localize.js @@ -3,168 +3,168 @@ */ ( function ( $, mw ) { -/** - * Gets a localized message, using parameters from options if present. - * - * @ignore - * @param {Object} options - * @param {string} key - * @return {string} Localized message - */ -function msg( options, key ) { - var args = options.params[ key ] || []; - // Format: mw.msg( key [, p1, p2, ...] ) - args.unshift( options.prefix + ( options.keys[ key ] || key ) ); - return mw.msg.apply( mw, args ); -} + /** + * Gets a localized message, using parameters from options if present. + * + * @ignore + * @param {Object} options + * @param {string} key + * @return {string} Localized message + */ + function msg( options, key ) { + var args = options.params[ key ] || []; + // Format: mw.msg( key [, p1, p2, ...] ) + args.unshift( options.prefix + ( options.keys[ key ] || key ) ); + return mw.msg.apply( mw, args ); + } -/** - * Localizes a DOM selection by replacing elements with localized text and adding - * localized title and alt attributes to elements with title-msg and alt-msg attributes - * respectively. - * - * Call on a selection of HTML which contains `` elements or elements - * with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes. - * `` elements will be replaced with localized text, *-msg attributes will be replaced - * with attributes that do not have the "-msg" suffix and contain a localized message. - * - * Example: - * // Messages: { 'title': 'Awesome', 'desc': 'Cat doing backflip' 'search' contains 'Search' } - * var html = '\ - *

    \ - * \ - * \ - * \ - *

    '; - * $( 'body' ).append( $( html ).localize() ); - * - * Appends something like this to the body... - *

    - * Awesome - * Cat doing backflip - * - *

    - * - * Arguments can be passed into uses of a message using the params property of the options object - * given to .localize(). Multiple messages can be given parameters, because the params property is - * an object keyed by the message key to apply the parameters to, each containing an array of - * parameters to use. The limitation is that you can not use different parameters to individual uses - * of a message in the same selection being localized - they will all recieve the same parameters. - * - * Example: - * // Messages: { 'easy-as': 'Easy as $1 $2 $3.' } - * var html = '

    '; - * $( 'body' ).append( $( html ).localize( { 'params': { 'easy-as': ['a', 'b', 'c'] } } ) ); - * - * Appends something like this to the body... - *

    Easy as a, b, c

    - * - * Raw HTML content can be used, instead of it being escaped as text. To do this, just use the raw - * attribute on a msg element. - * - * Example: - * // Messages: { 'hello': 'Hello $1!' } - * var html = '\ - *

    \ - * \ - * \ - *

    '; - * $( 'body' ).append( $( html ).localize( { 'params': { 'hello': ['world'] } } ) ); - * - * Appends something like this to the body... - *

    - * <b><i>Hello</i> world!</b> - * Hello world! - *

    - * - * Message keys can also be remapped, allowing the same generic template to be used with a variety - * of messages. This is important for improving re-usability of templates. - * - * Example: - * // Messages: { 'good-afternoon': 'Good afternoon' } - * var html = '

    '; - * $( 'body' ).append( $( html ).localize( { 'keys': { 'greeting': 'good-afternoon' } } ) ); - * - * Appends something like this to the body... - *

    Good afternoon

    - * - * Message keys can also be prefixed globally, which is handy when writing extensions, where by - * convention all messages are prefixed with the extension's name. - * - * Example: - * // Messages: { 'teleportation-warning': 'You may not get there all in one piece.' } - * var html = '

    '; - * $( 'body' ).append( $( html ).localize( { 'prefix': 'teleportation-' } ) ); - * - * Appends something like this to the body... - *

    You may not get there all in one piece.

    - * - * @param {Object} options Map of options to be used while localizing - * @param {string} options.prefix String to prepend to all message keys - * @param {Object} options.keys Message key aliases, used for remapping keys to a template - * @param {Object} options.params Lists of parameters to use with certain message keys - * @return {jQuery} - * @chainable - */ -$.fn.localize = function ( options ) { - var $target = this, - attributes = [ 'title', 'alt', 'placeholder' ]; + /** + * Localizes a DOM selection by replacing elements with localized text and adding + * localized title and alt attributes to elements with title-msg and alt-msg attributes + * respectively. + * + * Call on a selection of HTML which contains `` elements or elements + * with title-msg="message-key", alt-msg="message-key" or placeholder-msg="message-key" attributes. + * `` elements will be replaced with localized text, *-msg attributes will be replaced + * with attributes that do not have the "-msg" suffix and contain a localized message. + * + * Example: + * // Messages: { 'title': 'Awesome', 'desc': 'Cat doing backflip' 'search' contains 'Search' } + * var html = '\ + *

    \ + * \ + * \ + * \ + *

    '; + * $( 'body' ).append( $( html ).localize() ); + * + * Appends something like this to the body... + *

    + * Awesome + * Cat doing backflip + * + *

    + * + * Arguments can be passed into uses of a message using the params property of the options object + * given to .localize(). Multiple messages can be given parameters, because the params property is + * an object keyed by the message key to apply the parameters to, each containing an array of + * parameters to use. The limitation is that you can not use different parameters to individual uses + * of a message in the same selection being localized - they will all recieve the same parameters. + * + * Example: + * // Messages: { 'easy-as': 'Easy as $1 $2 $3.' } + * var html = '

    '; + * $( 'body' ).append( $( html ).localize( { 'params': { 'easy-as': ['a', 'b', 'c'] } } ) ); + * + * Appends something like this to the body... + *

    Easy as a, b, c

    + * + * Raw HTML content can be used, instead of it being escaped as text. To do this, just use the raw + * attribute on a msg element. + * + * Example: + * // Messages: { 'hello': 'Hello $1!' } + * var html = '\ + *

    \ + * \ + * \ + *

    '; + * $( 'body' ).append( $( html ).localize( { 'params': { 'hello': ['world'] } } ) ); + * + * Appends something like this to the body... + *

    + * <b><i>Hello</i> world!</b> + * Hello world! + *

    + * + * Message keys can also be remapped, allowing the same generic template to be used with a variety + * of messages. This is important for improving re-usability of templates. + * + * Example: + * // Messages: { 'good-afternoon': 'Good afternoon' } + * var html = '

    '; + * $( 'body' ).append( $( html ).localize( { 'keys': { 'greeting': 'good-afternoon' } } ) ); + * + * Appends something like this to the body... + *

    Good afternoon

    + * + * Message keys can also be prefixed globally, which is handy when writing extensions, where by + * convention all messages are prefixed with the extension's name. + * + * Example: + * // Messages: { 'teleportation-warning': 'You may not get there all in one piece.' } + * var html = '

    '; + * $( 'body' ).append( $( html ).localize( { 'prefix': 'teleportation-' } ) ); + * + * Appends something like this to the body... + *

    You may not get there all in one piece.

    + * + * @param {Object} options Map of options to be used while localizing + * @param {string} options.prefix String to prepend to all message keys + * @param {Object} options.keys Message key aliases, used for remapping keys to a template + * @param {Object} options.params Lists of parameters to use with certain message keys + * @return {jQuery} + * @chainable + */ + $.fn.localize = function ( options ) { + var $target = this, + attributes = [ 'title', 'alt', 'placeholder' ]; - // Extend options - options = $.extend( { - prefix: '', - keys: {}, - params: {} - }, options ); + // Extend options + options = $.extend( { + prefix: '', + keys: {}, + params: {} + }, options ); - // Elements - // Ok, so here's the story on this selector. In IE 6/7, searching for 'msg' turns up the - // 'html:msg', but searching for 'html:msg' doesn't. In later IE and other browsers, searching - // for 'html:msg' turns up the 'html:msg', but searching for 'msg' doesn't. So searching for - // both 'msg' and 'html:msg' seems to get the job done. This feels pretty icky, though. - $target.find( 'msg,html\\:msg' ).each( function () { - var $el = $( this ); - // Escape by default - if ( $el.attr( 'raw' ) ) { - $el.html( msg( options, $el.attr( 'key' ) ) ); - } else { - $el.text( msg( options, $el.attr( 'key' ) ) ); - } - // Remove wrapper - $el.replaceWith( $el.html() ); - } ); - - // Attributes - // Note: there's no way to prevent escaping of values being injected into attributes, this is - // on purpose, not a design flaw. - $.each( attributes, function ( i, attr ) { - var msgAttr = attr + '-msg'; - $target.find( '[' + msgAttr + ']' ).each( function () { + // Elements + // Ok, so here's the story on this selector. In IE 6/7, searching for 'msg' turns up the + // 'html:msg', but searching for 'html:msg' doesn't. In later IE and other browsers, searching + // for 'html:msg' turns up the 'html:msg', but searching for 'msg' doesn't. So searching for + // both 'msg' and 'html:msg' seems to get the job done. This feels pretty icky, though. + $target.find( 'msg,html\\:msg' ).each( function () { var $el = $( this ); - $el.attr( attr, msg( options, $el.attr( msgAttr ) ) ).removeAttr( msgAttr ); + // Escape by default + if ( $el.attr( 'raw' ) ) { + $el.html( msg( options, $el.attr( 'key' ) ) ); + } else { + $el.text( msg( options, $el.attr( 'key' ) ) ); + } + // Remove wrapper + $el.replaceWith( $el.html() ); } ); - } ); - // HTML, Text for elements which cannot have children e.g. OPTION - $target.find( '[data-msg-text]' ).each( function () { - var $el = $( this ); - $el.text( msg( options, $el.attr( 'data-msg-text' ) ) ); - } ); + // Attributes + // Note: there's no way to prevent escaping of values being injected into attributes, this is + // on purpose, not a design flaw. + $.each( attributes, function ( i, attr ) { + var msgAttr = attr + '-msg'; + $target.find( '[' + msgAttr + ']' ).each( function () { + var $el = $( this ); + $el.attr( attr, msg( options, $el.attr( msgAttr ) ) ).removeAttr( msgAttr ); + } ); + } ); - $target.find( '[data-msg-html]' ).each( function () { - var $el = $( this ); - $el.html( msg( options, $el.attr( 'data-msg-html' ) ) ); - } ); + // HTML, Text for elements which cannot have children e.g. OPTION + $target.find( '[data-msg-text]' ).each( function () { + var $el = $( this ); + $el.text( msg( options, $el.attr( 'data-msg-text' ) ) ); + } ); + + $target.find( '[data-msg-html]' ).each( function () { + var $el = $( this ); + $el.html( msg( options, $el.attr( 'data-msg-html' ) ) ); + } ); - return $target; -}; + return $target; + }; -// Let IE know about the msg tag before it's used... -document.createElement( 'msg' ); + // Let IE know about the msg tag before it's used... + document.createElement( 'msg' ); -/** - * @class jQuery - * @mixins jQuery.plugin.localize - */ + /** + * @class jQuery + * @mixins jQuery.plugin.localize + */ }( jQuery, mediaWiki ) ); diff --git a/resources/src/jquery/jquery.mwExtension.js b/resources/src/jquery/jquery.mwExtension.js index 27ceb2bc45..f9675fa69c 100644 --- a/resources/src/jquery/jquery.mwExtension.js +++ b/resources/src/jquery/jquery.mwExtension.js @@ -40,10 +40,11 @@ return false; }, compareArray: function ( arrThis, arrAgainst ) { + var i; if ( arrThis.length !== arrAgainst.length ) { return false; } - for ( var i = 0; i < arrThis.length; i++ ) { + for ( i = 0; i < arrThis.length; i++ ) { if ( $.isArray( arrThis[ i ] ) ) { if ( !$.compareArray( arrThis[ i ], arrAgainst[ i ] ) ) { return false; @@ -124,4 +125,4 @@ return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ); }, 'Use mediawiki.RegExp instead.' ); -} )( jQuery, mediaWiki ); +}( jQuery, mediaWiki ) ); diff --git a/resources/src/jquery/jquery.placeholder.js b/resources/src/jquery/jquery.placeholder.js index c472ac7b58..ae9c943869 100644 --- a/resources/src/jquery/jquery.placeholder.js +++ b/resources/src/jquery/jquery.placeholder.js @@ -35,7 +35,7 @@ function args( elem ) { // Return an object of element attributes var newAttrs = {}, - rinlinejQuery = /^jQuery\d+$/; + rinlinejQuery = /^jQuery\d+$/; $.each( elem.attributes, function ( i, attr ) { if ( attr.specified && !rinlinejQuery.test( attr.name ) ) { newAttrs[ attr.name ] = attr.value; @@ -46,7 +46,7 @@ function clearPlaceholder( event, value ) { var input = this, - $input = $( input ); + $input = $( input ); if ( input.value === $input.attr( 'placeholder' ) && $input.hasClass( 'placeholder' ) ) { if ( $input.data( 'placeholder-password' ) ) { $input = $input.hide().next().show().attr( 'id', $input.removeAttr( 'id' ).data( 'placeholder-id' ) ); @@ -68,9 +68,9 @@ function setPlaceholder() { var $replacement, - input = this, - $input = $( input ), - id = this.id; + input = this, + $input = $( input ), + id = this.id; if ( !input.value ) { if ( input.type === 'password' ) { if ( !$input.data( 'placeholder-textinput' ) ) { @@ -105,7 +105,7 @@ function changePlaceholder( text ) { var hasArgs = arguments.length, - $input = this; + $input = this; if ( hasArgs ) { if ( $input.attr( 'placeholder' ) !== text ) { $input.prop( 'placeholder', text ); diff --git a/resources/src/jquery/jquery.qunit.completenessTest.js b/resources/src/jquery/jquery.qunit.completenessTest.js index 8d263fbf2e..68bf83001e 100644 --- a/resources/src/jquery/jquery.qunit.completenessTest.js +++ b/resources/src/jquery/jquery.qunit.completenessTest.js @@ -17,9 +17,9 @@ var util, hasOwn = Object.prototype.hasOwnProperty, - log = ( window.console && window.console.log ) - ? function () { return window.console.log.apply( window.console, arguments ); } - : function () {}; + log = ( window.console && window.console.log ) ? + function () { return window.console.log.apply( window.console, arguments ); } : + function () {}; // Simplified version of a few jQuery methods, except that they don't // call other jQuery methods. Required to be able to run the CompletenessTest @@ -94,11 +94,11 @@ } ); QUnit.done( function () { + var toolbar, testResults, cntTotal, cntCalled, cntMissing; + that.populateMissingTests(); log( 'CompletenessTest/populateMissingTests', that ); - var toolbar, testResults, cntTotal, cntCalled, cntMissing; - cntTotal = util.keys( that.injectionTracker ).length; cntCalled = util.keys( that.methodCallTracker ).length; cntMissing = util.keys( that.missingTests ).length; @@ -292,11 +292,7 @@ // Make the spy inherit from the original so that its static methods are also // visible in the spy (e.g. when we inject a check into mw.log, mw.log.warn // must remain accessible). - // XXX: https://github.com/jshint/jshint/issues/2656 - /*jshint ignore:start */ - /*jshint proto:true */ spy.__proto__ = val; - /*jshint ignore:end */ // Objects are by reference, members (unless objects) are not. obj[ key ] = spy; diff --git a/resources/src/jquery/jquery.spinner.js b/resources/src/jquery/jquery.spinner.js index af5a97d6a4..9079cc09b7 100644 --- a/resources/src/jquery/jquery.spinner.js +++ b/resources/src/jquery/jquery.spinner.js @@ -59,6 +59,8 @@ * @return {jQuery} */ createSpinner: function ( opts ) { + var $spinner; + if ( opts !== undefined && $.type( opts ) !== 'object' ) { opts = { id: opts @@ -67,7 +69,7 @@ opts = $.extend( {}, defaults, opts ); - var $spinner = $( '
    ' ).addClass( 'mw-spinner' ).attr( 'title', '...' ); + $spinner = $( '
    ' ).addClass( 'mw-spinner' ).attr( 'title', '...' ); if ( opts.id !== undefined ) { $spinner.attr( 'id', 'mw-spinner-' + opts.id ); } diff --git a/resources/src/jquery/jquery.suggestions.js b/resources/src/jquery/jquery.suggestions.js index 884ecb6e96..f3e4e09313 100644 --- a/resources/src/jquery/jquery.suggestions.js +++ b/resources/src/jquery/jquery.suggestions.js @@ -353,7 +353,6 @@ $results.empty(); expWidth = -1; for ( i = 0; i < context.config.suggestions.length; i++ ) { - /*jshint loopfunc:true */ text = context.config.suggestions[ i ]; $result = $( '
    ' ) .addClass( 'suggestions-result' ) @@ -758,9 +757,9 @@ 46, // delete 8 // backspace ]; - if ( context.data.keypressedCount === 0 - && e.which === context.data.keypressed - && $.inArray( e.which, allowed ) !== -1 + if ( context.data.keypressedCount === 0 && + e.which === context.data.keypressed && + $.inArray( e.which, allowed ) !== -1 ) { $.suggestions.keypress( e, context, context.data.keypressed ); } diff --git a/resources/src/jquery/jquery.tablesorter.js b/resources/src/jquery/jquery.tablesorter.js index 62be0d8c4a..3b85f9a4c2 100644 --- a/resources/src/jquery/jquery.tablesorter.js +++ b/resources/src/jquery/jquery.tablesorter.js @@ -442,10 +442,11 @@ } function setHeadersCss( table, $headers, list, css, msg, columnToHeader ) { + var i, len; // Remove all header information and reset titles to default message $headers.removeClass( css[ 0 ] ).removeClass( css[ 1 ] ).attr( 'title', msg[ 1 ] ); - for ( var i = 0; i < list.length; i++ ) { + for ( i = 0, len = list.length; i < len; i++ ) { $headers .eq( columnToHeader[ list[ i ][ 0 ] ] ) .addClass( css[ list[ i ][ 1 ] ] ) @@ -686,12 +687,10 @@ } function buildCollationTable() { + var key, keys = []; ts.collationTable = mw.config.get( 'tableSorterCollation' ); ts.collationRegex = null; if ( ts.collationTable ) { - var key, - keys = []; - // Build array of key names for ( key in ts.collationTable ) { // Check hasOwn to be safe @@ -755,298 +754,299 @@ /* Public scope */ $.tablesorter = { - defaultOptions: { - cssHeader: 'headerSort', - cssAsc: 'headerSortUp', - cssDesc: 'headerSortDown', - cssChildRow: 'expand-child', - sortMultiSortKey: 'shiftKey', - unsortableClass: 'unsortable', - parsers: [], - cancelSelection: true, - sortList: [], - headerList: [], - headerToColumns: [], - columnToHeader: [], - columns: 0 - }, - - dateRegex: [], - monthNames: {}, - - /** - * @param {jQuery} $tables - * @param {Object} [settings] - */ - construct: function ( $tables, settings ) { - return $tables.each( function ( i, table ) { - // Declare and cache. - var $headers, cache, config, sortCSS, sortMsg, - $table = $( table ), - firstTime = true; - - // Quit if no tbody - if ( !table.tBodies ) { + defaultOptions: { + cssHeader: 'headerSort', + cssAsc: 'headerSortUp', + cssDesc: 'headerSortDown', + cssChildRow: 'expand-child', + sortMultiSortKey: 'shiftKey', + unsortableClass: 'unsortable', + parsers: [], + cancelSelection: true, + sortList: [], + headerList: [], + headerToColumns: [], + columnToHeader: [], + columns: 0 + }, + + dateRegex: [], + monthNames: {}, + + /** + * @param {jQuery} $tables + * @param {Object} [settings] + */ + construct: function ( $tables, settings ) { + return $tables.each( function ( i, table ) { + // Declare and cache. + var $headers, cache, config, sortCSS, sortMsg, + $table = $( table ), + firstTime = true; + + // Quit if no tbody + if ( !table.tBodies ) { + return; + } + if ( !table.tHead ) { + // No thead found. Look for rows with s and + // move them into a tag or a tag + emulateTHeadAndFoot( $table ); + + // Still no thead? Then quit + if ( !table.tHead ) { return; } - if ( !table.tHead ) { - // No thead found. Look for rows with s and - // move them into a tag or a tag - emulateTHeadAndFoot( $table ); + } + $table.addClass( 'jquery-tablesorter' ); - // Still no thead? Then quit - if ( !table.tHead ) { - return; - } - } - $table.addClass( 'jquery-tablesorter' ); - - // Merge and extend - config = $.extend( {}, $.tablesorter.defaultOptions, settings ); - - // Save the settings where they read - $.data( table, 'tablesorter', { config: config } ); - - // Get the CSS class names, could be done elsewhere - sortCSS = [ config.cssAsc, config.cssDesc ]; - // Messages tell the the user what the *next* state will be - // so are in reverse order to the CSS classes. - sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ]; - - // Build headers - $headers = buildHeaders( table, sortMsg ); - - // Grab and process locale settings. - buildTransformTable(); - buildDateTable(); - - // Precaching regexps can bring 10 fold - // performance improvements in some browsers. - cacheRegexs(); - - function setupForFirstSort() { - firstTime = false; - - // Defer buildCollationTable to first sort. As user and site scripts - // may customize tableSorterCollation but load after $.ready(), other - // scripts may call .tablesorter() before they have done the - // tableSorterCollation customizations. - buildCollationTable(); - - // Legacy fix of .sortbottoms - // Wrap them inside a tfoot (because that's what they actually want to be) - // and put the at the end of the - var $tfoot, - $sortbottoms = $table.find( '> tbody > tr.sortbottom' ); - if ( $sortbottoms.length ) { - $tfoot = $table.children( 'tfoot' ); - if ( $tfoot.length ) { - $tfoot.eq( 0 ).prepend( $sortbottoms ); - } else { - $table.append( $( '' ).append( $sortbottoms ) ); - } - } + // Merge and extend + config = $.extend( {}, $.tablesorter.defaultOptions, settings ); - explodeRowspans( $table ); - manageColspans( $table ); + // Save the settings where they read + $.data( table, 'tablesorter', { config: config } ); - // Try to auto detect column type, and store in tables config - config.parsers = buildParserCache( table, $headers ); - } + // Get the CSS class names, could be done elsewhere + sortCSS = [ config.cssAsc, config.cssDesc ]; + // Messages tell the the user what the *next* state will be + // so are in reverse order to the CSS classes. + sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ]; - // Apply event handling to headers - // this is too big, perhaps break it out? - $headers.on( 'keypress click', function ( e ) { - var cell, $cell, columns, newSortList, i, - totalRows, - j, s, o; - - if ( e.type === 'click' && e.target.nodeName.toLowerCase() === 'a' ) { - // The user clicked on a link inside a table header. - // Do nothing and let the default link click action continue. - return true; - } + // Build headers + $headers = buildHeaders( table, sortMsg ); - if ( e.type === 'keypress' && e.which !== 13 ) { - // Only handle keypresses on the "Enter" key. - return true; - } + // Grab and process locale settings. + buildTransformTable(); + buildDateTable(); - if ( firstTime ) { - setupForFirstSort(); + // Precaching regexps can bring 10 fold + // performance improvements in some browsers. + cacheRegexs(); + + function setupForFirstSort() { + var $tfoot, $sortbottoms; + + firstTime = false; + + // Defer buildCollationTable to first sort. As user and site scripts + // may customize tableSorterCollation but load after $.ready(), other + // scripts may call .tablesorter() before they have done the + // tableSorterCollation customizations. + buildCollationTable(); + + // Legacy fix of .sortbottoms + // Wrap them inside a tfoot (because that's what they actually want to be) + // and put the at the end of the
    + $sortbottoms = $table.find( '> tbody > tr.sortbottom' ); + if ( $sortbottoms.length ) { + $tfoot = $table.children( 'tfoot' ); + if ( $tfoot.length ) { + $tfoot.eq( 0 ).prepend( $sortbottoms ); + } else { + $table.append( $( '' ).append( $sortbottoms ) ); } + } - // Build the cache for the tbody cells - // to share between calculations for this sort action. - // Re-calculated each time a sort action is performed due to possiblity - // that sort values change. Shouldn't be too expensive, but if it becomes - // too slow an event based system should be implemented somehow where - // cells get event .change() and bubbles up to the
    here - cache = buildCache( table ); - - totalRows = ( $table[ 0 ].tBodies[ 0 ] && $table[ 0 ].tBodies[ 0 ].rows.length ) || 0; - if ( totalRows > 0 ) { - cell = this; - $cell = $( cell ); - - // Get current column sort order - $cell.data( { - order: $cell.data( 'count' ) % 2, - count: $cell.data( 'count' ) + 1 - } ); + explodeRowspans( $table ); + manageColspans( $table ); - cell = this; - // Get current column index - columns = config.headerToColumns[ $cell.data( 'headerIndex' ) ]; - newSortList = $.map( columns, function ( c ) { - // jQuery "helpfully" flattens the arrays... - return [ [ c, $cell.data( 'order' ) ] ]; - } ); - // Index of first column belonging to this header - i = columns[ 0 ]; + // Try to auto detect column type, and store in tables config + config.parsers = buildParserCache( table, $headers ); + } - if ( !e[ config.sortMultiSortKey ] ) { - // User only wants to sort on one column set - // Flush the sort list and add new columns - config.sortList = newSortList; - } else { - // Multi column sorting - // It is not possible for one column to belong to multiple headers, - // so this is okay - we don't need to check for every value in the columns array - if ( isValueInArray( i, config.sortList ) ) { - // The user has clicked on an already sorted column. - // Reverse the sorting direction for all tables. - for ( j = 0; j < config.sortList.length; j++ ) { - s = config.sortList[ j ]; - o = config.headerList[ config.columnToHeader[ s[ 0 ] ] ]; - if ( isValueInArray( s[ 0 ], newSortList ) ) { - $( o ).data( 'count', s[ 1 ] + 1 ); - s[ 1 ] = $( o ).data( 'count' ) % 2; - } + // Apply event handling to headers + // this is too big, perhaps break it out? + $headers.on( 'keypress click', function ( e ) { + var cell, $cell, columns, newSortList, i, + totalRows, + j, s, o; + + if ( e.type === 'click' && e.target.nodeName.toLowerCase() === 'a' ) { + // The user clicked on a link inside a table header. + // Do nothing and let the default link click action continue. + return true; + } + + if ( e.type === 'keypress' && e.which !== 13 ) { + // Only handle keypresses on the "Enter" key. + return true; + } + + if ( firstTime ) { + setupForFirstSort(); + } + + // Build the cache for the tbody cells + // to share between calculations for this sort action. + // Re-calculated each time a sort action is performed due to possiblity + // that sort values change. Shouldn't be too expensive, but if it becomes + // too slow an event based system should be implemented somehow where + // cells get event .change() and bubbles up to the
    here + cache = buildCache( table ); + + totalRows = ( $table[ 0 ].tBodies[ 0 ] && $table[ 0 ].tBodies[ 0 ].rows.length ) || 0; + if ( totalRows > 0 ) { + cell = this; + $cell = $( cell ); + + // Get current column sort order + $cell.data( { + order: $cell.data( 'count' ) % 2, + count: $cell.data( 'count' ) + 1 + } ); + + cell = this; + // Get current column index + columns = config.headerToColumns[ $cell.data( 'headerIndex' ) ]; + newSortList = $.map( columns, function ( c ) { + // jQuery "helpfully" flattens the arrays... + return [ [ c, $cell.data( 'order' ) ] ]; + } ); + // Index of first column belonging to this header + i = columns[ 0 ]; + + if ( !e[ config.sortMultiSortKey ] ) { + // User only wants to sort on one column set + // Flush the sort list and add new columns + config.sortList = newSortList; + } else { + // Multi column sorting + // It is not possible for one column to belong to multiple headers, + // so this is okay - we don't need to check for every value in the columns array + if ( isValueInArray( i, config.sortList ) ) { + // The user has clicked on an already sorted column. + // Reverse the sorting direction for all tables. + for ( j = 0; j < config.sortList.length; j++ ) { + s = config.sortList[ j ]; + o = config.headerList[ config.columnToHeader[ s[ 0 ] ] ]; + if ( isValueInArray( s[ 0 ], newSortList ) ) { + $( o ).data( 'count', s[ 1 ] + 1 ); + s[ 1 ] = $( o ).data( 'count' ) % 2; } - } else { - // Add columns to sort list array - config.sortList = config.sortList.concat( newSortList ); } + } else { + // Add columns to sort list array + config.sortList = config.sortList.concat( newSortList ); } + } - // Reset order/counts of cells not affected by sorting - setHeadersOrder( $headers, config.sortList, config.headerToColumns ); + // Reset order/counts of cells not affected by sorting + setHeadersOrder( $headers, config.sortList, config.headerToColumns ); - // Set CSS for headers - setHeadersCss( $table[ 0 ], $headers, config.sortList, sortCSS, sortMsg, config.columnToHeader ); - appendToTable( - $table[ 0 ], multisort( $table[ 0 ], config.sortList, cache ) - ); + // Set CSS for headers + setHeadersCss( $table[ 0 ], $headers, config.sortList, sortCSS, sortMsg, config.columnToHeader ); + appendToTable( + $table[ 0 ], multisort( $table[ 0 ], config.sortList, cache ) + ); - // Stop normal event by returning false - return false; - } + // Stop normal event by returning false + return false; + } - // Cancel selection - } ).mousedown( function () { - if ( config.cancelSelection ) { - this.onselectstart = function () { - return false; - }; + // Cancel selection + } ).mousedown( function () { + if ( config.cancelSelection ) { + this.onselectstart = function () { return false; - } - } ); + }; + return false; + } + } ); - /** - * Sorts the table. If no sorting is specified by passing a list of sort - * objects, the table is sorted according to the initial sorting order. - * Passing an empty array will reset sorting (basically just reset the headers - * making the table appear unsorted). - * - * @param {Array} [sortList] List of sort objects. - */ - $table.data( 'tablesorter' ).sort = function ( sortList ) { - - if ( firstTime ) { - setupForFirstSort(); - } + /** + * Sorts the table. If no sorting is specified by passing a list of sort + * objects, the table is sorted according to the initial sorting order. + * Passing an empty array will reset sorting (basically just reset the headers + * making the table appear unsorted). + * + * @param {Array} [sortList] List of sort objects. + */ + $table.data( 'tablesorter' ).sort = function ( sortList ) { + + if ( firstTime ) { + setupForFirstSort(); + } - if ( sortList === undefined ) { - sortList = config.sortList; - } else if ( sortList.length > 0 ) { - sortList = convertSortList( sortList ); - } + if ( sortList === undefined ) { + sortList = config.sortList; + } else if ( sortList.length > 0 ) { + sortList = convertSortList( sortList ); + } - // Set each column's sort count to be able to determine the correct sort - // order when clicking on a header cell the next time - setHeadersOrder( $headers, sortList, config.headerToColumns ); + // Set each column's sort count to be able to determine the correct sort + // order when clicking on a header cell the next time + setHeadersOrder( $headers, sortList, config.headerToColumns ); - // re-build the cache for the tbody cells - cache = buildCache( table ); + // re-build the cache for the tbody cells + cache = buildCache( table ); - // set css for headers - setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, config.columnToHeader ); + // set css for headers + setHeadersCss( table, $headers, sortList, sortCSS, sortMsg, config.columnToHeader ); - // sort the table and append it to the dom - appendToTable( table, multisort( table, sortList, cache ) ); - }; + // sort the table and append it to the dom + appendToTable( table, multisort( table, sortList, cache ) ); + }; - // sort initially - if ( config.sortList.length > 0 ) { - config.sortList = convertSortList( config.sortList ); - $table.data( 'tablesorter' ).sort(); - } + // sort initially + if ( config.sortList.length > 0 ) { + config.sortList = convertSortList( config.sortList ); + $table.data( 'tablesorter' ).sort(); + } - } ); - }, + } ); + }, - addParser: function ( parser ) { - if ( !getParserById( parser.id ) ) { - parsers.push( parser ); - } - }, - - formatDigit: function ( s ) { - var out, c, p, i; - if ( ts.transformTable !== false ) { - out = ''; - for ( p = 0; p < s.length; p++ ) { - c = s.charAt( p ); - if ( c in ts.transformTable ) { - out += ts.transformTable[ c ]; - } else { - out += c; - } + addParser: function ( parser ) { + if ( !getParserById( parser.id ) ) { + parsers.push( parser ); + } + }, + + formatDigit: function ( s ) { + var out, c, p, i; + if ( ts.transformTable !== false ) { + out = ''; + for ( p = 0; p < s.length; p++ ) { + c = s.charAt( p ); + if ( c in ts.transformTable ) { + out += ts.transformTable[ c ]; + } else { + out += c; } - s = out; } - i = parseFloat( s.replace( /[, ]/g, '' ).replace( '\u2212', '-' ) ); - return isNaN( i ) ? 0 : i; - }, + s = out; + } + i = parseFloat( s.replace( /[, ]/g, '' ).replace( '\u2212', '-' ) ); + return isNaN( i ) ? 0 : i; + }, - formatFloat: function ( s ) { - var i = parseFloat( s ); - return isNaN( i ) ? 0 : i; - }, + formatFloat: function ( s ) { + var i = parseFloat( s ); + return isNaN( i ) ? 0 : i; + }, - formatInt: function ( s ) { - var i = parseInt( s, 10 ); - return isNaN( i ) ? 0 : i; - }, + formatInt: function ( s ) { + var i = parseInt( s, 10 ); + return isNaN( i ) ? 0 : i; + }, - clearTableBody: function ( table ) { - $( table.tBodies[ 0 ] ).empty(); - }, + clearTableBody: function ( table ) { + $( table.tBodies[ 0 ] ).empty(); + }, - getParser: function ( id ) { - buildTransformTable(); - buildDateTable(); - cacheRegexs(); - buildCollationTable(); + getParser: function ( id ) { + buildTransformTable(); + buildDateTable(); + cacheRegexs(); + buildCollationTable(); - return getParserById( id ); - }, + return getParserById( id ); + }, - getParsers: function () { // for table diagnosis - return parsers; - } - }; + getParsers: function () { // for table diagnosis + return parsers; + } + }; // Shortcut ts = $.tablesorter; @@ -1063,9 +1063,10 @@ return true; }, format: function ( s ) { + var tsc; s = $.trim( s.toLowerCase() ); if ( ts.collationRegex ) { - var tsc = ts.collationTable; + tsc = ts.collationTable; s = s.replace( ts.collationRegex, function ( match ) { var r = tsc[ match ] ? tsc[ match ] : tsc[ match.toUpperCase() ]; return r.toLowerCase(); @@ -1135,7 +1136,7 @@ if ( !matches ) { return $.tablesorter.formatFloat( 0 ); } - isodate = new Date( matches[ 2 ] + '/' + matches[ 3 ] + '/' + matches[ 1 ] ); + isodate = new Date( matches[ 2 ] + '/' + matches[ 3 ] + '/' + matches[ 1 ] ); } else { matches = s.match( ts.rgx.isoDate[ 0 ] ); if ( !matches ) { diff --git a/resources/src/jquery/jquery.textSelection.js b/resources/src/jquery/jquery.textSelection.js index 5e93ba62c1..b6fbe35985 100644 --- a/resources/src/jquery/jquery.textSelection.js +++ b/resources/src/jquery/jquery.textSelection.js @@ -34,10 +34,11 @@ * Helper function to get an IE TextRange object for an element */ function rangeForElementIE( e ) { + var sel; if ( e.nodeName.toLowerCase() === 'input' ) { return e.createTextRange(); } else { - var sel = document.body.createTextRange(); + sel = document.body.createTextRange(); sel.moveToElementText( e ); return sel; } @@ -275,7 +276,7 @@ var caretPos = 0, endPos = 0, preText, rawPreText, periText, - rawPeriText, postText, rawPostText, + rawPeriText, postText, // IE Support preFinished, periFinished, @@ -310,7 +311,7 @@ // Load the text values we need to compare preText = rawPreText = preRange.text; periText = rawPeriText = periRange.text; - postText = rawPostText = postRange.text; + postText = postRange.text; /* * Check each range for trimmed newlines by shrinking the range by 1 @@ -347,9 +348,7 @@ postFinished = true; } else { postRange.moveEnd( 'character', -1 ); - if ( postRange.text === postText ) { - rawPostText += '\r\n'; - } else { + if ( postRange.text !== postText ) { postFinished = true; } } 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.action/mediawiki.action.edit.stash.js b/resources/src/mediawiki.action/mediawiki.action.edit.stash.js index 6c639575da..c8d3fad631 100644 --- a/resources/src/mediawiki.action/mediawiki.action.edit.stash.js +++ b/resources/src/mediawiki.action/mediawiki.action.edit.stash.js @@ -1,6 +1,7 @@ /*! * Scripts for pre-emptive edit preparing on action=edit */ +/* eslint-disable no-use-before-define */ ( function ( mw, $ ) { if ( !mw.config.get( 'wgAjaxEditStash' ) ) { return; @@ -138,10 +139,10 @@ if ( // Reverts may involve use (undo) links; stash as they review the diff. // Since the form has a pre-filled summary, stash the edit immediately. - mw.util.getParamValue( 'undo' ) !== null + mw.util.getParamValue( 'undo' ) !== null || // Pressing "show changes" and "preview" also signify that the user will // probably save the page soon - || $.inArray( $form.find( '#mw-edit-mode' ).val(), [ 'preview', 'diff' ] ) > -1 + $.inArray( $form.find( '#mw-edit-mode' ).val(), [ 'preview', 'diff' ] ) > -1 ) { checkStash(); } diff --git a/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js b/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js index 2be29f0963..7439754668 100644 --- a/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js +++ b/resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js @@ -4,11 +4,12 @@ ( function ( mw, $ ) { $( function () { mw.util.$content.dblclick( function ( e ) { + var $a; // Recheck preference so extensions can do a hack to disable this code. if ( parseInt( mw.user.options.get( 'editondblclick' ), 10 ) ) { e.preventDefault(); // Trigger native HTMLElement click instead of opening URL (bug 43052) - var $a = $( '#ca-edit a' ); + $a = $( '#ca-edit a' ); // Not every page has an edit link (bug 57713) if ( $a.length ) { $a.get( 0 ).click(); diff --git a/resources/src/mediawiki.action/mediawiki.action.view.redirect.js b/resources/src/mediawiki.action/mediawiki.action.view.redirect.js index 29a5a79f9b..39a122d981 100644 --- a/resources/src/mediawiki.action/mediawiki.action.view.redirect.js +++ b/resources/src/mediawiki.action/mediawiki.action.view.redirect.js @@ -28,7 +28,7 @@ } // Note that this will update the hash in a modern browser, retaining back behaviour - history.replaceState( /*data=*/ history.state, /*title=*/ document.title, /*url=*/ canonical ); + history.replaceState( /* data= */ history.state, /* title= */ document.title, /* url= */ canonical ); if ( shouldChangeFragment ) { // Specification for history.replaceState() doesn't require browser to scroll, // so scroll to be sure (see also T110501). Support for IE9 and IE10. diff --git a/resources/src/mediawiki.language/languages/ga.js b/resources/src/mediawiki.language/languages/ga.js index a4c911a339..e1362114c1 100644 --- a/resources/src/mediawiki.language/languages/ga.js +++ b/resources/src/mediawiki.language/languages/ga.js @@ -3,7 +3,6 @@ */ mediaWiki.language.convertGrammar = function ( word, form ) { - /*jshint onecase:true */ var grammarForms = mediaWiki.language.getData( 'ga', 'grammarForms' ); if ( grammarForms && grammarForms[ form ] ) { return grammarForms[ form ][ word ]; diff --git a/resources/src/mediawiki.language/languages/he.js b/resources/src/mediawiki.language/languages/he.js index 945f02fed2..5bf8c4df91 100644 --- a/resources/src/mediawiki.language/languages/he.js +++ b/resources/src/mediawiki.language/languages/he.js @@ -21,7 +21,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) { } // Add a hyphen (maqaf) before numbers and non-Hebrew letters - if ( word.slice( 0, 1 ) < 'א' || word.slice( 0, 1 ) > 'ת' ) { + if ( word.slice( 0, 1 ) < 'א' || word.slice( 0, 1 ) > 'ת' ) { word = '־' + word; } } diff --git a/resources/src/mediawiki.language/languages/hy.js b/resources/src/mediawiki.language/languages/hy.js index 935d466d14..bb6f61dffe 100644 --- a/resources/src/mediawiki.language/languages/hy.js +++ b/resources/src/mediawiki.language/languages/hy.js @@ -3,7 +3,6 @@ */ mediaWiki.language.convertGrammar = function ( word, form ) { - /*jshint onecase:true */ var grammarForms = mediaWiki.language.getData( 'hy', 'grammarForms' ); if ( grammarForms && grammarForms[ form ] ) { return grammarForms[ form ][ word ]; diff --git a/resources/src/mediawiki.language/mediawiki.language.js b/resources/src/mediawiki.language/mediawiki.language.js index cf3ef79746..fc2af3d0d8 100644 --- a/resources/src/mediawiki.language/mediawiki.language.js +++ b/resources/src/mediawiki.language/mediawiki.language.js @@ -3,154 +3,155 @@ */ ( function ( mw, $ ) { -/** - * @class mw.language - */ -$.extend( mw.language, { - /** - * Process the PLURAL template substitution - * - * @private - * @param {Object} template Template object - * @param {string} template.title - * @param {Array} template.parameters - * @return {string} + * @class mw.language */ - procPLURAL: function ( template ) { - if ( template.title && template.parameters && mw.language.convertPlural ) { - // Check if we have forms to replace - if ( template.parameters.length === 0 ) { - return ''; + $.extend( mw.language, { + + /** + * Process the PLURAL template substitution + * + * @private + * @param {Object} template Template object + * @param {string} template.title + * @param {Array} template.parameters + * @return {string} + */ + procPLURAL: function ( template ) { + var count; + if ( template.title && template.parameters && mw.language.convertPlural ) { + // Check if we have forms to replace + if ( template.parameters.length === 0 ) { + return ''; + } + // Restore the count into a Number ( if it got converted earlier ) + count = mw.language.convertNumber( template.title, true ); + // Do convertPlural call + return mw.language.convertPlural( parseInt( count, 10 ), template.parameters ); } - // Restore the count into a Number ( if it got converted earlier ) - var count = mw.language.convertNumber( template.title, true ); - // Do convertPlural call - return mw.language.convertPlural( parseInt( count, 10 ), template.parameters ); - } - // Could not process plural return first form or nothing - if ( template.parameters[ 0 ] ) { - return template.parameters[ 0 ]; - } - return ''; - }, + // Could not process plural return first form or nothing + if ( template.parameters[ 0 ] ) { + return template.parameters[ 0 ]; + } + return ''; + }, - /** - * Plural form transformations, needed for some languages. - * - * @param {number} count Non-localized quantifier - * @param {Array} forms List of plural forms - * @param {Object} [explicitPluralForms] List of explicit plural forms - * @return {string} Correct form for quantifier in this language - */ - convertPlural: function ( count, forms, explicitPluralForms ) { - var pluralRules, - pluralFormIndex = 0; + /** + * Plural form transformations, needed for some languages. + * + * @param {number} count Non-localized quantifier + * @param {Array} forms List of plural forms + * @param {Object} [explicitPluralForms] List of explicit plural forms + * @return {string} Correct form for quantifier in this language + */ + convertPlural: function ( count, forms, explicitPluralForms ) { + var pluralRules, + pluralFormIndex = 0; - if ( explicitPluralForms && ( explicitPluralForms[ count ] !== undefined ) ) { - return explicitPluralForms[ count ]; - } + if ( explicitPluralForms && ( explicitPluralForms[ count ] !== undefined ) ) { + return explicitPluralForms[ count ]; + } - if ( !forms || forms.length === 0 ) { - return ''; - } + if ( !forms || forms.length === 0 ) { + return ''; + } - pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' ); - if ( !pluralRules ) { - // default fallback. - return ( count === 1 ) ? forms[ 0 ] : forms[ 1 ]; - } - pluralFormIndex = mw.cldr.getPluralForm( count, pluralRules ); - pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 ); - return forms[ pluralFormIndex ]; - }, + pluralRules = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'pluralRules' ); + if ( !pluralRules ) { + // default fallback. + return ( count === 1 ) ? forms[ 0 ] : forms[ 1 ]; + } + pluralFormIndex = mw.cldr.getPluralForm( count, pluralRules ); + pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 ); + return forms[ pluralFormIndex ]; + }, - /** - * Pads an array to a specific length by copying the last one element. - * - * @private - * @param {Array} forms Number of forms given to convertPlural - * @param {number} count Number of forms required - * @return {Array} Padded array of forms - */ - preConvertPlural: function ( forms, count ) { - while ( forms.length < count ) { - forms.push( forms[ forms.length - 1 ] ); - } - return forms; - }, + /** + * Pads an array to a specific length by copying the last one element. + * + * @private + * @param {Array} forms Number of forms given to convertPlural + * @param {number} count Number of forms required + * @return {Array} Padded array of forms + */ + preConvertPlural: function ( forms, count ) { + while ( forms.length < count ) { + forms.push( forms[ forms.length - 1 ] ); + } + return forms; + }, - /** - * Provides an alternative text depending on specified gender. - * - * Usage in message text: `{{gender:[gender|user object]|masculine|feminine|neutral}}`. - * If second or third parameter are not specified, masculine is used. - * - * These details may be overridden per language. - * - * @param {string} gender 'male', 'female', or anything else for neutral. - * @param {Array} forms List of gender forms - * @return {string} - */ - gender: function ( gender, forms ) { - if ( !forms || forms.length === 0 ) { - return ''; - } - forms = mw.language.preConvertPlural( forms, 2 ); - if ( gender === 'male' ) { - return forms[ 0 ]; - } - if ( gender === 'female' ) { - return forms[ 1 ]; - } - return ( forms.length === 3 ) ? forms[ 2 ] : forms[ 0 ]; - }, + /** + * Provides an alternative text depending on specified gender. + * + * Usage in message text: `{{gender:[gender|user object]|masculine|feminine|neutral}}`. + * If second or third parameter are not specified, masculine is used. + * + * These details may be overridden per language. + * + * @param {string} gender 'male', 'female', or anything else for neutral. + * @param {Array} forms List of gender forms + * @return {string} + */ + gender: function ( gender, forms ) { + if ( !forms || forms.length === 0 ) { + return ''; + } + forms = mw.language.preConvertPlural( forms, 2 ); + if ( gender === 'male' ) { + return forms[ 0 ]; + } + if ( gender === 'female' ) { + return forms[ 1 ]; + } + return ( forms.length === 3 ) ? forms[ 2 ] : forms[ 0 ]; + }, - /** - * Grammatical transformations, needed for inflected languages. - * Invoked by putting `{{grammar:form|word}}` in a message. - * - * The rules can be defined in $wgGrammarForms global or computed - * dynamically by overriding this method per language. - * - * @param {string} word - * @param {string} form - * @return {string} - */ - convertGrammar: function ( word, form ) { - var grammarForms = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'grammarForms' ); - if ( grammarForms && grammarForms[ form ] ) { - return grammarForms[ form ][ word ] || word; - } - return word; - }, + /** + * Grammatical transformations, needed for inflected languages. + * Invoked by putting `{{grammar:form|word}}` in a message. + * + * The rules can be defined in $wgGrammarForms global or computed + * dynamically by overriding this method per language. + * + * @param {string} word + * @param {string} form + * @return {string} + */ + convertGrammar: function ( word, form ) { + var grammarForms = mw.language.getData( mw.config.get( 'wgUserLanguage' ), 'grammarForms' ); + if ( grammarForms && grammarForms[ form ] ) { + return grammarForms[ form ][ word ] || word; + } + return word; + }, - /** - * Turn a list of string into a simple list using commas and 'and'. - * - * See Language::listToText in languages/Language.php - * - * @param {string[]} list - * @return {string} - */ - listToText: function ( list ) { - var text = '', - i = 0; + /** + * Turn a list of string into a simple list using commas and 'and'. + * + * See Language::listToText in languages/Language.php + * + * @param {string[]} list + * @return {string} + */ + listToText: function ( list ) { + var text = '', + i = 0; - for ( ; i < list.length; i++ ) { - text += list[ i ]; - if ( list.length - 2 === i ) { - text += mw.msg( 'and' ) + mw.msg( 'word-separator' ); - } else if ( list.length - 1 !== i ) { - text += mw.msg( 'comma-separator' ); + for ( ; i < list.length; i++ ) { + text += list[ i ]; + if ( list.length - 2 === i ) { + text += mw.msg( 'and' ) + mw.msg( 'word-separator' ); + } else if ( list.length - 1 !== i ) { + text += mw.msg( 'comma-separator' ); + } } - } - return text; - }, + return text; + }, - setSpecialCharacters: function ( data ) { - this.specialCharacters = data; - } -} ); + setSpecialCharacters: function ( data ) { + this.specialCharacters = data; + } + } ); }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.language/mediawiki.language.numbers.js b/resources/src/mediawiki.language/mediawiki.language.numbers.js index 268985f8e4..11926508b5 100644 --- a/resources/src/mediawiki.language/mediawiki.language.numbers.js +++ b/resources/src/mediawiki.language/mediawiki.language.numbers.js @@ -15,11 +15,12 @@ * @return {string} */ function replicate( str, num ) { + var buf = []; + if ( num <= 0 || !str ) { return ''; } - var buf = []; while ( num-- ) { buf.push( str ); } @@ -43,12 +44,14 @@ * @return {string} */ function pad( text, size, ch, end ) { + var out, padStr; + if ( !ch ) { ch = '0'; } - var out = String( text ), - padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) ); + out = String( text ); + padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) ); return end ? out + padStr : padStr + out; } @@ -69,15 +72,6 @@ * @return {string} */ function commafyNumber( value, pattern, options ) { - options = options || { - group: ',', - decimal: '.' - }; - - if ( isNaN( value ) ) { - return value; - } - var padLength, patternDigits, index, @@ -92,6 +86,15 @@ groupSize2 = 0, pieces = []; + options = options || { + group: ',', + decimal: '.' + }; + + if ( isNaN( value ) ) { + return value; + } + if ( patternParts[ 1 ] ) { // Pad fractional with trailing zeros padLength = ( patternParts[ 1 ] && patternParts[ 1 ].lastIndexOf( '0' ) + 1 ); 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.legacy/protect.js b/resources/src/mediawiki.legacy/protect.js index 6226c90b8d..fd3fb00caa 100644 --- a/resources/src/mediawiki.legacy/protect.js +++ b/resources/src/mediawiki.legacy/protect.js @@ -1,242 +1,242 @@ ( function ( mw, $ ) { -var ProtectionForm = window.ProtectionForm = { - /** - * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox) - * on the protection form - */ - init: function () { - var $cell = $( '' ).append( $cell ); - - if ( !$( '#mwProtectSet' ).length ) { - return false; - } - - if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) { - $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) ); - } - this.getExpirySelectors().each( function () { - $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) ); - } ); - this.getExpiryInputs().each( function () { - $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) ); - } ); - this.getLevelSelectors().each( function () { - $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) ); - } ); - - $( '#mwProtectSet > tbody > tr:first' ).after( $row ); - - // If there is only one protection type, there is nothing to chain - if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) { - $cell.append( - $( '' ) - .attr( { id: 'mwProtectUnchained', type: 'checkbox' } ) - .click( this.onChainClick.bind( this ) ) - .prop( 'checked', !this.areAllTypesMatching() ), - document.createTextNode( ' ' ), - $( '' ).append( $cell ); + + if ( !$( '#mwProtectSet' ).length ) { return false; - } else { - $( '#mwProtect-cascade' ).prop( 'disabled', false ); } - } ); - }, - - /** - * Checks if a certain protection level is cascadeable. - * - * @param {string} level - * @return {boolean} - */ - isCascadeableLevel: function ( level ) { - return $.inArray( level, mw.config.get( 'wgCascadeableLevels' ) ) !== -1; - }, - - /** - * When protection levels are locked together, update the rest - * when one action's level changes - * - * @param {Element} source Level selector that changed - */ - updateLevels: function ( source ) { - if ( !this.isUnchained() ) { - this.setAllSelectors( source.selectedIndex ); - } - this.updateCascadeCheckbox(); - }, - - /** - * When protection levels are locked together, update the - * expiries when one changes - * - * @param {Element} source expiry input that changed - */ - - updateExpiry: function ( source ) { - if ( !this.isUnchained() ) { + + if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) { + $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) ); + } + this.getExpirySelectors().each( function () { + $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) ); + } ); this.getExpiryInputs().each( function () { - this.value = source.value; + $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) ); } ); - } - if ( this.isUnchained() ) { - $( '#' + source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' ); - } else { - this.getExpirySelectors().each( function () { - this.value = 'othertime'; + this.getLevelSelectors().each( function () { + $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) ); } ); - } - }, - - /** - * When protection levels are locked together, update the - * expiry lists when one changes and clear the custom inputs - * - * @param {Element} source Expiry selector that changed - */ - updateExpiryList: function ( source ) { - if ( !this.isUnchained() ) { - this.getExpirySelectors().each( function () { - this.value = source.value; + + $( '#mwProtectSet > tbody > tr:first' ).after( $row ); + + // If there is only one protection type, there is nothing to chain + if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) { + $cell.append( + $( '' ) + .attr( { id: 'mwProtectUnchained', type: 'checkbox' } ) + .click( this.onChainClick.bind( this ) ) + .prop( 'checked', !this.areAllTypesMatching() ), + document.createTextNode( ' ' ), + $( '
    ' ), - $row = $( '
    ' ), + $row = $( '
    ' ); $( '' ) - .append( $( '#' ).css( 'width', '4em' ) ) + .append( $( '#' ).css( 'width', '4em' ) ) .append( $( 'SQL' ) ) - .append( $( 'Time' ).css( 'width', '8em' ) ) + .append( $( 'Time' ).css( 'width', '8em' ) ) .append( $( 'Call' ).css( 'width', '18em' ) ) .appendTo( $table ); diff --git a/resources/src/mediawiki/mediawiki.experiments.js b/resources/src/mediawiki/mediawiki.experiments.js index 49ff4115b5..0c9ea97c14 100644 --- a/resources/src/mediawiki/mediawiki.experiments.js +++ b/resources/src/mediawiki/mediawiki.experiments.js @@ -1,4 +1,3 @@ -/* jshint bitwise:false */ ( function ( mw, $ ) { var CONTROL_BUCKET = 'control', @@ -17,6 +16,7 @@ * @see https://jsbin.com/kejewi/4/watch?js,console */ function hashString( string ) { + /* eslint-disable no-bitwise */ var hash = 0, i = string.length; @@ -30,6 +30,7 @@ hash += ( hash << 15 ); return hash >>> 0; + /* eslint-enable no-bitwise */ } /** diff --git a/resources/src/mediawiki/mediawiki.feedback.js b/resources/src/mediawiki/mediawiki.feedback.js index 0b3ea043e8..6abdf83877 100644 --- a/resources/src/mediawiki/mediawiki.feedback.js +++ b/resources/src/mediawiki/mediawiki.feedback.js @@ -6,8 +6,6 @@ * @author Moriel Schottlender, 2015 * @since 1.19 */ -/*jshint esversion:5 */ -/*global OO*/ ( function ( mw, $ ) { /** * This is a way of getting simple feedback from users. It's useful @@ -296,7 +294,7 @@ this.feedbackSubjectInput.getValue() ); - this.actions.setAbilities( { submit: isValid } ); + this.actions.setAbilities( { submit: isValid } ); }; /** @@ -458,10 +456,10 @@ if ( secondaryCode === 'http' ) { fb.status = 'error3'; // ajax request failed - mw.log.warn( 'Feedback report failed with HTTP error: ' + details.textStatus ); + mw.log.warn( 'Feedback report failed with HTTP error: ' + details.textStatus ); } else { fb.status = 'error2'; - mw.log.warn( 'Feedback report failed with API error: ' + secondaryCode ); + mw.log.warn( 'Feedback report failed with API error: ' + secondaryCode ); } } else { fb.status = 'error1'; diff --git a/resources/src/mediawiki/mediawiki.filewarning.js b/resources/src/mediawiki/mediawiki.filewarning.js index 882affe17f..72bf3d7696 100644 --- a/resources/src/mediawiki/mediawiki.filewarning.js +++ b/resources/src/mediawiki/mediawiki.filewarning.js @@ -4,7 +4,6 @@ * @author Mark Holmquist, 2015 * @since 1.25 */ -/*global OO*/ ( function ( mw, $, oo ) { var warningConfig = mw.config.get( 'wgFileWarning' ), warningMessages = warningConfig.messages, diff --git a/resources/src/mediawiki/mediawiki.inspect.js b/resources/src/mediawiki/mediawiki.inspect.js index a74aef3b3c..249430585a 100644 --- a/resources/src/mediawiki/mediawiki.inspect.js +++ b/resources/src/mediawiki/mediawiki.inspect.js @@ -4,7 +4,9 @@ * @author Ori Livneh * @since 1.22 */ -/*jshint devel:true */ + +/* eslint-disable no-console */ + ( function ( mw, $ ) { var inspect, @@ -18,11 +20,12 @@ } function humanSize( bytes ) { - if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; } - var i = 0, + var i, units = [ '', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB' ]; - for ( ; bytes >= 1024; bytes /= 1024 ) { i++; } + if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; } + + for ( i = 0; bytes >= 1024; bytes /= 1024 ) { i++; } // Maintain one decimal for kB and above, but don't // add ".0" for bytes. return bytes.toFixed( i > 0 ? 1 : 0 ) + units[ i ]; @@ -229,7 +232,7 @@ allSelectors: stats.total, matchedSelectors: stats.matched, percentMatched: stats.total !== 0 ? - ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null + ( stats.matched / stats.total * 100 ).toFixed( 2 ) + '%' : null } ); } ); sortByProperty( modules, 'allSelectors', true ); @@ -247,7 +250,7 @@ $.extend( stats, mw.loader.store.stats ); try { raw = localStorage.getItem( mw.loader.store.getStoreKey() ); - stats.totalSizeInBytes = $.byteLength( raw ); + stats.totalSizeInBytes = $.byteLength( raw ); stats.totalSize = humanSize( $.byteLength( raw ) ); } catch ( e ) {} } @@ -278,8 +281,8 @@ // Grep module's CSS if ( - $.isPlainObject( module.style ) && $.isArray( module.style.css ) - && pattern.test( module.style.css.join( '' ) ) + $.isPlainObject( module.style ) && $.isArray( module.style.css ) && + pattern.test( module.style.css.join( '' ) ) ) { // Module's CSS source matches return true; diff --git a/resources/src/mediawiki/mediawiki.jqueryMsg.js b/resources/src/mediawiki/mediawiki.jqueryMsg.js index 2646cffe98..8504964685 100644 --- a/resources/src/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/src/mediawiki/mediawiki.jqueryMsg.js @@ -136,6 +136,7 @@ function getFailableParserFn( options ) { return function ( args ) { var fallback, + // eslint-disable-next-line new-cap parser = new mw.jqueryMsg.parser( options ), key = args[ 0 ], argsArray = $.isArray( args[ 1 ] ) ? args[ 1 ] : slice.call( args, 1 ); @@ -212,10 +213,11 @@ } return function () { + var failableResult; if ( !failableParserFn ) { failableParserFn = getFailableParserFn( options ); } - var failableResult = failableParserFn( arguments ); + failableResult = failableParserFn( arguments ); if ( format === 'text' || format === 'escaped' ) { return failableResult.text(); } else { @@ -250,10 +252,11 @@ var failableParserFn; return function () { + var $target; if ( !failableParserFn ) { failableParserFn = getFailableParserFn( options ); } - var $target = this.empty(); + $target = this.empty(); appendWithoutParsing( $target, failableParserFn( arguments ) ); return $target; }; @@ -272,6 +275,7 @@ this.settings.onlyCurlyBraceTransform = ( this.settings.format === 'text' || this.settings.format === 'escaped' ); this.astCache = {}; + // eslint-disable-next-line new-cap this.emitter = new mw.jqueryMsg.htmlEmitter( this.settings.language, this.settings.magic ); }; @@ -292,10 +296,10 @@ /** * Fetch the message string associated with a key, return parsed structure. Memoized. - * Note that we pass '[' + key + ']' back for a missing message here. + * Note that we pass '⧼' + key + '⧽' back for a missing message here. * * @param {string} key - * @return {string|Array} string of '[key]' if message missing, simple string if possible, array of arrays if needs parsing + * @return {string|Array} string of '⧼key⧽' if message missing, simple string if possible, array of arrays if needs parsing */ getAst: function ( key ) { var wikiText; @@ -303,7 +307,7 @@ if ( !this.astCache.hasOwnProperty( key ) ) { wikiText = this.settings.messages.get( key ); if ( typeof wikiText !== 'string' ) { - wikiText = '\\[' + key + '\\]'; + wikiText = '⧼' + key + '⧽'; } this.astCache[ key ] = this.wikiTextToAst( wikiText ); } @@ -935,8 +939,8 @@ * htmlEmitter - object which primarily exists to emit HTML from parser ASTs */ mw.jqueryMsg.htmlEmitter = function ( language, magic ) { - this.language = language; var jmsg = this; + this.language = language; $.each( magic, function ( key, val ) { jmsg[ key.toLowerCase() ] = function () { return val; @@ -1275,7 +1279,7 @@ * @return {number|string} Formatted number */ formatnum: function ( nodes ) { - var isInteger = ( nodes[ 1 ] && nodes[ 1 ] === 'R' ) ? true : false, + var isInteger = !!nodes[ 1 ] && nodes[ 1 ] === 'R', number = nodes[ 0 ]; return this.language.convertNumber( number, isInteger ); @@ -1371,6 +1375,6 @@ return function () { return reusableParent.msg( this.key, this.parameters ).contents().detach(); }; - } )(); + }() ); }( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 6280c95eaa..4dce192cf3 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -7,7 +7,9 @@ * @alternateClassName mediaWiki * @singleton */ -/*jshint latedef:false */ + +/* eslint-disable no-use-before-define */ + ( function ( $ ) { 'use strict'; @@ -31,7 +33,7 @@ * @return {string} hash as an seven-character base 36 string */ function fnv132( str ) { - /*jshint bitwise:false */ + /* eslint-disable no-bitwise */ var hash = 0x811C9DC5, i; @@ -46,6 +48,7 @@ } return hash; + /* eslint-enable no-bitwise */ } StringSet = window.Set || ( function () { @@ -325,12 +328,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' ) { @@ -460,9 +466,9 @@ log.deprecate = !Object.defineProperty ? function ( obj, key, val ) { obj[ key ] = val; } : function ( obj, key, val, msg, logName ) { + var logged = new StringSet(); logName = logName || key; msg = 'Use of "' + logName + '" is deprecated.' + ( msg ? ( ' ' + msg ) : '' ); - var logged = new StringSet(); function uniqueTrace() { var trace = new Error().stack; if ( logged.has( trace ) ) { @@ -1109,7 +1115,7 @@ } if ( registry[ module ].skip !== null ) { - /*jshint evil:true */ + // eslint-disable-next-line no-new-func skip = new Function( registry[ module ].skip ); registry[ module ].skip = null; if ( skip() ) { @@ -1149,7 +1155,7 @@ ) ); } - unresolved.add( module ); + unresolved.add( module ); sortDependencies( deps[ i ], resolved, unresolved ); } } @@ -1294,9 +1300,9 @@ } ); }; - implicitDependencies = ( $.inArray( module, legacyModules ) !== -1 ) - ? [] - : legacyModules; + implicitDependencies = ( $.inArray( module, legacyModules ) !== -1 ) ? + [] : + legacyModules; if ( module === 'user' ) { // Implicit dependency on the site module. Not real dependency because @@ -1304,9 +1310,9 @@ implicitDependencies.push( 'site' ); } - legacyWait = implicitDependencies.length - ? mw.loader.using( implicitDependencies ) - : $.Deferred().resolve(); + legacyWait = implicitDependencies.length ? + mw.loader.using( implicitDependencies ) : + $.Deferred().resolve(); legacyWait.always( function () { try { @@ -1635,9 +1641,9 @@ prefix = modules[ i ].substr( 0, lastDotIndex ); suffix = modules[ i ].slice( lastDotIndex + 1 ); - bytesAdded = hasOwn.call( moduleMap, prefix ) - ? suffix.length + 3 // '%2C'.length == 3 - : modules[ i ].length + 3; // '%7C'.length == 3 + bytesAdded = hasOwn.call( moduleMap, prefix ) ? + suffix.length + 3 : // '%2C'.length == 3 + modules[ i ].length + 3; // '%7C'.length == 3 // If the url would become too long, create a new one, // but don't create empty requests @@ -1777,11 +1783,13 @@ return true; } ); asyncEval( implementations, function ( err ) { + var failed; // Not good, the cached mw.loader.implement calls failed! This should // never happen, barring ResourceLoader bugs, browser bugs and PEBKACs. // 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 @@ -1790,7 +1798,7 @@ mw.track( 'resourceloader.exception', { exception: err, source: 'store-eval' } ); // Re-add the failed ones that are still pending back to the batch - var failed = $.grep( sourceModules, function ( module ) { + failed = $.grep( sourceModules, function ( module ) { return registry[ module ].state === 'loading'; } ); batchRequest( failed ); @@ -2184,7 +2192,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. @@ -2325,7 +2333,7 @@ // Partial descriptor // (e.g. skipped module, or style module with state=ready) $.inArray( undefined, [ descriptor.script, descriptor.style, - descriptor.messages, descriptor.templates ] ) !== -1 + descriptor.messages, descriptor.templates ] ) !== -1 ) { // Decline to store return false; @@ -2504,7 +2512,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.log.js b/resources/src/mediawiki/mediawiki.log.js index c886817799..4d23604ca1 100644 --- a/resources/src/mediawiki/mediawiki.log.js +++ b/resources/src/mediawiki/mediawiki.log.js @@ -52,11 +52,11 @@ if ( !$log.length ) { $log = $( '
    ' ).css( { - overflow: 'auto', - height: '150px', - backgroundColor: 'white', - borderTop: 'solid 2px #ADADAD' - } ); + overflow: 'auto', + height: '150px', + backgroundColor: 'white', + borderTop: 'solid 2px #ADADAD' + } ); hovzer = $.getFootHovzer(); hovzer.$.append( $log ); hovzer.update(); diff --git a/resources/src/mediawiki/mediawiki.notification.js b/resources/src/mediawiki/mediawiki.notification.js index 36b45f177f..6c6f5607f2 100644 --- a/resources/src/mediawiki/mediawiki.notification.js +++ b/resources/src/mediawiki/mediawiki.notification.js @@ -25,7 +25,7 @@ * @private */ function Notification( message, options ) { - var $notification, $notificationTitle, $notificationContent; + var $notification, $notificationContent; $notification = $( '
    ' ) .data( 'mw.notification', this ) @@ -48,7 +48,7 @@ } if ( options.title ) { - $notificationTitle = $( '
    ' ) + $( '
    ' ) .text( options.title ) .appendTo( $notification ); } @@ -227,7 +227,7 @@ $area.hide(); notif.$notification.remove(); } else { - notif.$notification.slideUp( 'fast', function () { + notif.$notification.slideUp( 'fast', function () { $( this ).remove(); } ); } diff --git a/resources/src/mediawiki/mediawiki.template.mustache.js b/resources/src/mediawiki/mediawiki.template.mustache.js index 7f62256adf..c5e96eb4e7 100644 --- a/resources/src/mediawiki/mediawiki.template.mustache.js +++ b/resources/src/mediawiki/mediawiki.template.mustache.js @@ -1,4 +1,4 @@ -/*global Mustache */ +/* global Mustache */ ( function ( mw, $ ) { // Register mustache compiler mw.template.registerCompiler( 'mustache', { diff --git a/resources/src/mediawiki/mediawiki.user.js b/resources/src/mediawiki/mediawiki.user.js index 63e7de87ef..240d1bd1b1 100644 --- a/resources/src/mediawiki/mediawiki.user.js +++ b/resources/src/mediawiki/mediawiki.user.js @@ -2,6 +2,7 @@ * @class mw.user * @singleton */ +/* global Uint8Array */ ( function ( mw, $ ) { var i, userInfoPromise, @@ -50,7 +51,7 @@ * @return {string} 64 bit integer in hex format, padded */ generateRandomSessionId: function () { - /*jshint bitwise:false */ + /* eslint-disable no-bitwise */ var rnds, i, r, hexRnds = new Array( 8 ), // Support: IE 11 @@ -79,6 +80,7 @@ // Concatenation of two random integers with entropy n and m // returns a string with entropy n+m if those strings are independent return hexRnds.join( '' ); + /* eslint-enable no-bitwise */ }, /** @@ -108,10 +110,11 @@ * unavailable, or Date for when the user registered. */ getRegistration: function () { + var registration; if ( mw.user.isAnon() ) { return false; } - var registration = mw.config.get( 'wgUserRegistration' ); + registration = mw.config.get( 'wgUserRegistration' ); // Registration may be unavailable if the user signed up before MediaWiki // began tracking this. return !registration ? null : new Date( registration ); diff --git a/resources/src/mediawiki/mediawiki.util.js b/resources/src/mediawiki/mediawiki.util.js index 866f213bd9..bee22c7d3d 100644 --- a/resources/src/mediawiki/mediawiki.util.js +++ b/resources/src/mediawiki/mediawiki.util.js @@ -124,11 +124,12 @@ query = $.param( params ); } if ( query ) { - url = title - ? util.wikiScript() + '?title=' + util.wikiUrlencode( title ) + '&' + query - : util.wikiScript() + '?' + query; + url = title ? + 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 @@ -187,12 +188,10 @@ * @return {Mixed} Parameter value or null. */ getParamValue: function ( param, url ) { - if ( url === undefined ) { - url = location.href; - } // Get last match, stop at hash var re = new RegExp( '^[^#]*[&?]' + mw.RegExp.escape( param ) + '=([^&#]*)' ), - m = re.exec( url ); + m = re.exec( url !== undefined ? url : location.href ); + if ( m ) { // Beware that decodeURIComponent is not required to understand '+' // by spec, as encodeURIComponent does not produce it. @@ -413,20 +412,15 @@ html5EmailRegexp = new RegExp( // start of string - '^' - + + '^' + // User part which is liberal :p - '[' + rfc5322Atext + '\\.]+' - + + '[' + rfc5322Atext + '\\.]+' + // 'at' - '@' - + + '@' + // Domain first part - '[' + rfc1034LdhStr + ']+' - + + '[' + rfc1034LdhStr + ']+' + // Optional second part and following are separated by a dot - '(?:\\.[' + rfc1034LdhStr + ']+)*' - + + '(?:\\.[' + rfc1034LdhStr + ']+)*' + // End of string '$', // RegExp is case insensitive @@ -443,13 +437,15 @@ * @return {boolean} */ isIPv4Address: function ( address, allowBlock ) { + var block, RE_IP_BYTE, RE_IP_ADD; + if ( typeof address !== 'string' ) { return false; } - var block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : '', - RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])', - RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE; + block = allowBlock ? '(?:\\/(?:3[0-2]|[12]?\\d))?' : ''; + RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])'; + RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE; return ( new RegExp( '^' + RE_IP_ADD + block + '$' ).test( address ) ); }, @@ -462,19 +458,21 @@ * @return {boolean} */ isIPv6Address: function ( address, allowBlock ) { + var block, RE_IPV6_ADD; + if ( typeof address !== 'string' ) { return false; } - var block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : '', - RE_IPV6_ADD = - '(?:' + // starts with "::" (including "::") - ':(?::|(?::' + '[0-9A-Fa-f]{1,4}' + '){1,7})' + - '|' + // ends with "::" (except "::") - '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){0,6}::' + - '|' + // contains no "::" - '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' + - ')'; + block = allowBlock ? '(?:\\/(?:12[0-8]|1[01][0-9]|[1-9]?\\d))?' : ''; + RE_IPV6_ADD = + '(?:' + // starts with "::" (including "::") + ':(?::|(?::' + '[0-9A-Fa-f]{1,4}' + '){1,7})' + + '|' + // ends with "::" (except "::") + '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){0,6}::' + + '|' + // contains no "::" + '[0-9A-Fa-f]{1,4}' + '(?::' + '[0-9A-Fa-f]{1,4}' + '){7}' + + ')'; if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) { return true; @@ -484,9 +482,9 @@ RE_IPV6_ADD = '[0-9A-Fa-f]{1,4}' + '(?:::?' + '[0-9A-Fa-f]{1,4}' + '){1,6}'; return ( - new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) - && /::/.test( address ) - && !/::.*::/.test( address ) + new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) && + /::/.test( address ) && + !/::.*::/.test( address ) ); }, diff --git a/resources/src/mediawiki/mediawiki.viewport.js b/resources/src/mediawiki/mediawiki.viewport.js index 6396331f90..cb1e73fa30 100644 --- a/resources/src/mediawiki/mediawiki.viewport.js +++ b/resources/src/mediawiki/mediawiki.viewport.js @@ -85,7 +85,7 @@ */ isElementCloseToViewport: function ( el, threshold, rectangle ) { var viewport = rectangle ? $.extend( {}, rectangle ) : this.makeViewportFromWindow(); - threshold = threshold || 50 ; + threshold = threshold || 50; viewport.top -= threshold; viewport.left -= threshold; diff --git a/resources/src/mediawiki/page/gallery-slideshow.js b/resources/src/mediawiki/page/gallery-slideshow.js index cf448b0183..be75a2e80f 100644 --- a/resources/src/mediawiki/page/gallery-slideshow.js +++ b/resources/src/mediawiki/page/gallery-slideshow.js @@ -335,7 +335,7 @@ if ( this.imageInfoCache[ imageSrc ] === undefined ) { api = new mw.Api(); // TODO: This supports only gallery of images - title = new mw.Title.newFromImg( $img ); + title = mw.Title.newFromImg( $img ); params = { action: 'query', formatversion: 2, @@ -450,11 +450,10 @@ }; // Bootstrap all slideshow galleries - $( function () { - $( '.mw-gallery-slideshow' ).each( function () { - /*jshint -W031 */ + mw.hook( 'wikipage.content' ).add( function ( $content ) { + $content.find( '.mw-gallery-slideshow' ).each( function () { + // eslint-disable-next-line no-new new mw.GallerySlideshow( this ); - /*jshint +W031 */ } ); } ); }( mediaWiki, jQuery, OO ) ); 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/mediawiki/page/image-pagination.js b/resources/src/mediawiki/page/image-pagination.js index 02bc1de949..6038a574c8 100644 --- a/resources/src/mediawiki/page/image-pagination.js +++ b/resources/src/mediawiki/page/image-pagination.js @@ -1,8 +1,10 @@ /*! * Implement AJAX navigation for multi-page images so the user may browse without a full page reload. */ + +/* eslint-disable no-use-before-define */ + ( function ( mw, $ ) { - /*jshint latedef:false */ var jqXhr, $multipageimage, $spinner, cache = {}, cacheOrder = []; diff --git a/resources/src/mediawiki/page/patrol.ajax.js b/resources/src/mediawiki/page/patrol.ajax.js index 89bbbe79d8..6d6d46dd94 100644 --- a/resources/src/mediawiki/page/patrol.ajax.js +++ b/resources/src/mediawiki/page/patrol.ajax.js @@ -35,11 +35,12 @@ rcid: rcid } ) .done( function ( data ) { + var title; // Remove all patrollinks from the page (including any spinners inside). $patrolLinks.closest( '.patrollink' ).remove(); if ( data.patrol !== undefined ) { // Success - var title = new mw.Title( data.patrol.title ); + title = new mw.Title( data.patrol.title ); mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) ); } else { // This should never happen as errors should trigger fail diff --git a/resources/src/mediawiki/page/rollback.js b/resources/src/mediawiki/page/rollback.js index 83d14b3ed3..cb46b110dc 100644 --- a/resources/src/mediawiki/page/rollback.js +++ b/resources/src/mediawiki/page/rollback.js @@ -43,9 +43,9 @@ } $( e.delegateTarget ).remove(); }, function ( errorCode, data ) { - var message = data && data.error && data.error.messageHtml - ? $.parseHTML( data.error.messageHtml ) - : mw.msg( 'rollbackfailed' ), + var message = data && data.error && data.error.messageHtml ? + $.parseHTML( data.error.messageHtml ) : + mw.msg( 'rollbackfailed' ), type = errorCode === 'alreadyrolled' ? 'warn' : 'error'; mw.notify( message, { diff --git a/resources/src/mediawiki/page/startup.js b/resources/src/mediawiki/page/startup.js index 282799ad36..076357a40c 100644 --- a/resources/src/mediawiki/page/startup.js +++ b/resources/src/mediawiki/page/startup.js @@ -3,6 +3,7 @@ mw.page = {}; $( function () { + var $diff; mw.util.init(); /** @@ -23,7 +24,7 @@ */ mw.hook( 'wikipage.content' ).fire( $( '#mw-content-text' ) ); - var $diff = $( 'table.diff[data-mw="interface"]' ); + $diff = $( 'table.diff[data-mw="interface"]' ); if ( $diff.length ) { /** * Fired when the diff is added to a page containing a diff diff --git a/resources/src/moment-dmy.js b/resources/src/moment-dmy.js index c67b93e976..0abb9574bf 100644 --- a/resources/src/moment-dmy.js +++ b/resources/src/moment-dmy.js @@ -1,7 +1,7 @@ // Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines. // This affects English only (and languages without localisations, that fall back to English). // http://momentjs.com/docs/#/customization/long-date-formats/ -/*global moment */ +/* global moment */ moment.locale( 'en', { longDateFormat: { // Unchanged, but have to be repeated here: diff --git a/resources/src/moment-locale-overrides.js b/resources/src/moment-locale-overrides.js index dd33b007f6..13f26f07dc 100644 --- a/resources/src/moment-locale-overrides.js +++ b/resources/src/moment-locale-overrides.js @@ -1,4 +1,4 @@ -/*global moment, mw */ +/* global moment, mw */ // HACK: Overwrite moment's i18n with MediaWiki's for the current language so that // wgTranslateNumerals is respected. 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..c8acc86df4 100644 --- a/resources/src/polyfill-nodeTypes.js +++ b/resources/src/polyfill-nodeTypes.js @@ -1,19 +1,19 @@ /** * 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 || { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 }; diff --git a/resources/src/startup.js b/resources/src/startup.js index d026cb01ab..e3094d2e61 100644 --- a/resources/src/startup.js +++ b/resources/src/startup.js @@ -3,11 +3,11 @@ * * This file is where we decide whether to initialise the modern run-time. */ -/*jshint unused: false */ -/*globals mw, RLQ: true, NORLQ: true, $VARS, $CODE, performance */ -var mediaWikiLoadStart = ( new Date() ).getTime(), +/* global mw, $VARS, $CODE */ +// eslint-disable-next-line no-unused-vars +var mediaWikiLoadStart = ( new Date() ).getTime(), mwPerformance = ( window.performance && performance.mark ) ? performance : { mark: function () {} }; @@ -51,23 +51,23 @@ function isCompatible( str ) { var ua = str || navigator.userAgent; return !!( // http://caniuse.com/#feat=queryselector - 'querySelector' in document + 'querySelector' in document && // http://caniuse.com/#feat=namevalue-storage // https://developer.blackberry.com/html5/apis/v1_0/localstorage.html // https://blog.whatwg.org/this-week-in-html-5-episode-30 - && 'localStorage' in window + 'localStorage' in window && // http://caniuse.com/#feat=addeventlistener - && 'addEventListener' in window + 'addEventListener' in window && // 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\/$/ ) ) ); } @@ -112,6 +112,7 @@ function isCompatible( str ) { // Must be after mw.config.set because these callbacks may use mw.loader which // needs to have values 'skin', 'debug' etc. from mw.config. + // eslint-disable-next-line vars-on-top var RLQ = window.RLQ || []; while ( RLQ.length ) { RLQ.shift()(); 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..66df315e10 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -35,12 +35,14 @@ $wgAutoloadClasses += [ 'DjVuSupport' => "$testDir/parser/DjVuSupport.php", 'TestRecorder' => "$testDir/parser/TestRecorder.php", 'MultiTestRecorder' => "$testDir/parser/MultiTestRecorder.php", + 'ParserTestMockParser' => "$testDir/parser/ParserTestMockParser.php", 'ParserTestRunner' => "$testDir/parser/ParserTestRunner.php", 'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php", 'ParserTestPrinter' => "$testDir/parser/ParserTestPrinter.php", '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/ParserTestMockParser.php b/tests/parser/ParserTestMockParser.php new file mode 100644 index 0000000000..0757b34cfb --- /dev/null +++ b/tests/parser/ParserTestMockParser.php @@ -0,0 +1,20 @@ +getParser(); + $restore = $this->executeSetupSnippets( [ 'wgParser' => new ParserTestMockParser ] ); $status = $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW ); + $restore(); + if ( !$status->isOK() ) { throw new MWException( $status->getWikiText( false, false, 'en' ) ); } 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..5b17eacab0 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. @@ -7417,6 +7440,23 @@ Piped link with no link text

    [[Thomas Bek (bishop of St David's)|]]

    !! end +!! test +Piped link with empty link text +!! wikitext +[[Main Page|]] - empty nowiki +[[Main Page| ]] - empty space +[[Main Page| ]] - empty non breaking space +!! html/php +

    - empty nowiki + - empty space +  - empty non breaking space +

    +!! html/parsoid +

    - empty nowiki + - empty space +  - empty non breaking space

    +!! end + !! test Broken link !! wikitext @@ -19098,7 +19138,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/data/upload/buggynamespace-bad.svg b/tests/phpunit/data/upload/buggynamespace-bad.svg new file mode 100644 index 0000000000..974fac0623 --- /dev/null +++ b/tests/phpunit/data/upload/buggynamespace-bad.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + eJzsvWl3XjXyL3pf91r9HZ50MyQkfrw1awcIZCBAYyAQaEIzBMd+krjx1LYDzf/F+exXNUml/Qwx +d1/++qv/+P2XX3/z1fc//9mvf/jyH796+/Lbc2Z9+eNXvzn/9Pbr77/64cfvvv/q7Ye//+53hNBH ++sFf/MXf/Nv/5ec/+38A7g2BFw== + + + diff --git a/tests/phpunit/data/upload/buggynamespace-evilhtml.svg b/tests/phpunit/data/upload/buggynamespace-evilhtml.svg new file mode 100644 index 0000000000..f4be479ed8 --- /dev/null +++ b/tests/phpunit/data/upload/buggynamespace-evilhtml.svg @@ -0,0 +1,12 @@ + + + +]> + +
    foo
    +
    diff --git a/tests/phpunit/data/upload/buggynamespace-okay.svg b/tests/phpunit/data/upload/buggynamespace-okay.svg new file mode 100644 index 0000000000..4a5c6aae23 --- /dev/null +++ b/tests/phpunit/data/upload/buggynamespace-okay.svg @@ -0,0 +1,52 @@ + + + +image/svg+xml + + + + + + + + + + + + eJzsvWl3XjXyL3pf91r9HZ50MyQkfrw1awcIZCBAYyAQaEIzBMd+krjx1LYDzf/F+exXNUml/Qwx +d1/++qv/+P2XX3/z1fc//9mvf/jyH796+/Lbc2Z9+eNXvzn/9Pbr77/64cfvvv/q7Ye//+53hNBH ++sFf/MXf/Nv/5ec/+38A7g2BFw== + + + \ No newline at end of file diff --git a/tests/phpunit/data/upload/buggynamespace-okay2.svg b/tests/phpunit/data/upload/buggynamespace-okay2.svg new file mode 100644 index 0000000000..fe42310f63 --- /dev/null +++ b/tests/phpunit/data/upload/buggynamespace-okay2.svg @@ -0,0 +1,52 @@ + + + +image/svg+xml + + + + + + + + + + + + eJzsvWl3XjXyL3pf91r9HZ50MyQkfrw1awcIZCBAYyAQaEIzBMd+krjx1LYDzf/F+exXNUml/Qwx +d1/++qv/+P2XX3/z1fc//9mvf/jyH796+/Lbc2Z9+eNXvzn/9Pbr77/64cfvvv/q7Ye//+53hNBH ++sFf/MXf/Nv/5ec/+38A7g2BFw== + + + \ No newline at end of file diff --git a/tests/phpunit/data/upload/buggynamespace-original.svg b/tests/phpunit/data/upload/buggynamespace-original.svg new file mode 100644 index 0000000000..c61c91cfd8 --- /dev/null +++ b/tests/phpunit/data/upload/buggynamespace-original.svg @@ -0,0 +1,33 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + eJzsvWl3XjXyL3pf91r9HZ50MyQkfrw1awcIZCBAYyAQaEIzBMd+krjx1LYDzf/F+exXNUml/Qwx +d1/++qv/+P2XX3/z1fc//9mvf/jyH796+/Lbc2Z9+eNXvzn/9Pbr77/64cfvvv/q7Ye//+53hNBH ++sFf/MXf/Nv/5ec/+38A7g2BFw== + + + 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/ThrottlerTest.php b/tests/phpunit/includes/auth/ThrottlerTest.php index 5806003f64..c945885c76 100644 --- a/tests/phpunit/includes/auth/ThrottlerTest.php +++ b/tests/phpunit/includes/auth/ThrottlerTest.php @@ -196,7 +196,7 @@ class ThrottlerTest extends \MediaWikiTestCase { ->setMethods( [ 'log' ] ) ->getMockForAbstractClass(); $logger->expects( $this->once() )->method( 'log' )->with( $this->anything(), $this->anything(), [ - 'type' => 'custom', + 'throttle' => 'custom', 'index' => 0, 'ip' => '1.2.3.4', 'username' => 'SomeUser', 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 3debe6e198..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, @@ -397,6 +397,46 @@ class UploadBaseTest extends MediaWikiTestCase { // @codingStandardsIgnoreEnd } + /** + * @dataProvider provideDetectScriptInSvg + */ + public function testDetectScriptInSvg( $svg, $expected, $message ) { + // This only checks some weird cases, most tests are in testCheckSvgScriptCallback() above + $result = $this->upload->detectScriptInSvg( $svg, false ); + $this->assertSame( $expected, $result, $message ); + } + + public static function provideDetectScriptInSvg() { + global $IP; + return [ + [ + "$IP/tests/phpunit/data/upload/buggynamespace-original.svg", + false, + 'SVG with a weird but valid namespace definition created by Adobe Illustrator' + ], + [ + "$IP/tests/phpunit/data/upload/buggynamespace-okay.svg", + false, + 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape' + ], + [ + "$IP/tests/phpunit/data/upload/buggynamespace-okay2.svg", + false, + 'SVG with a namespace definition created by Adobe Illustrator and mangled by Inkscape (twice)' + ], + [ + "$IP/tests/phpunit/data/upload/buggynamespace-bad.svg", + [ 'uploadscriptednamespace', 'i' ], + 'SVG with a namespace definition using an undefined entity' + ], + [ + "$IP/tests/phpunit/data/upload/buggynamespace-evilhtml.svg", + [ 'uploadscriptednamespace', 'http://www.w3.org/1999/xhtml' ], + 'SVG with an html namespace encoded as an entity' + ], + ]; + } + /** * @dataProvider provideCheckXMLEncodingMissmatch */ @@ -442,4 +482,11 @@ class UploadTestHandler extends UploadBase { ); return [ $check->wellFormed, $check->filterMatch ]; } + + /** + * Same as parent function, but override visibility to 'public'. + */ + public function detectScriptInSvg( $filename, $partial ) { + return parent::detectScriptInSvg( $filename, $partial ); + } } 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.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index 886e2b6f04..910bcc1989 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -1,4 +1,3 @@ -/*jshint -W024 */ ( function ( mw, $ ) { var repeat = function ( input, multiplier ) { return new Array( multiplier + 1 ).join( input ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js index b12803d6c7..97185fca73 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js @@ -1,4 +1,3 @@ -/*jshint -W024 */ ( function ( mw, $ ) { QUnit.module( 'mediawiki.Uri', QUnit.newMwEnvironment( { setup: function () { diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index caaef83c1b..12181379f0 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -706,7 +706,7 @@ assert.equal( formatParse( 'uses-missing-int' ), - '[doesnt-exist]', + '⧼doesnt-exist⧽', 'int: where nested message does not exist' ); } ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index 1518a80cad..bac8274fef 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -1,4 +1,3 @@ -/*jshint -W024 */ ( function ( mw ) { var specialCharactersPageName, // Can't mock SITENAME since jqueryMsg caches it at load @@ -239,9 +238,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 +317,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/' ] };