GlobalFunctions.php 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096
  1. <?php
  2. /**
  3. * Global functions used everywhere.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. if ( !defined( 'MEDIAWIKI' ) ) {
  23. die( "This file is part of MediaWiki, it is not a valid entry point" );
  24. }
  25. use MediaWiki\BadFileLookup;
  26. use MediaWiki\Linker\LinkTarget;
  27. use MediaWiki\Logger\LoggerFactory;
  28. use MediaWiki\MediaWikiServices;
  29. use MediaWiki\ProcOpenError;
  30. use MediaWiki\Session\SessionManager;
  31. use MediaWiki\Shell\Shell;
  32. use Wikimedia\AtEase\AtEase;
  33. use Wikimedia\WrappedString;
  34. /**
  35. * Load an extension
  36. *
  37. * This queues an extension to be loaded through
  38. * the ExtensionRegistry system.
  39. *
  40. * @param string $ext Name of the extension to load
  41. * @param string|null $path Absolute path of where to find the extension.json file
  42. * @since 1.25
  43. */
  44. function wfLoadExtension( $ext, $path = null ) {
  45. if ( !$path ) {
  46. global $wgExtensionDirectory;
  47. $path = "$wgExtensionDirectory/$ext/extension.json";
  48. }
  49. ExtensionRegistry::getInstance()->queue( $path );
  50. }
  51. /**
  52. * Load multiple extensions at once
  53. *
  54. * Same as wfLoadExtension, but more efficient if you
  55. * are loading multiple extensions.
  56. *
  57. * If you want to specify custom paths, you should interact with
  58. * ExtensionRegistry directly.
  59. *
  60. * @see wfLoadExtension
  61. * @param string[] $exts Array of extension names to load
  62. * @since 1.25
  63. */
  64. function wfLoadExtensions( array $exts ) {
  65. global $wgExtensionDirectory;
  66. $registry = ExtensionRegistry::getInstance();
  67. foreach ( $exts as $ext ) {
  68. $registry->queue( "$wgExtensionDirectory/$ext/extension.json" );
  69. }
  70. }
  71. /**
  72. * Load a skin
  73. *
  74. * @see wfLoadExtension
  75. * @param string $skin Name of the extension to load
  76. * @param string|null $path Absolute path of where to find the skin.json file
  77. * @since 1.25
  78. */
  79. function wfLoadSkin( $skin, $path = null ) {
  80. if ( !$path ) {
  81. global $wgStyleDirectory;
  82. $path = "$wgStyleDirectory/$skin/skin.json";
  83. }
  84. ExtensionRegistry::getInstance()->queue( $path );
  85. }
  86. /**
  87. * Load multiple skins at once
  88. *
  89. * @see wfLoadExtensions
  90. * @param string[] $skins Array of extension names to load
  91. * @since 1.25
  92. */
  93. function wfLoadSkins( array $skins ) {
  94. global $wgStyleDirectory;
  95. $registry = ExtensionRegistry::getInstance();
  96. foreach ( $skins as $skin ) {
  97. $registry->queue( "$wgStyleDirectory/$skin/skin.json" );
  98. }
  99. }
  100. /**
  101. * Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
  102. * @param array $a
  103. * @param array $b
  104. * @return array
  105. */
  106. function wfArrayDiff2( $a, $b ) {
  107. return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
  108. }
  109. /**
  110. * @param array|string $a
  111. * @param array|string $b
  112. * @return int
  113. */
  114. function wfArrayDiff2_cmp( $a, $b ) {
  115. if ( is_string( $a ) && is_string( $b ) ) {
  116. return strcmp( $a, $b );
  117. } elseif ( count( $a ) !== count( $b ) ) {
  118. return count( $a ) <=> count( $b );
  119. } else {
  120. reset( $a );
  121. reset( $b );
  122. while ( key( $a ) !== null && key( $b ) !== null ) {
  123. $valueA = current( $a );
  124. $valueB = current( $b );
  125. $cmp = strcmp( $valueA, $valueB );
  126. if ( $cmp !== 0 ) {
  127. return $cmp;
  128. }
  129. next( $a );
  130. next( $b );
  131. }
  132. return 0;
  133. }
  134. }
  135. /**
  136. * Appends to second array if $value differs from that in $default
  137. *
  138. * @param string|int $key
  139. * @param mixed $value
  140. * @param mixed $default
  141. * @param array &$changed Array to alter
  142. * @throws MWException
  143. */
  144. function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
  145. if ( is_null( $changed ) ) {
  146. throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
  147. }
  148. if ( $default[$key] !== $value ) {
  149. $changed[$key] = $value;
  150. }
  151. }
  152. /**
  153. * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
  154. * e.g.
  155. * wfMergeErrorArrays(
  156. * [ [ 'x' ] ],
  157. * [ [ 'x', '2' ] ],
  158. * [ [ 'x' ] ],
  159. * [ [ 'y' ] ]
  160. * );
  161. * returns:
  162. * [
  163. * [ 'x', '2' ],
  164. * [ 'x' ],
  165. * [ 'y' ]
  166. * ]
  167. *
  168. * @param array ...$args
  169. * @return array
  170. */
  171. function wfMergeErrorArrays( ...$args ) {
  172. $out = [];
  173. foreach ( $args as $errors ) {
  174. foreach ( $errors as $params ) {
  175. $originalParams = $params;
  176. if ( $params[0] instanceof MessageSpecifier ) {
  177. $msg = $params[0];
  178. $params = array_merge( [ $msg->getKey() ], $msg->getParams() );
  179. }
  180. # @todo FIXME: Sometimes get nested arrays for $params,
  181. # which leads to E_NOTICEs
  182. $spec = implode( "\t", $params );
  183. $out[$spec] = $originalParams;
  184. }
  185. }
  186. return array_values( $out );
  187. }
  188. /**
  189. * Insert array into another array after the specified *KEY*
  190. *
  191. * @param array $array The array.
  192. * @param array $insert The array to insert.
  193. * @param mixed $after The key to insert after. Callers need to make sure the key is set.
  194. * @return array
  195. */
  196. function wfArrayInsertAfter( array $array, array $insert, $after ) {
  197. // Find the offset of the element to insert after.
  198. $keys = array_keys( $array );
  199. $offsetByKey = array_flip( $keys );
  200. $offset = $offsetByKey[$after];
  201. // Insert at the specified offset
  202. $before = array_slice( $array, 0, $offset + 1, true );
  203. $after = array_slice( $array, $offset + 1, count( $array ) - $offset, true );
  204. $output = $before + $insert + $after;
  205. return $output;
  206. }
  207. /**
  208. * Recursively converts the parameter (an object) to an array with the same data
  209. *
  210. * @param object|array $objOrArray
  211. * @param bool $recursive
  212. * @return array
  213. */
  214. function wfObjectToArray( $objOrArray, $recursive = true ) {
  215. $array = [];
  216. if ( is_object( $objOrArray ) ) {
  217. $objOrArray = get_object_vars( $objOrArray );
  218. }
  219. foreach ( $objOrArray as $key => $value ) {
  220. if ( $recursive && ( is_object( $value ) || is_array( $value ) ) ) {
  221. $value = wfObjectToArray( $value );
  222. }
  223. $array[$key] = $value;
  224. }
  225. return $array;
  226. }
  227. /**
  228. * Get a random decimal value in the domain of [0, 1), in a way
  229. * not likely to give duplicate values for any realistic
  230. * number of articles.
  231. *
  232. * @note This is designed for use in relation to Special:RandomPage
  233. * and the page_random database field.
  234. *
  235. * @return string
  236. */
  237. function wfRandom() {
  238. // The maximum random value is "only" 2^31-1, so get two random
  239. // values to reduce the chance of dupes
  240. $max = mt_getrandmax() + 1;
  241. $rand = number_format( ( mt_rand() * $max + mt_rand() ) / $max / $max, 12, '.', '' );
  242. return $rand;
  243. }
  244. /**
  245. * Get a random string containing a number of pseudo-random hex characters.
  246. *
  247. * @note This is not secure, if you are trying to generate some sort
  248. * of token please use MWCryptRand instead.
  249. *
  250. * @param int $length The length of the string to generate
  251. * @return string
  252. * @since 1.20
  253. */
  254. function wfRandomString( $length = 32 ) {
  255. $str = '';
  256. for ( $n = 0; $n < $length; $n += 7 ) {
  257. $str .= sprintf( '%07x', mt_rand() & 0xfffffff );
  258. }
  259. return substr( $str, 0, $length );
  260. }
  261. /**
  262. * We want some things to be included as literal characters in our title URLs
  263. * for prettiness, which urlencode encodes by default. According to RFC 1738,
  264. * all of the following should be safe:
  265. *
  266. * ;:@&=$-_.+!*'(),
  267. *
  268. * RFC 1738 says ~ is unsafe, however RFC 3986 considers it an unreserved
  269. * character which should not be encoded. More importantly, google chrome
  270. * always converts %7E back to ~, and converting it in this function can
  271. * cause a redirect loop (T105265).
  272. *
  273. * But + is not safe because it's used to indicate a space; &= are only safe in
  274. * paths and not in queries (and we don't distinguish here); ' seems kind of
  275. * scary; and urlencode() doesn't touch -_. to begin with. Plus, although /
  276. * is reserved, we don't care. So the list we unescape is:
  277. *
  278. * ;:@$!*(),/~
  279. *
  280. * However, IIS7 redirects fail when the url contains a colon (see T24709),
  281. * so no fancy : for IIS7.
  282. *
  283. * %2F in the page titles seems to fatally break for some reason.
  284. *
  285. * @param string $s
  286. * @return string
  287. */
  288. function wfUrlencode( $s ) {
  289. static $needle;
  290. if ( is_null( $s ) ) {
  291. // Reset $needle for testing.
  292. $needle = null;
  293. return '';
  294. }
  295. if ( is_null( $needle ) ) {
  296. $needle = [ '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F', '%7E' ];
  297. if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
  298. ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false )
  299. ) {
  300. $needle[] = '%3A';
  301. }
  302. }
  303. $s = urlencode( $s );
  304. $s = str_ireplace(
  305. $needle,
  306. [ ';', '@', '$', '!', '*', '(', ')', ',', '/', '~', ':' ],
  307. $s
  308. );
  309. return $s;
  310. }
  311. /**
  312. * This function takes one or two arrays as input, and returns a CGI-style string, e.g.
  313. * "days=7&limit=100". Options in the first array override options in the second.
  314. * Options set to null or false will not be output.
  315. *
  316. * @param array $array1 ( String|Array )
  317. * @param array|null $array2 ( String|Array )
  318. * @param string $prefix
  319. * @return string
  320. */
  321. function wfArrayToCgi( $array1, $array2 = null, $prefix = '' ) {
  322. if ( !is_null( $array2 ) ) {
  323. $array1 = $array1 + $array2;
  324. }
  325. $cgi = '';
  326. foreach ( $array1 as $key => $value ) {
  327. if ( !is_null( $value ) && $value !== false ) {
  328. if ( $cgi != '' ) {
  329. $cgi .= '&';
  330. }
  331. if ( $prefix !== '' ) {
  332. $key = $prefix . "[$key]";
  333. }
  334. if ( is_array( $value ) ) {
  335. $firstTime = true;
  336. foreach ( $value as $k => $v ) {
  337. $cgi .= $firstTime ? '' : '&';
  338. if ( is_array( $v ) ) {
  339. $cgi .= wfArrayToCgi( $v, null, $key . "[$k]" );
  340. } else {
  341. $cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
  342. }
  343. $firstTime = false;
  344. }
  345. } else {
  346. if ( is_object( $value ) ) {
  347. $value = $value->__toString();
  348. }
  349. $cgi .= urlencode( $key ) . '=' . urlencode( $value );
  350. }
  351. }
  352. }
  353. return $cgi;
  354. }
  355. /**
  356. * This is the logical opposite of wfArrayToCgi(): it accepts a query string as
  357. * its argument and returns the same string in array form. This allows compatibility
  358. * with legacy functions that accept raw query strings instead of nice
  359. * arrays. Of course, keys and values are urldecode()d.
  360. *
  361. * @param string $query Query string
  362. * @return string[] Array version of input
  363. */
  364. function wfCgiToArray( $query ) {
  365. if ( isset( $query[0] ) && $query[0] == '?' ) {
  366. $query = substr( $query, 1 );
  367. }
  368. $bits = explode( '&', $query );
  369. $ret = [];
  370. foreach ( $bits as $bit ) {
  371. if ( $bit === '' ) {
  372. continue;
  373. }
  374. if ( strpos( $bit, '=' ) === false ) {
  375. // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
  376. $key = $bit;
  377. $value = '';
  378. } else {
  379. list( $key, $value ) = explode( '=', $bit );
  380. }
  381. $key = urldecode( $key );
  382. $value = urldecode( $value );
  383. if ( strpos( $key, '[' ) !== false ) {
  384. $keys = array_reverse( explode( '[', $key ) );
  385. $key = array_pop( $keys );
  386. $temp = $value;
  387. foreach ( $keys as $k ) {
  388. $k = substr( $k, 0, -1 );
  389. $temp = [ $k => $temp ];
  390. }
  391. if ( isset( $ret[$key] ) ) {
  392. $ret[$key] = array_merge( $ret[$key], $temp );
  393. } else {
  394. $ret[$key] = $temp;
  395. }
  396. } else {
  397. $ret[$key] = $value;
  398. }
  399. }
  400. return $ret;
  401. }
  402. /**
  403. * Append a query string to an existing URL, which may or may not already
  404. * have query string parameters already. If so, they will be combined.
  405. *
  406. * @param string $url
  407. * @param string|string[] $query String or associative array
  408. * @return string
  409. */
  410. function wfAppendQuery( $url, $query ) {
  411. if ( is_array( $query ) ) {
  412. $query = wfArrayToCgi( $query );
  413. }
  414. if ( $query != '' ) {
  415. // Remove the fragment, if there is one
  416. $fragment = false;
  417. $hashPos = strpos( $url, '#' );
  418. if ( $hashPos !== false ) {
  419. $fragment = substr( $url, $hashPos );
  420. $url = substr( $url, 0, $hashPos );
  421. }
  422. // Add parameter
  423. if ( strpos( $url, '?' ) === false ) {
  424. $url .= '?';
  425. } else {
  426. $url .= '&';
  427. }
  428. $url .= $query;
  429. // Put the fragment back
  430. if ( $fragment !== false ) {
  431. $url .= $fragment;
  432. }
  433. }
  434. return $url;
  435. }
  436. /**
  437. * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
  438. * is correct.
  439. *
  440. * The meaning of the PROTO_* constants is as follows:
  441. * PROTO_HTTP: Output a URL starting with http://
  442. * PROTO_HTTPS: Output a URL starting with https://
  443. * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
  444. * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
  445. * on which protocol was used for the current incoming request
  446. * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
  447. * For protocol-relative URLs, use the protocol of $wgCanonicalServer
  448. * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
  449. *
  450. * @todo this won't work with current-path-relative URLs
  451. * like "subdir/foo.html", etc.
  452. *
  453. * @param string $url Either fully-qualified or a local path + query
  454. * @param string|int|null $defaultProto One of the PROTO_* constants. Determines the
  455. * protocol to use if $url or $wgServer is protocol-relative
  456. * @return string|false Fully-qualified URL, current-path-relative URL or false if
  457. * no valid URL can be constructed
  458. */
  459. function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
  460. global $wgServer, $wgCanonicalServer, $wgInternalServer, $wgRequest,
  461. $wgHttpsPort;
  462. if ( $defaultProto === PROTO_CANONICAL ) {
  463. $serverUrl = $wgCanonicalServer;
  464. } elseif ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
  465. // Make $wgInternalServer fall back to $wgServer if not set
  466. $serverUrl = $wgInternalServer;
  467. } else {
  468. $serverUrl = $wgServer;
  469. if ( $defaultProto === PROTO_CURRENT ) {
  470. $defaultProto = $wgRequest->getProtocol() . '://';
  471. }
  472. }
  473. // Analyze $serverUrl to obtain its protocol
  474. $bits = wfParseUrl( $serverUrl );
  475. $serverHasProto = $bits && $bits['scheme'] != '';
  476. if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
  477. if ( $serverHasProto ) {
  478. $defaultProto = $bits['scheme'] . '://';
  479. } else {
  480. // $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
  481. // This really isn't supposed to happen. Fall back to HTTP in this
  482. // ridiculous case.
  483. $defaultProto = PROTO_HTTP;
  484. }
  485. }
  486. $defaultProtoWithoutSlashes = $defaultProto !== null ? substr( $defaultProto, 0, -2 ) : '';
  487. if ( substr( $url, 0, 2 ) == '//' ) {
  488. $url = $defaultProtoWithoutSlashes . $url;
  489. } elseif ( substr( $url, 0, 1 ) == '/' ) {
  490. // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
  491. // otherwise leave it alone.
  492. if ( $serverHasProto ) {
  493. $url = $serverUrl . $url;
  494. } else {
  495. // If an HTTPS URL is synthesized from a protocol-relative $wgServer, allow the
  496. // user to override the port number (T67184)
  497. if ( $defaultProto === PROTO_HTTPS && $wgHttpsPort != 443 ) {
  498. if ( isset( $bits['port'] ) ) {
  499. throw new Exception( 'A protocol-relative $wgServer may not contain a port number' );
  500. }
  501. $url = $defaultProtoWithoutSlashes . $serverUrl . ':' . $wgHttpsPort . $url;
  502. } else {
  503. $url = $defaultProtoWithoutSlashes . $serverUrl . $url;
  504. }
  505. }
  506. }
  507. $bits = wfParseUrl( $url );
  508. if ( $bits && isset( $bits['path'] ) ) {
  509. $bits['path'] = wfRemoveDotSegments( $bits['path'] );
  510. return wfAssembleUrl( $bits );
  511. } elseif ( $bits ) {
  512. # No path to expand
  513. return $url;
  514. } elseif ( substr( $url, 0, 1 ) != '/' ) {
  515. # URL is a relative path
  516. return wfRemoveDotSegments( $url );
  517. }
  518. # Expanded URL is not valid.
  519. return false;
  520. }
  521. /**
  522. * Get the wiki's "server", i.e. the protocol and host part of the URL, with a
  523. * protocol specified using a PROTO_* constant as in wfExpandUrl()
  524. *
  525. * @since 1.32
  526. * @param string|int|null $proto One of the PROTO_* constants.
  527. * @return string The URL
  528. */
  529. function wfGetServerUrl( $proto ) {
  530. $url = wfExpandUrl( '/', $proto );
  531. return substr( $url, 0, -1 );
  532. }
  533. /**
  534. * This function will reassemble a URL parsed with wfParseURL. This is useful
  535. * if you need to edit part of a URL and put it back together.
  536. *
  537. * This is the basic structure used (brackets contain keys for $urlParts):
  538. * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
  539. *
  540. * @todo Need to integrate this into wfExpandUrl (see T34168)
  541. *
  542. * @since 1.19
  543. * @param array $urlParts URL parts, as output from wfParseUrl
  544. * @return string URL assembled from its component parts
  545. */
  546. function wfAssembleUrl( $urlParts ) {
  547. $result = '';
  548. if ( isset( $urlParts['delimiter'] ) ) {
  549. if ( isset( $urlParts['scheme'] ) ) {
  550. $result .= $urlParts['scheme'];
  551. }
  552. $result .= $urlParts['delimiter'];
  553. }
  554. if ( isset( $urlParts['host'] ) ) {
  555. if ( isset( $urlParts['user'] ) ) {
  556. $result .= $urlParts['user'];
  557. if ( isset( $urlParts['pass'] ) ) {
  558. $result .= ':' . $urlParts['pass'];
  559. }
  560. $result .= '@';
  561. }
  562. $result .= $urlParts['host'];
  563. if ( isset( $urlParts['port'] ) ) {
  564. $result .= ':' . $urlParts['port'];
  565. }
  566. }
  567. if ( isset( $urlParts['path'] ) ) {
  568. $result .= $urlParts['path'];
  569. }
  570. if ( isset( $urlParts['query'] ) ) {
  571. $result .= '?' . $urlParts['query'];
  572. }
  573. if ( isset( $urlParts['fragment'] ) ) {
  574. $result .= '#' . $urlParts['fragment'];
  575. }
  576. return $result;
  577. }
  578. /**
  579. * Remove all dot-segments in the provided URL path. For example,
  580. * '/a/./b/../c/' becomes '/a/c/'. For details on the algorithm, please see
  581. * RFC3986 section 5.2.4.
  582. *
  583. * @todo Need to integrate this into wfExpandUrl (see T34168)
  584. *
  585. * @since 1.19
  586. *
  587. * @param string $urlPath URL path, potentially containing dot-segments
  588. * @return string URL path with all dot-segments removed
  589. */
  590. function wfRemoveDotSegments( $urlPath ) {
  591. $output = '';
  592. $inputOffset = 0;
  593. $inputLength = strlen( $urlPath );
  594. while ( $inputOffset < $inputLength ) {
  595. $prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
  596. $prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
  597. $prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
  598. $prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
  599. $trimOutput = false;
  600. if ( $prefixLengthTwo == './' ) {
  601. # Step A, remove leading "./"
  602. $inputOffset += 2;
  603. } elseif ( $prefixLengthThree == '../' ) {
  604. # Step A, remove leading "../"
  605. $inputOffset += 3;
  606. } elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
  607. # Step B, replace leading "/.$" with "/"
  608. $inputOffset += 1;
  609. $urlPath[$inputOffset] = '/';
  610. } elseif ( $prefixLengthThree == '/./' ) {
  611. # Step B, replace leading "/./" with "/"
  612. $inputOffset += 2;
  613. } elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
  614. # Step C, replace leading "/..$" with "/" and
  615. # remove last path component in output
  616. $inputOffset += 2;
  617. $urlPath[$inputOffset] = '/';
  618. $trimOutput = true;
  619. } elseif ( $prefixLengthFour == '/../' ) {
  620. # Step C, replace leading "/../" with "/" and
  621. # remove last path component in output
  622. $inputOffset += 3;
  623. $trimOutput = true;
  624. } elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
  625. # Step D, remove "^.$"
  626. $inputOffset += 1;
  627. } elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
  628. # Step D, remove "^..$"
  629. $inputOffset += 2;
  630. } else {
  631. # Step E, move leading path segment to output
  632. if ( $prefixLengthOne == '/' ) {
  633. $slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
  634. } else {
  635. $slashPos = strpos( $urlPath, '/', $inputOffset );
  636. }
  637. if ( $slashPos === false ) {
  638. $output .= substr( $urlPath, $inputOffset );
  639. $inputOffset = $inputLength;
  640. } else {
  641. $output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
  642. $inputOffset += $slashPos - $inputOffset;
  643. }
  644. }
  645. if ( $trimOutput ) {
  646. $slashPos = strrpos( $output, '/' );
  647. if ( $slashPos === false ) {
  648. $output = '';
  649. } else {
  650. $output = substr( $output, 0, $slashPos );
  651. }
  652. }
  653. }
  654. return $output;
  655. }
  656. /**
  657. * Returns a regular expression of url protocols
  658. *
  659. * @param bool $includeProtocolRelative If false, remove '//' from the returned protocol list.
  660. * DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
  661. * @return string
  662. */
  663. function wfUrlProtocols( $includeProtocolRelative = true ) {
  664. global $wgUrlProtocols;
  665. // Cache return values separately based on $includeProtocolRelative
  666. static $withProtRel = null, $withoutProtRel = null;
  667. $cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
  668. if ( !is_null( $cachedValue ) ) {
  669. return $cachedValue;
  670. }
  671. // Support old-style $wgUrlProtocols strings, for backwards compatibility
  672. // with LocalSettings files from 1.5
  673. if ( is_array( $wgUrlProtocols ) ) {
  674. $protocols = [];
  675. foreach ( $wgUrlProtocols as $protocol ) {
  676. // Filter out '//' if !$includeProtocolRelative
  677. if ( $includeProtocolRelative || $protocol !== '//' ) {
  678. $protocols[] = preg_quote( $protocol, '/' );
  679. }
  680. }
  681. $retval = implode( '|', $protocols );
  682. } else {
  683. // Ignore $includeProtocolRelative in this case
  684. // This case exists for pre-1.6 compatibility, and we can safely assume
  685. // that '//' won't appear in a pre-1.6 config because protocol-relative
  686. // URLs weren't supported until 1.18
  687. $retval = $wgUrlProtocols;
  688. }
  689. // Cache return value
  690. if ( $includeProtocolRelative ) {
  691. $withProtRel = $retval;
  692. } else {
  693. $withoutProtRel = $retval;
  694. }
  695. return $retval;
  696. }
  697. /**
  698. * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
  699. * you need a regex that matches all URL protocols but does not match protocol-
  700. * relative URLs
  701. * @return string
  702. */
  703. function wfUrlProtocolsWithoutProtRel() {
  704. return wfUrlProtocols( false );
  705. }
  706. /**
  707. * parse_url() work-alike, but non-broken. Differences:
  708. *
  709. * 1) Does not raise warnings on bad URLs (just returns false).
  710. * 2) Handles protocols that don't use :// (e.g., mailto: and news:, as well as
  711. * protocol-relative URLs) correctly.
  712. * 3) Adds a "delimiter" element to the array (see (2)).
  713. * 4) Verifies that the protocol is on the $wgUrlProtocols whitelist.
  714. * 5) Rejects some invalid URLs that parse_url doesn't, e.g. the empty string or URLs starting with
  715. * a line feed character.
  716. *
  717. * @param string $url A URL to parse
  718. * @return string[]|bool Bits of the URL in an associative array, or false on failure.
  719. * Possible fields:
  720. * - scheme: URI scheme (protocol), e.g. 'http', 'mailto'. Lowercase, always present, but can
  721. * be an empty string for protocol-relative URLs.
  722. * - delimiter: either '://', ':' or '//'. Always present.
  723. * - host: domain name / IP. Always present, but could be an empty string, e.g. for file: URLs.
  724. * - user: user name, e.g. for HTTP Basic auth URLs such as http://user:pass@example.com/
  725. * Missing when there is no username.
  726. * - pass: password, same as above.
  727. * - path: path including the leading /. Will be missing when empty (e.g. 'http://example.com')
  728. * - query: query string (as a string; see wfCgiToArray() for parsing it), can be missing.
  729. * - fragment: the part after #, can be missing.
  730. */
  731. function wfParseUrl( $url ) {
  732. global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
  733. // Protocol-relative URLs are handled really badly by parse_url(). It's so
  734. // bad that the easiest way to handle them is to just prepend 'http:' and
  735. // strip the protocol out later.
  736. $wasRelative = substr( $url, 0, 2 ) == '//';
  737. if ( $wasRelative ) {
  738. $url = "http:$url";
  739. }
  740. AtEase::suppressWarnings();
  741. $bits = parse_url( $url );
  742. AtEase::restoreWarnings();
  743. // parse_url() returns an array without scheme for some invalid URLs, e.g.
  744. // parse_url("%0Ahttp://example.com") == [ 'host' => '%0Ahttp', 'path' => 'example.com' ]
  745. if ( !$bits || !isset( $bits['scheme'] ) ) {
  746. return false;
  747. }
  748. // parse_url() incorrectly handles schemes case-sensitively. Convert it to lowercase.
  749. $bits['scheme'] = strtolower( $bits['scheme'] );
  750. // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
  751. if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
  752. $bits['delimiter'] = '://';
  753. } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
  754. $bits['delimiter'] = ':';
  755. // parse_url detects for news: and mailto: the host part of an url as path
  756. // We have to correct this wrong detection
  757. if ( isset( $bits['path'] ) ) {
  758. $bits['host'] = $bits['path'];
  759. $bits['path'] = '';
  760. }
  761. } else {
  762. return false;
  763. }
  764. /* Provide an empty host for eg. file:/// urls (see T30627) */
  765. if ( !isset( $bits['host'] ) ) {
  766. $bits['host'] = '';
  767. // See T47069
  768. if ( isset( $bits['path'] ) ) {
  769. /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
  770. if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
  771. $bits['path'] = '/' . $bits['path'];
  772. }
  773. } else {
  774. $bits['path'] = '';
  775. }
  776. }
  777. // If the URL was protocol-relative, fix scheme and delimiter
  778. if ( $wasRelative ) {
  779. $bits['scheme'] = '';
  780. $bits['delimiter'] = '//';
  781. }
  782. return $bits;
  783. }
  784. /**
  785. * Take a URL, make sure it's expanded to fully qualified, and replace any
  786. * encoded non-ASCII Unicode characters with their UTF-8 original forms
  787. * for more compact display and legibility for local audiences.
  788. *
  789. * @todo handle punycode domains too
  790. *
  791. * @param string $url
  792. * @return string
  793. */
  794. function wfExpandIRI( $url ) {
  795. return preg_replace_callback(
  796. '/((?:%[89A-F][0-9A-F])+)/i',
  797. function ( array $matches ) {
  798. return urldecode( $matches[1] );
  799. },
  800. wfExpandUrl( $url )
  801. );
  802. }
  803. /**
  804. * Check whether a given URL has a domain that occurs in a given set of domains
  805. * @param string $url
  806. * @param array $domains Array of domains (strings)
  807. * @return bool True if the host part of $url ends in one of the strings in $domains
  808. */
  809. function wfMatchesDomainList( $url, $domains ) {
  810. $bits = wfParseUrl( $url );
  811. if ( is_array( $bits ) && isset( $bits['host'] ) ) {
  812. $host = '.' . $bits['host'];
  813. foreach ( (array)$domains as $domain ) {
  814. $domain = '.' . $domain;
  815. if ( substr( $host, -strlen( $domain ) ) === $domain ) {
  816. return true;
  817. }
  818. }
  819. }
  820. return false;
  821. }
  822. /**
  823. * Sends a line to the debug log if enabled or, optionally, to a comment in output.
  824. * In normal operation this is a NOP.
  825. *
  826. * Controlling globals:
  827. * $wgDebugLogFile - points to the log file
  828. * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output.
  829. * $wgDebugComments - if on, some debug items may appear in comments in the HTML output.
  830. *
  831. * @since 1.25 support for additional context data
  832. *
  833. * @param string $text
  834. * @param string|bool $dest Destination of the message:
  835. * - 'all': both to the log and HTML (debug toolbar or HTML comments)
  836. * - 'private': excluded from HTML output
  837. * For backward compatibility, it can also take a boolean:
  838. * - true: same as 'all'
  839. * - false: same as 'private'
  840. * @param array $context Additional logging context data
  841. */
  842. function wfDebug( $text, $dest = 'all', array $context = [] ) {
  843. global $wgDebugRawPage, $wgDebugLogPrefix;
  844. global $wgDebugTimestamps;
  845. if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
  846. return;
  847. }
  848. $text = trim( $text );
  849. if ( $wgDebugTimestamps ) {
  850. $context['seconds_elapsed'] = sprintf(
  851. '%6.4f',
  852. microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT']
  853. );
  854. $context['memory_used'] = sprintf(
  855. '%5.1fM',
  856. ( memory_get_usage( true ) / ( 1024 * 1024 ) )
  857. );
  858. }
  859. if ( $wgDebugLogPrefix !== '' ) {
  860. $context['prefix'] = $wgDebugLogPrefix;
  861. }
  862. $context['private'] = ( $dest === false || $dest === 'private' );
  863. $logger = LoggerFactory::getInstance( 'wfDebug' );
  864. $logger->debug( $text, $context );
  865. }
  866. /**
  867. * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
  868. * @return bool
  869. */
  870. function wfIsDebugRawPage() {
  871. static $cache;
  872. if ( $cache !== null ) {
  873. return $cache;
  874. }
  875. // Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
  876. // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
  877. if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
  878. || (
  879. isset( $_SERVER['SCRIPT_NAME'] )
  880. && substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
  881. )
  882. ) {
  883. $cache = true;
  884. } else {
  885. $cache = false;
  886. }
  887. return $cache;
  888. }
  889. /**
  890. * Send a line giving PHP memory usage.
  891. *
  892. * @param bool $exact Print exact byte values instead of kibibytes (default: false)
  893. */
  894. function wfDebugMem( $exact = false ) {
  895. $mem = memory_get_usage();
  896. if ( !$exact ) {
  897. $mem = floor( $mem / 1024 ) . ' KiB';
  898. } else {
  899. $mem .= ' B';
  900. }
  901. wfDebug( "Memory usage: $mem\n" );
  902. }
  903. /**
  904. * Send a line to a supplementary debug log file, if configured, or main debug
  905. * log if not.
  906. *
  907. * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to
  908. * a string filename or an associative array mapping 'destination' to the
  909. * desired filename. The associative array may also contain a 'sample' key
  910. * with an integer value, specifying a sampling factor. Sampled log events
  911. * will be emitted with a 1 in N random chance.
  912. *
  913. * @since 1.23 support for sampling log messages via $wgDebugLogGroups.
  914. * @since 1.25 support for additional context data
  915. * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi
  916. *
  917. * @param string $logGroup
  918. * @param string $text
  919. * @param string|bool $dest Destination of the message:
  920. * - 'all': both to the log and HTML (debug toolbar or HTML comments)
  921. * - 'private': only to the specific log if set in $wgDebugLogGroups and
  922. * discarded otherwise
  923. * For backward compatibility, it can also take a boolean:
  924. * - true: same as 'all'
  925. * - false: same as 'private'
  926. * @param array $context Additional logging context data
  927. */
  928. function wfDebugLog(
  929. $logGroup, $text, $dest = 'all', array $context = []
  930. ) {
  931. $text = trim( $text );
  932. $logger = LoggerFactory::getInstance( $logGroup );
  933. $context['private'] = ( $dest === false || $dest === 'private' );
  934. $logger->info( $text, $context );
  935. }
  936. /**
  937. * Log for database errors
  938. *
  939. * @since 1.25 support for additional context data
  940. *
  941. * @param string $text Database error message.
  942. * @param array $context Additional logging context data
  943. */
  944. function wfLogDBError( $text, array $context = [] ) {
  945. $logger = LoggerFactory::getInstance( 'wfLogDBError' );
  946. $logger->error( trim( $text ), $context );
  947. }
  948. /**
  949. * Throws a warning that $function is deprecated
  950. *
  951. * @param string $function Function that is deprecated.
  952. * @param string|bool $version Version of MediaWiki that the function
  953. * was deprecated in (Added in 1.19).
  954. * @param string|bool $component Component to which the function belongs.
  955. * If false, it is assumed the function is in MediaWiki core (Added in 1.19).
  956. * @param int $callerOffset How far up the call stack is the original
  957. * caller. 2 = function that called the function that called
  958. * wfDeprecated (Added in 1.20).
  959. *
  960. * @throws Exception If the MediaWiki version number is not a string or boolean.
  961. */
  962. function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
  963. if ( is_string( $version ) || is_bool( $version ) ) {
  964. MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
  965. } else {
  966. throw new Exception(
  967. "MediaWiki version must either be a string or a boolean. " .
  968. "Example valid version: '1.33'"
  969. );
  970. }
  971. }
  972. /**
  973. * Send a warning either to the debug log or in a PHP error depending on
  974. * $wgDevelopmentWarnings. To log warnings in production, use wfLogWarning() instead.
  975. *
  976. * @param string $msg Message to send
  977. * @param int $callerOffset Number of items to go back in the backtrace to
  978. * find the correct caller (1 = function calling wfWarn, ...)
  979. * @param int $level PHP error level; defaults to E_USER_NOTICE;
  980. * only used when $wgDevelopmentWarnings is true
  981. */
  982. function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
  983. MWDebug::warning( $msg, $callerOffset + 1, $level, 'auto' );
  984. }
  985. /**
  986. * Send a warning as a PHP error and the debug log. This is intended for logging
  987. * warnings in production. For logging development warnings, use WfWarn instead.
  988. *
  989. * @param string $msg Message to send
  990. * @param int $callerOffset Number of items to go back in the backtrace to
  991. * find the correct caller (1 = function calling wfLogWarning, ...)
  992. * @param int $level PHP error level; defaults to E_USER_WARNING
  993. */
  994. function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) {
  995. MWDebug::warning( $msg, $callerOffset + 1, $level, 'production' );
  996. }
  997. /**
  998. * @todo document
  999. * @todo Move logic to MediaWiki.php
  1000. */
  1001. function wfLogProfilingData() {
  1002. global $wgDebugLogGroups, $wgDebugRawPage;
  1003. $context = RequestContext::getMain();
  1004. $request = $context->getRequest();
  1005. $profiler = Profiler::instance();
  1006. $profiler->setContext( $context );
  1007. $profiler->logData();
  1008. // Send out any buffered statsd metrics as needed
  1009. MediaWiki::emitBufferedStatsdData(
  1010. MediaWikiServices::getInstance()->getStatsdDataFactory(),
  1011. $context->getConfig()
  1012. );
  1013. // Profiling must actually be enabled...
  1014. if ( $profiler instanceof ProfilerStub ) {
  1015. return;
  1016. }
  1017. if ( isset( $wgDebugLogGroups['profileoutput'] )
  1018. && $wgDebugLogGroups['profileoutput'] === false
  1019. ) {
  1020. // Explicitly disabled
  1021. return;
  1022. }
  1023. if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
  1024. return;
  1025. }
  1026. $ctx = [ 'elapsed' => $request->getElapsedTime() ];
  1027. if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
  1028. $ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
  1029. }
  1030. if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
  1031. $ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP'];
  1032. }
  1033. if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
  1034. $ctx['from'] = $_SERVER['HTTP_FROM'];
  1035. }
  1036. if ( isset( $ctx['forwarded_for'] ) ||
  1037. isset( $ctx['client_ip'] ) ||
  1038. isset( $ctx['from'] ) ) {
  1039. // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
  1040. $ctx['proxy'] = $_SERVER['REMOTE_ADDR'];
  1041. }
  1042. // Don't load $wgUser at this late stage just for statistics purposes
  1043. // @todo FIXME: We can detect some anons even if it is not loaded.
  1044. // See User::getId()
  1045. $user = $context->getUser();
  1046. $ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon();
  1047. // Command line script uses a FauxRequest object which does not have
  1048. // any knowledge about an URL and throw an exception instead.
  1049. try {
  1050. $ctx['url'] = urldecode( $request->getRequestURL() );
  1051. } catch ( Exception $ignored ) {
  1052. // no-op
  1053. }
  1054. $ctx['output'] = $profiler->getOutput();
  1055. $log = LoggerFactory::getInstance( 'profileoutput' );
  1056. $log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx );
  1057. }
  1058. /**
  1059. * Increment a statistics counter
  1060. *
  1061. * @param string $key
  1062. * @param int $count
  1063. * @return void
  1064. */
  1065. function wfIncrStats( $key, $count = 1 ) {
  1066. $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
  1067. $stats->updateCount( $key, $count );
  1068. }
  1069. /**
  1070. * Check whether the wiki is in read-only mode.
  1071. *
  1072. * @return bool
  1073. */
  1074. function wfReadOnly() {
  1075. return MediaWikiServices::getInstance()->getReadOnlyMode()
  1076. ->isReadOnly();
  1077. }
  1078. /**
  1079. * Check if the site is in read-only mode and return the message if so
  1080. *
  1081. * This checks wfConfiguredReadOnlyReason() and the main load balancer
  1082. * for replica DB lag. This may result in DB connection being made.
  1083. *
  1084. * @return string|bool String when in read-only mode; false otherwise
  1085. */
  1086. function wfReadOnlyReason() {
  1087. return MediaWikiServices::getInstance()->getReadOnlyMode()
  1088. ->getReason();
  1089. }
  1090. /**
  1091. * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
  1092. *
  1093. * @return string|bool String when in read-only mode; false otherwise
  1094. * @since 1.27
  1095. */
  1096. function wfConfiguredReadOnlyReason() {
  1097. return MediaWikiServices::getInstance()->getConfiguredReadOnlyMode()
  1098. ->getReason();
  1099. }
  1100. /**
  1101. * Return a Language object from $langcode
  1102. *
  1103. * @param Language|string|bool $langcode Either:
  1104. * - a Language object
  1105. * - code of the language to get the message for, if it is
  1106. * a valid code create a language for that language, if
  1107. * it is a string but not a valid code then make a basic
  1108. * language object
  1109. * - a boolean: if it's false then use the global object for
  1110. * the current user's language (as a fallback for the old parameter
  1111. * functionality), or if it is true then use global object
  1112. * for the wiki's content language.
  1113. * @return Language
  1114. */
  1115. function wfGetLangObj( $langcode = false ) {
  1116. # Identify which language to get or create a language object for.
  1117. # Using is_object here due to Stub objects.
  1118. if ( is_object( $langcode ) ) {
  1119. # Great, we already have the object (hopefully)!
  1120. return $langcode;
  1121. }
  1122. global $wgLanguageCode;
  1123. if ( $langcode === true || $langcode === $wgLanguageCode ) {
  1124. # $langcode is the language code of the wikis content language object.
  1125. # or it is a boolean and value is true
  1126. return MediaWikiServices::getInstance()->getContentLanguage();
  1127. }
  1128. global $wgLang;
  1129. if ( $langcode === false || $langcode === $wgLang->getCode() ) {
  1130. # $langcode is the language code of user language object.
  1131. # or it was a boolean and value is false
  1132. return $wgLang;
  1133. }
  1134. $validCodes = array_keys( Language::fetchLanguageNames() );
  1135. if ( in_array( $langcode, $validCodes ) ) {
  1136. # $langcode corresponds to a valid language.
  1137. return Language::factory( $langcode );
  1138. }
  1139. # $langcode is a string, but not a valid language code; use content language.
  1140. wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
  1141. return MediaWikiServices::getInstance()->getContentLanguage();
  1142. }
  1143. /**
  1144. * This is the function for getting translated interface messages.
  1145. *
  1146. * @see Message class for documentation how to use them.
  1147. * @see https://www.mediawiki.org/wiki/Manual:Messages_API
  1148. *
  1149. * This function replaces all old wfMsg* functions.
  1150. *
  1151. * @param string|string[]|MessageSpecifier $key Message key, or array of keys, or a MessageSpecifier
  1152. * @param string|string[] ...$params Normal message parameters
  1153. * @return Message
  1154. *
  1155. * @since 1.17
  1156. *
  1157. * @see Message::__construct
  1158. */
  1159. function wfMessage( $key, ...$params ) {
  1160. $message = new Message( $key );
  1161. // We call Message::params() to reduce code duplication
  1162. if ( $params ) {
  1163. $message->params( ...$params );
  1164. }
  1165. return $message;
  1166. }
  1167. /**
  1168. * This function accepts multiple message keys and returns a message instance
  1169. * for the first message which is non-empty. If all messages are empty then an
  1170. * instance of the first message key is returned.
  1171. *
  1172. * @param string ...$keys Message keys
  1173. * @return Message
  1174. *
  1175. * @since 1.18
  1176. *
  1177. * @see Message::newFallbackSequence
  1178. */
  1179. function wfMessageFallback( ...$keys ) {
  1180. return Message::newFallbackSequence( ...$keys );
  1181. }
  1182. /**
  1183. * Replace message parameter keys on the given formatted output.
  1184. *
  1185. * @param string $message
  1186. * @param array $args
  1187. * @return string
  1188. * @private
  1189. */
  1190. function wfMsgReplaceArgs( $message, $args ) {
  1191. # Fix windows line-endings
  1192. # Some messages are split with explode("\n", $msg)
  1193. $message = str_replace( "\r", '', $message );
  1194. // Replace arguments
  1195. if ( is_array( $args ) && $args ) {
  1196. if ( is_array( $args[0] ) ) {
  1197. $args = array_values( $args[0] );
  1198. }
  1199. $replacementKeys = [];
  1200. foreach ( $args as $n => $param ) {
  1201. $replacementKeys['$' . ( $n + 1 )] = $param;
  1202. }
  1203. $message = strtr( $message, $replacementKeys );
  1204. }
  1205. return $message;
  1206. }
  1207. /**
  1208. * Fetch server name for use in error reporting etc.
  1209. * Use real server name if available, so we know which machine
  1210. * in a server farm generated the current page.
  1211. *
  1212. * @return string
  1213. */
  1214. function wfHostname() {
  1215. static $host;
  1216. if ( is_null( $host ) ) {
  1217. # Hostname overriding
  1218. global $wgOverrideHostname;
  1219. if ( $wgOverrideHostname !== false ) {
  1220. # Set static and skip any detection
  1221. $host = $wgOverrideHostname;
  1222. return $host;
  1223. }
  1224. if ( function_exists( 'posix_uname' ) ) {
  1225. // This function not present on Windows
  1226. $uname = posix_uname();
  1227. } else {
  1228. $uname = false;
  1229. }
  1230. if ( is_array( $uname ) && isset( $uname['nodename'] ) ) {
  1231. $host = $uname['nodename'];
  1232. } elseif ( getenv( 'COMPUTERNAME' ) ) {
  1233. # Windows computer name
  1234. $host = getenv( 'COMPUTERNAME' );
  1235. } else {
  1236. # This may be a virtual server.
  1237. $host = $_SERVER['SERVER_NAME'];
  1238. }
  1239. }
  1240. return $host;
  1241. }
  1242. /**
  1243. * Returns a script tag that stores the amount of time it took MediaWiki to
  1244. * handle the request in milliseconds as 'wgBackendResponseTime'.
  1245. *
  1246. * If $wgShowHostnames is true, the script will also set 'wgHostname' to the
  1247. * hostname of the server handling the request.
  1248. *
  1249. * @param string|null $nonce Value from OutputPage::getCSPNonce
  1250. * @return string|WrappedString HTML
  1251. */
  1252. function wfReportTime( $nonce = null ) {
  1253. global $wgShowHostnames;
  1254. $elapsed = ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] );
  1255. // seconds to milliseconds
  1256. $responseTime = round( $elapsed * 1000 );
  1257. $reportVars = [ 'wgBackendResponseTime' => $responseTime ];
  1258. if ( $wgShowHostnames ) {
  1259. $reportVars['wgHostname'] = wfHostname();
  1260. }
  1261. return Skin::makeVariablesScript( $reportVars, $nonce );
  1262. }
  1263. /**
  1264. * Safety wrapper for debug_backtrace().
  1265. *
  1266. * Will return an empty array if debug_backtrace is disabled, otherwise
  1267. * the output from debug_backtrace() (trimmed).
  1268. *
  1269. * @param int $limit This parameter can be used to limit the number of stack frames returned
  1270. *
  1271. * @return array Array of backtrace information
  1272. */
  1273. function wfDebugBacktrace( $limit = 0 ) {
  1274. static $disabled = null;
  1275. if ( is_null( $disabled ) ) {
  1276. $disabled = !function_exists( 'debug_backtrace' );
  1277. if ( $disabled ) {
  1278. wfDebug( "debug_backtrace() is disabled\n" );
  1279. }
  1280. }
  1281. if ( $disabled ) {
  1282. return [];
  1283. }
  1284. if ( $limit ) {
  1285. return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 1 ), 1 );
  1286. } else {
  1287. return array_slice( debug_backtrace(), 1 );
  1288. }
  1289. }
  1290. /**
  1291. * Get a debug backtrace as a string
  1292. *
  1293. * @param bool|null $raw If true, the return value is plain text. If false, HTML.
  1294. * Defaults to $wgCommandLineMode if unset.
  1295. * @return string
  1296. * @since 1.25 Supports $raw parameter.
  1297. */
  1298. function wfBacktrace( $raw = null ) {
  1299. global $wgCommandLineMode;
  1300. if ( $raw === null ) {
  1301. $raw = $wgCommandLineMode;
  1302. }
  1303. if ( $raw ) {
  1304. $frameFormat = "%s line %s calls %s()\n";
  1305. $traceFormat = "%s";
  1306. } else {
  1307. $frameFormat = "<li>%s line %s calls %s()</li>\n";
  1308. $traceFormat = "<ul>\n%s</ul>\n";
  1309. }
  1310. $frames = array_map( function ( $frame ) use ( $frameFormat ) {
  1311. $file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
  1312. $line = $frame['line'] ?? '-';
  1313. $call = $frame['function'];
  1314. if ( !empty( $frame['class'] ) ) {
  1315. $call = $frame['class'] . $frame['type'] . $call;
  1316. }
  1317. return sprintf( $frameFormat, $file, $line, $call );
  1318. }, wfDebugBacktrace() );
  1319. return sprintf( $traceFormat, implode( '', $frames ) );
  1320. }
  1321. /**
  1322. * Get the name of the function which called this function
  1323. * wfGetCaller( 1 ) is the function with the wfGetCaller() call (ie. __FUNCTION__)
  1324. * wfGetCaller( 2 ) [default] is the caller of the function running wfGetCaller()
  1325. * wfGetCaller( 3 ) is the parent of that.
  1326. *
  1327. * @param int $level
  1328. * @return string
  1329. */
  1330. function wfGetCaller( $level = 2 ) {
  1331. $backtrace = wfDebugBacktrace( $level + 1 );
  1332. if ( isset( $backtrace[$level] ) ) {
  1333. return wfFormatStackFrame( $backtrace[$level] );
  1334. } else {
  1335. return 'unknown';
  1336. }
  1337. }
  1338. /**
  1339. * Return a string consisting of callers in the stack. Useful sometimes
  1340. * for profiling specific points.
  1341. *
  1342. * @param int $limit The maximum depth of the stack frame to return, or false for the entire stack.
  1343. * @return string
  1344. */
  1345. function wfGetAllCallers( $limit = 3 ) {
  1346. $trace = array_reverse( wfDebugBacktrace() );
  1347. if ( !$limit || $limit > count( $trace ) - 1 ) {
  1348. $limit = count( $trace ) - 1;
  1349. }
  1350. $trace = array_slice( $trace, -$limit - 1, $limit );
  1351. return implode( '/', array_map( 'wfFormatStackFrame', $trace ) );
  1352. }
  1353. /**
  1354. * Return a string representation of frame
  1355. *
  1356. * @param array $frame
  1357. * @return string
  1358. */
  1359. function wfFormatStackFrame( $frame ) {
  1360. if ( !isset( $frame['function'] ) ) {
  1361. return 'NO_FUNCTION_GIVEN';
  1362. }
  1363. return isset( $frame['class'] ) && isset( $frame['type'] ) ?
  1364. $frame['class'] . $frame['type'] . $frame['function'] :
  1365. $frame['function'];
  1366. }
  1367. /* Some generic result counters, pulled out of SearchEngine */
  1368. /**
  1369. * @todo document
  1370. *
  1371. * @param int $offset
  1372. * @param int $limit
  1373. * @return string
  1374. */
  1375. function wfShowingResults( $offset, $limit ) {
  1376. return wfMessage( 'showingresults' )->numParams( $limit, $offset + 1 )->parse();
  1377. }
  1378. /**
  1379. * Whether the client accept gzip encoding
  1380. *
  1381. * Uses the Accept-Encoding header to check if the client supports gzip encoding.
  1382. * Use this when considering to send a gzip-encoded response to the client.
  1383. *
  1384. * @param bool $force Forces another check even if we already have a cached result.
  1385. * @return bool
  1386. */
  1387. function wfClientAcceptsGzip( $force = false ) {
  1388. static $result = null;
  1389. if ( $result === null || $force ) {
  1390. $result = false;
  1391. if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
  1392. # @todo FIXME: We may want to blacklist some broken browsers
  1393. $m = [];
  1394. if ( preg_match(
  1395. '/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
  1396. $_SERVER['HTTP_ACCEPT_ENCODING'],
  1397. $m
  1398. )
  1399. ) {
  1400. if ( isset( $m[2] ) && ( $m[1] == 'q' ) && ( $m[2] == 0 ) ) {
  1401. $result = false;
  1402. return $result;
  1403. }
  1404. wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
  1405. $result = true;
  1406. }
  1407. }
  1408. }
  1409. return $result;
  1410. }
  1411. /**
  1412. * Escapes the given text so that it may be output using addWikiText()
  1413. * without any linking, formatting, etc. making its way through. This
  1414. * is achieved by substituting certain characters with HTML entities.
  1415. * As required by the callers, "<nowiki>" is not used.
  1416. *
  1417. * @param string $text Text to be escaped
  1418. * @param-taint $text escapes_html
  1419. * @return string
  1420. */
  1421. function wfEscapeWikiText( $text ) {
  1422. global $wgEnableMagicLinks;
  1423. static $repl = null, $repl2 = null;
  1424. if ( $repl === null || defined( 'MW_PARSER_TEST' ) || defined( 'MW_PHPUNIT_TEST' ) ) {
  1425. // Tests depend upon being able to change $wgEnableMagicLinks, so don't cache
  1426. // in those situations
  1427. $repl = [
  1428. '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
  1429. '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
  1430. '{' => '&#123;', '|' => '&#124;', '}' => '&#125;', ';' => '&#59;',
  1431. "\n#" => "\n&#35;", "\r#" => "\r&#35;",
  1432. "\n*" => "\n&#42;", "\r*" => "\r&#42;",
  1433. "\n:" => "\n&#58;", "\r:" => "\r&#58;",
  1434. "\n " => "\n&#32;", "\r " => "\r&#32;",
  1435. "\n\n" => "\n&#10;", "\r\n" => "&#13;\n",
  1436. "\n\r" => "\n&#13;", "\r\r" => "\r&#13;",
  1437. "\n\t" => "\n&#9;", "\r\t" => "\r&#9;", // "\n\t\n" is treated like "\n\n"
  1438. "\n----" => "\n&#45;---", "\r----" => "\r&#45;---",
  1439. '__' => '_&#95;', '://' => '&#58;//',
  1440. ];
  1441. $magicLinks = array_keys( array_filter( $wgEnableMagicLinks ) );
  1442. // We have to catch everything "\s" matches in PCRE
  1443. foreach ( $magicLinks as $magic ) {
  1444. $repl["$magic "] = "$magic&#32;";
  1445. $repl["$magic\t"] = "$magic&#9;";
  1446. $repl["$magic\r"] = "$magic&#13;";
  1447. $repl["$magic\n"] = "$magic&#10;";
  1448. $repl["$magic\f"] = "$magic&#12;";
  1449. }
  1450. // And handle protocols that don't use "://"
  1451. global $wgUrlProtocols;
  1452. $repl2 = [];
  1453. foreach ( $wgUrlProtocols as $prot ) {
  1454. if ( substr( $prot, -1 ) === ':' ) {
  1455. $repl2[] = preg_quote( substr( $prot, 0, -1 ), '/' );
  1456. }
  1457. }
  1458. $repl2 = $repl2 ? '/\b(' . implode( '|', $repl2 ) . '):/i' : '/^(?!)/';
  1459. }
  1460. $text = substr( strtr( "\n$text", $repl ), 1 );
  1461. $text = preg_replace( $repl2, '$1&#58;', $text );
  1462. return $text;
  1463. }
  1464. /**
  1465. * Sets dest to source and returns the original value of dest
  1466. * If source is NULL, it just returns the value, it doesn't set the variable
  1467. * If force is true, it will set the value even if source is NULL
  1468. *
  1469. * @param mixed &$dest
  1470. * @param mixed $source
  1471. * @param bool $force
  1472. * @return mixed
  1473. */
  1474. function wfSetVar( &$dest, $source, $force = false ) {
  1475. $temp = $dest;
  1476. if ( !is_null( $source ) || $force ) {
  1477. $dest = $source;
  1478. }
  1479. return $temp;
  1480. }
  1481. /**
  1482. * As for wfSetVar except setting a bit
  1483. *
  1484. * @param int &$dest
  1485. * @param int $bit
  1486. * @param bool $state
  1487. *
  1488. * @return bool
  1489. */
  1490. function wfSetBit( &$dest, $bit, $state = true ) {
  1491. $temp = (bool)( $dest & $bit );
  1492. if ( !is_null( $state ) ) {
  1493. if ( $state ) {
  1494. $dest |= $bit;
  1495. } else {
  1496. $dest &= ~$bit;
  1497. }
  1498. }
  1499. return $temp;
  1500. }
  1501. /**
  1502. * A wrapper around the PHP function var_export().
  1503. * Either print it or add it to the regular output ($wgOut).
  1504. *
  1505. * @param mixed $var A PHP variable to dump.
  1506. */
  1507. function wfVarDump( $var ) {
  1508. global $wgOut;
  1509. $s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
  1510. if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
  1511. print $s;
  1512. } else {
  1513. $wgOut->addHTML( $s );
  1514. }
  1515. }
  1516. /**
  1517. * Provide a simple HTTP error.
  1518. *
  1519. * @param int|string $code
  1520. * @param string $label
  1521. * @param string $desc
  1522. */
  1523. function wfHttpError( $code, $label, $desc ) {
  1524. global $wgOut;
  1525. HttpStatus::header( $code );
  1526. if ( $wgOut ) {
  1527. $wgOut->disable();
  1528. $wgOut->sendCacheControl();
  1529. }
  1530. MediaWiki\HeaderCallback::warnIfHeadersSent();
  1531. header( 'Content-type: text/html; charset=utf-8' );
  1532. print '<!DOCTYPE html>' .
  1533. '<html><head><title>' .
  1534. htmlspecialchars( $label ) .
  1535. '</title></head><body><h1>' .
  1536. htmlspecialchars( $label ) .
  1537. '</h1><p>' .
  1538. nl2br( htmlspecialchars( $desc ) ) .
  1539. "</p></body></html>\n";
  1540. }
  1541. /**
  1542. * Clear away any user-level output buffers, discarding contents.
  1543. *
  1544. * Suitable for 'starting afresh', for instance when streaming
  1545. * relatively large amounts of data without buffering, or wanting to
  1546. * output image files without ob_gzhandler's compression.
  1547. *
  1548. * The optional $resetGzipEncoding parameter controls suppression of
  1549. * the Content-Encoding header sent by ob_gzhandler; by default it
  1550. * is left. See comments for wfClearOutputBuffers() for why it would
  1551. * be used.
  1552. *
  1553. * Note that some PHP configuration options may add output buffer
  1554. * layers which cannot be removed; these are left in place.
  1555. *
  1556. * @param bool $resetGzipEncoding
  1557. */
  1558. function wfResetOutputBuffers( $resetGzipEncoding = true ) {
  1559. if ( $resetGzipEncoding ) {
  1560. // Suppress Content-Encoding and Content-Length
  1561. // headers from OutputHandler::handle.
  1562. global $wgDisableOutputCompression;
  1563. $wgDisableOutputCompression = true;
  1564. }
  1565. while ( $status = ob_get_status() ) {
  1566. if ( isset( $status['flags'] ) ) {
  1567. $flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
  1568. $deleteable = ( $status['flags'] & $flags ) === $flags;
  1569. } elseif ( isset( $status['del'] ) ) {
  1570. $deleteable = $status['del'];
  1571. } else {
  1572. // Guess that any PHP-internal setting can't be removed.
  1573. $deleteable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
  1574. }
  1575. if ( !$deleteable ) {
  1576. // Give up, and hope the result doesn't break
  1577. // output behavior.
  1578. break;
  1579. }
  1580. if ( $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) {
  1581. // Unit testing barrier to prevent this function from breaking PHPUnit.
  1582. break;
  1583. }
  1584. if ( !ob_end_clean() ) {
  1585. // Could not remove output buffer handler; abort now
  1586. // to avoid getting in some kind of infinite loop.
  1587. break;
  1588. }
  1589. if ( $resetGzipEncoding && $status['name'] == 'ob_gzhandler' ) {
  1590. // Reset the 'Content-Encoding' field set by this handler
  1591. // so we can start fresh.
  1592. header_remove( 'Content-Encoding' );
  1593. break;
  1594. }
  1595. }
  1596. }
  1597. /**
  1598. * More legible than passing a 'false' parameter to wfResetOutputBuffers():
  1599. *
  1600. * Clear away output buffers, but keep the Content-Encoding header
  1601. * produced by ob_gzhandler, if any.
  1602. *
  1603. * This should be used for HTTP 304 responses, where you need to
  1604. * preserve the Content-Encoding header of the real result, but
  1605. * also need to suppress the output of ob_gzhandler to keep to spec
  1606. * and avoid breaking Firefox in rare cases where the headers and
  1607. * body are broken over two packets.
  1608. */
  1609. function wfClearOutputBuffers() {
  1610. wfResetOutputBuffers( false );
  1611. }
  1612. /**
  1613. * Converts an Accept-* header into an array mapping string values to quality
  1614. * factors
  1615. *
  1616. * @param string $accept
  1617. * @param string $def Default
  1618. * @return float[] Associative array of string => float pairs
  1619. */
  1620. function wfAcceptToPrefs( $accept, $def = '*/*' ) {
  1621. # No arg means accept anything (per HTTP spec)
  1622. if ( !$accept ) {
  1623. return [ $def => 1.0 ];
  1624. }
  1625. $prefs = [];
  1626. $parts = explode( ',', $accept );
  1627. foreach ( $parts as $part ) {
  1628. # @todo FIXME: Doesn't deal with params like 'text/html; level=1'
  1629. $values = explode( ';', trim( $part ) );
  1630. $match = [];
  1631. if ( count( $values ) == 1 ) {
  1632. $prefs[$values[0]] = 1.0;
  1633. } elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
  1634. $prefs[$values[0]] = floatval( $match[1] );
  1635. }
  1636. }
  1637. return $prefs;
  1638. }
  1639. /**
  1640. * Checks if a given MIME type matches any of the keys in the given
  1641. * array. Basic wildcards are accepted in the array keys.
  1642. *
  1643. * Returns the matching MIME type (or wildcard) if a match, otherwise
  1644. * NULL if no match.
  1645. *
  1646. * @param string $type
  1647. * @param array $avail
  1648. * @return string
  1649. * @private
  1650. */
  1651. function mimeTypeMatch( $type, $avail ) {
  1652. if ( array_key_exists( $type, $avail ) ) {
  1653. return $type;
  1654. } else {
  1655. $mainType = explode( '/', $type )[0];
  1656. if ( array_key_exists( "$mainType/*", $avail ) ) {
  1657. return "$mainType/*";
  1658. } elseif ( array_key_exists( '*/*', $avail ) ) {
  1659. return '*/*';
  1660. } else {
  1661. return null;
  1662. }
  1663. }
  1664. }
  1665. /**
  1666. * Returns the 'best' match between a client's requested internet media types
  1667. * and the server's list of available types. Each list should be an associative
  1668. * array of type to preference (preference is a float between 0.0 and 1.0).
  1669. * Wildcards in the types are acceptable.
  1670. *
  1671. * @param array $cprefs Client's acceptable type list
  1672. * @param array $sprefs Server's offered types
  1673. * @return string|null
  1674. *
  1675. * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
  1676. * XXX: generalize to negotiate other stuff
  1677. * @todo The function appears unused. Is it worth to keep?
  1678. */
  1679. function wfNegotiateType( $cprefs, $sprefs ) {
  1680. $combine = [];
  1681. foreach ( array_keys( $sprefs ) as $type ) {
  1682. $subType = explode( '/', $type )[1];
  1683. if ( $subType != '*' ) {
  1684. $ckey = mimeTypeMatch( $type, $cprefs );
  1685. if ( $ckey ) {
  1686. $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
  1687. }
  1688. }
  1689. }
  1690. foreach ( array_keys( $cprefs ) as $type ) {
  1691. $subType = explode( '/', $type )[1];
  1692. if ( $subType != '*' && !array_key_exists( $type, $sprefs ) ) {
  1693. $skey = mimeTypeMatch( $type, $sprefs );
  1694. if ( $skey ) {
  1695. $combine[$type] = $sprefs[$skey] * $cprefs[$type];
  1696. }
  1697. }
  1698. }
  1699. $bestq = 0;
  1700. $besttype = null;
  1701. foreach ( array_keys( $combine ) as $type ) {
  1702. if ( $combine[$type] > $bestq ) {
  1703. $besttype = $type;
  1704. $bestq = $combine[$type];
  1705. }
  1706. }
  1707. return $besttype;
  1708. }
  1709. /**
  1710. * Get a timestamp string in one of various formats
  1711. *
  1712. * @param mixed $outputtype A timestamp in one of the supported formats, the
  1713. * function will autodetect which format is supplied and act accordingly.
  1714. * @param mixed $ts Optional timestamp to convert, default 0 for the current time
  1715. * @return string|bool String / false The same date in the format specified in $outputtype or false
  1716. */
  1717. function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
  1718. $ret = MWTimestamp::convert( $outputtype, $ts );
  1719. if ( $ret === false ) {
  1720. wfDebug( "wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n" );
  1721. }
  1722. return $ret;
  1723. }
  1724. /**
  1725. * Return a formatted timestamp, or null if input is null.
  1726. * For dealing with nullable timestamp columns in the database.
  1727. *
  1728. * @param int $outputtype
  1729. * @param string|null $ts
  1730. * @return string
  1731. */
  1732. function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
  1733. if ( is_null( $ts ) ) {
  1734. return null;
  1735. } else {
  1736. return wfTimestamp( $outputtype, $ts );
  1737. }
  1738. }
  1739. /**
  1740. * Convenience function; returns MediaWiki timestamp for the present time.
  1741. *
  1742. * @return string TS_MW timestamp
  1743. */
  1744. function wfTimestampNow() {
  1745. return MWTimestamp::now( TS_MW );
  1746. }
  1747. /**
  1748. * Check if the operating system is Windows
  1749. *
  1750. * @return bool True if it's Windows, false otherwise.
  1751. */
  1752. function wfIsWindows() {
  1753. static $isWindows = null;
  1754. if ( $isWindows === null ) {
  1755. $isWindows = strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
  1756. }
  1757. return $isWindows;
  1758. }
  1759. /**
  1760. * Check if we are running under HHVM
  1761. *
  1762. * @deprecated since 1.34, HHVM is no longer supported
  1763. * @return bool
  1764. */
  1765. function wfIsHHVM() {
  1766. // wfDeprecated( __FUNCTION__, '1.34' );
  1767. return false;
  1768. }
  1769. /**
  1770. * Check if we are running from the commandline
  1771. *
  1772. * @since 1.31
  1773. * @return bool
  1774. */
  1775. function wfIsCLI() {
  1776. return PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg';
  1777. }
  1778. /**
  1779. * Tries to get the system directory for temporary files. First
  1780. * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
  1781. * environment variables are then checked in sequence, then
  1782. * sys_get_temp_dir(), then upload_tmp_dir from php.ini.
  1783. *
  1784. * NOTE: When possible, use instead the tmpfile() function to create
  1785. * temporary files to avoid race conditions on file creation, etc.
  1786. *
  1787. * @return string
  1788. */
  1789. function wfTempDir() {
  1790. global $wgTmpDirectory;
  1791. if ( $wgTmpDirectory !== false ) {
  1792. return $wgTmpDirectory;
  1793. }
  1794. return TempFSFile::getUsableTempDirectory();
  1795. }
  1796. /**
  1797. * Make directory, and make all parent directories if they don't exist
  1798. *
  1799. * @param string $dir Full path to directory to create
  1800. * @param int|null $mode Chmod value to use, default is $wgDirectoryMode
  1801. * @param string|null $caller Optional caller param for debugging.
  1802. * @throws MWException
  1803. * @return bool
  1804. */
  1805. function wfMkdirParents( $dir, $mode = null, $caller = null ) {
  1806. global $wgDirectoryMode;
  1807. if ( FileBackend::isStoragePath( $dir ) ) { // sanity
  1808. throw new MWException( __FUNCTION__ . " given storage path '$dir'." );
  1809. }
  1810. if ( !is_null( $caller ) ) {
  1811. wfDebug( "$caller: called wfMkdirParents($dir)\n" );
  1812. }
  1813. if ( strval( $dir ) === '' || is_dir( $dir ) ) {
  1814. return true;
  1815. }
  1816. $dir = str_replace( [ '\\', '/' ], DIRECTORY_SEPARATOR, $dir );
  1817. if ( is_null( $mode ) ) {
  1818. $mode = $wgDirectoryMode;
  1819. }
  1820. // Turn off the normal warning, we're doing our own below
  1821. AtEase::suppressWarnings();
  1822. $ok = mkdir( $dir, $mode, true ); // PHP5 <3
  1823. AtEase::restoreWarnings();
  1824. if ( !$ok ) {
  1825. // directory may have been created on another request since we last checked
  1826. if ( is_dir( $dir ) ) {
  1827. return true;
  1828. }
  1829. // PHP doesn't report the path in its warning message, so add our own to aid in diagnosis.
  1830. wfLogWarning( sprintf( "failed to mkdir \"%s\" mode 0%o", $dir, $mode ) );
  1831. }
  1832. return $ok;
  1833. }
  1834. /**
  1835. * Remove a directory and all its content.
  1836. * Does not hide error.
  1837. * @param string $dir
  1838. */
  1839. function wfRecursiveRemoveDir( $dir ) {
  1840. wfDebug( __FUNCTION__ . "( $dir )\n" );
  1841. // taken from https://www.php.net/manual/en/function.rmdir.php#98622
  1842. if ( is_dir( $dir ) ) {
  1843. $objects = scandir( $dir );
  1844. foreach ( $objects as $object ) {
  1845. if ( $object != "." && $object != ".." ) {
  1846. if ( filetype( $dir . '/' . $object ) == "dir" ) {
  1847. wfRecursiveRemoveDir( $dir . '/' . $object );
  1848. } else {
  1849. unlink( $dir . '/' . $object );
  1850. }
  1851. }
  1852. }
  1853. reset( $objects );
  1854. rmdir( $dir );
  1855. }
  1856. }
  1857. /**
  1858. * @param int $nr The number to format
  1859. * @param int $acc The number of digits after the decimal point, default 2
  1860. * @param bool $round Whether or not to round the value, default true
  1861. * @return string
  1862. */
  1863. function wfPercent( $nr, $acc = 2, $round = true ) {
  1864. $ret = sprintf( "%.${acc}f", $nr );
  1865. return $round ? round( (float)$ret, $acc ) . '%' : "$ret%";
  1866. }
  1867. /**
  1868. * Safety wrapper around ini_get() for boolean settings.
  1869. * The values returned from ini_get() are pre-normalized for settings
  1870. * set via php.ini or php_flag/php_admin_flag... but *not*
  1871. * for those set via php_value/php_admin_value.
  1872. *
  1873. * It's fairly common for people to use php_value instead of php_flag,
  1874. * which can leave you with an 'off' setting giving a false positive
  1875. * for code that just takes the ini_get() return value as a boolean.
  1876. *
  1877. * To make things extra interesting, setting via php_value accepts
  1878. * "true" and "yes" as true, but php.ini and php_flag consider them false. :)
  1879. * Unrecognized values go false... again opposite PHP's own coercion
  1880. * from string to bool.
  1881. *
  1882. * Luckily, 'properly' set settings will always come back as '0' or '1',
  1883. * so we only have to worry about them and the 'improper' settings.
  1884. *
  1885. * I frickin' hate PHP... :P
  1886. *
  1887. * @param string $setting
  1888. * @return bool
  1889. */
  1890. function wfIniGetBool( $setting ) {
  1891. return wfStringToBool( ini_get( $setting ) );
  1892. }
  1893. /**
  1894. * Convert string value to boolean, when the following are interpreted as true:
  1895. * - on
  1896. * - true
  1897. * - yes
  1898. * - Any number, except 0
  1899. * All other strings are interpreted as false.
  1900. *
  1901. * @param string $val
  1902. * @return bool
  1903. * @since 1.31
  1904. */
  1905. function wfStringToBool( $val ) {
  1906. $val = strtolower( $val );
  1907. // 'on' and 'true' can't have whitespace around them, but '1' can.
  1908. return $val == 'on'
  1909. || $val == 'true'
  1910. || $val == 'yes'
  1911. || preg_match( "/^\s*[+-]?0*[1-9]/", $val ); // approx C atoi() function
  1912. }
  1913. /**
  1914. * Version of escapeshellarg() that works better on Windows.
  1915. *
  1916. * Originally, this fixed the incorrect use of single quotes on Windows
  1917. * (https://bugs.php.net/bug.php?id=26285) and the locale problems on Linux in
  1918. * PHP 5.2.6+ (bug backported to earlier distro releases of PHP).
  1919. *
  1920. * @param string|string[] ...$args strings to escape and glue together,
  1921. * or a single array of strings parameter
  1922. * @return string
  1923. * @deprecated since 1.30 use MediaWiki\Shell\Shell::escape()
  1924. */
  1925. function wfEscapeShellArg( ...$args ) {
  1926. return Shell::escape( ...$args );
  1927. }
  1928. /**
  1929. * Execute a shell command, with time and memory limits mirrored from the PHP
  1930. * configuration if supported.
  1931. *
  1932. * @param string|string[] $cmd If string, a properly shell-escaped command line,
  1933. * or an array of unescaped arguments, in which case each value will be escaped
  1934. * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
  1935. * @param null|mixed &$retval Optional, will receive the program's exit code.
  1936. * (non-zero is usually failure). If there is an error from
  1937. * read, select, or proc_open(), this will be set to -1.
  1938. * @param array $environ Optional environment variables which should be
  1939. * added to the executed command environment.
  1940. * @param array $limits Optional array with limits(filesize, memory, time, walltime)
  1941. * this overwrites the global wgMaxShell* limits.
  1942. * @param array $options Array of options:
  1943. * - duplicateStderr: Set this to true to duplicate stderr to stdout,
  1944. * including errors from limit.sh
  1945. * - profileMethod: By default this function will profile based on the calling
  1946. * method. Set this to a string for an alternative method to profile from
  1947. * @phan-param array{duplicateStderr?:bool,profileMethod?:string} $options
  1948. *
  1949. * @return string Collected stdout as a string
  1950. * @deprecated since 1.30 use class MediaWiki\Shell\Shell
  1951. */
  1952. function wfShellExec( $cmd, &$retval = null, $environ = [],
  1953. $limits = [], $options = []
  1954. ) {
  1955. if ( Shell::isDisabled() ) {
  1956. $retval = 1;
  1957. // Backwards compatibility be upon us...
  1958. return 'Unable to run external programs, proc_open() is disabled.';
  1959. }
  1960. if ( is_array( $cmd ) ) {
  1961. $cmd = Shell::escape( $cmd );
  1962. }
  1963. $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
  1964. $profileMethod = $options['profileMethod'] ?? wfGetCaller();
  1965. try {
  1966. $result = Shell::command( [] )
  1967. ->unsafeParams( (array)$cmd )
  1968. ->environment( $environ )
  1969. ->limits( $limits )
  1970. ->includeStderr( $includeStderr )
  1971. ->profileMethod( $profileMethod )
  1972. // For b/c
  1973. ->restrict( Shell::RESTRICT_NONE )
  1974. ->execute();
  1975. } catch ( ProcOpenError $ex ) {
  1976. $retval = -1;
  1977. return '';
  1978. }
  1979. $retval = $result->getExitCode();
  1980. return $result->getStdout();
  1981. }
  1982. /**
  1983. * Execute a shell command, returning both stdout and stderr. Convenience
  1984. * function, as all the arguments to wfShellExec can become unwieldy.
  1985. *
  1986. * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
  1987. * @param string|string[] $cmd If string, a properly shell-escaped command line,
  1988. * or an array of unescaped arguments, in which case each value will be escaped
  1989. * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'"
  1990. * @param null|mixed &$retval Optional, will receive the program's exit code.
  1991. * (non-zero is usually failure)
  1992. * @param array $environ Optional environment variables which should be
  1993. * added to the executed command environment.
  1994. * @param array $limits Optional array with limits(filesize, memory, time, walltime)
  1995. * this overwrites the global wgMaxShell* limits.
  1996. * @return string Collected stdout and stderr as a string
  1997. * @deprecated since 1.30 use class MediaWiki\Shell\Shell
  1998. */
  1999. function wfShellExecWithStderr( $cmd, &$retval = null, $environ = [], $limits = [] ) {
  2000. return wfShellExec( $cmd, $retval, $environ, $limits,
  2001. [ 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ] );
  2002. }
  2003. /**
  2004. * Generate a shell-escaped command line string to run a MediaWiki cli script.
  2005. * Note that $parameters should be a flat array and an option with an argument
  2006. * should consist of two consecutive items in the array (do not use "--option value").
  2007. *
  2008. * @deprecated since 1.31, use Shell::makeScriptCommand()
  2009. *
  2010. * @param string $script MediaWiki cli script path
  2011. * @param array $parameters Arguments and options to the script
  2012. * @param array $options Associative array of options:
  2013. * 'php': The path to the php executable
  2014. * 'wrapper': Path to a PHP wrapper to handle the maintenance script
  2015. * @phan-param array{php?:string,wrapper?:string} $options
  2016. * @return string
  2017. */
  2018. function wfShellWikiCmd( $script, array $parameters = [], array $options = [] ) {
  2019. global $wgPhpCli;
  2020. // Give site config file a chance to run the script in a wrapper.
  2021. // The caller may likely want to call wfBasename() on $script.
  2022. Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
  2023. $cmd = [ $options['php'] ?? $wgPhpCli ];
  2024. if ( isset( $options['wrapper'] ) ) {
  2025. $cmd[] = $options['wrapper'];
  2026. }
  2027. $cmd[] = $script;
  2028. // Escape each parameter for shell
  2029. return Shell::escape( array_merge( $cmd, $parameters ) );
  2030. }
  2031. /**
  2032. * wfMerge attempts to merge differences between three texts.
  2033. * Returns true for a clean merge and false for failure or a conflict.
  2034. *
  2035. * @param string $old
  2036. * @param string $mine
  2037. * @param string $yours
  2038. * @param string &$result
  2039. * @param string|null &$mergeAttemptResult
  2040. * @return bool
  2041. */
  2042. function wfMerge( $old, $mine, $yours, &$result, &$mergeAttemptResult = null ) {
  2043. global $wgDiff3;
  2044. # This check may also protect against code injection in
  2045. # case of broken installations.
  2046. AtEase::suppressWarnings();
  2047. $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
  2048. AtEase::restoreWarnings();
  2049. if ( !$haveDiff3 ) {
  2050. wfDebug( "diff3 not found\n" );
  2051. return false;
  2052. }
  2053. # Make temporary files
  2054. $td = wfTempDir();
  2055. $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
  2056. $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
  2057. $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
  2058. # NOTE: diff3 issues a warning to stderr if any of the files does not end with
  2059. # a newline character. To avoid this, we normalize the trailing whitespace before
  2060. # creating the diff.
  2061. fwrite( $oldtextFile, rtrim( $old ) . "\n" );
  2062. fclose( $oldtextFile );
  2063. fwrite( $mytextFile, rtrim( $mine ) . "\n" );
  2064. fclose( $mytextFile );
  2065. fwrite( $yourtextFile, rtrim( $yours ) . "\n" );
  2066. fclose( $yourtextFile );
  2067. # Check for a conflict
  2068. $cmd = Shell::escape( $wgDiff3, '-a', '--overlap-only', $mytextName,
  2069. $oldtextName, $yourtextName );
  2070. $handle = popen( $cmd, 'r' );
  2071. $mergeAttemptResult = '';
  2072. do {
  2073. $data = fread( $handle, 8192 );
  2074. if ( strlen( $data ) == 0 ) {
  2075. break;
  2076. }
  2077. $mergeAttemptResult .= $data;
  2078. } while ( true );
  2079. pclose( $handle );
  2080. $conflict = $mergeAttemptResult !== '';
  2081. # Merge differences
  2082. $cmd = Shell::escape( $wgDiff3, '-a', '-e', '--merge', $mytextName,
  2083. $oldtextName, $yourtextName );
  2084. $handle = popen( $cmd, 'r' );
  2085. $result = '';
  2086. do {
  2087. $data = fread( $handle, 8192 );
  2088. if ( strlen( $data ) == 0 ) {
  2089. break;
  2090. }
  2091. $result .= $data;
  2092. } while ( true );
  2093. pclose( $handle );
  2094. unlink( $mytextName );
  2095. unlink( $oldtextName );
  2096. unlink( $yourtextName );
  2097. if ( $result === '' && $old !== '' && !$conflict ) {
  2098. wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
  2099. $conflict = true;
  2100. }
  2101. return !$conflict;
  2102. }
  2103. /**
  2104. * Returns unified plain-text diff of two texts.
  2105. * "Useful" for machine processing of diffs.
  2106. *
  2107. * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly
  2108. *
  2109. * @param string $before The text before the changes.
  2110. * @param string $after The text after the changes.
  2111. * @param string $params Command-line options for the diff command.
  2112. * @return string Unified diff of $before and $after
  2113. */
  2114. function wfDiff( $before, $after, $params = '-u' ) {
  2115. if ( $before == $after ) {
  2116. return '';
  2117. }
  2118. global $wgDiff;
  2119. AtEase::suppressWarnings();
  2120. $haveDiff = $wgDiff && file_exists( $wgDiff );
  2121. AtEase::restoreWarnings();
  2122. # This check may also protect against code injection in
  2123. # case of broken installations.
  2124. if ( !$haveDiff ) {
  2125. wfDebug( "diff executable not found\n" );
  2126. $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
  2127. $format = new UnifiedDiffFormatter();
  2128. return $format->format( $diffs );
  2129. }
  2130. # Make temporary files
  2131. $td = wfTempDir();
  2132. $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
  2133. $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
  2134. fwrite( $oldtextFile, $before );
  2135. fclose( $oldtextFile );
  2136. fwrite( $newtextFile, $after );
  2137. fclose( $newtextFile );
  2138. // Get the diff of the two files
  2139. $cmd = "$wgDiff " . $params . ' ' . Shell::escape( $oldtextName, $newtextName );
  2140. $h = popen( $cmd, 'r' );
  2141. if ( !$h ) {
  2142. unlink( $oldtextName );
  2143. unlink( $newtextName );
  2144. throw new Exception( __METHOD__ . '(): popen() failed' );
  2145. }
  2146. $diff = '';
  2147. do {
  2148. $data = fread( $h, 8192 );
  2149. if ( strlen( $data ) == 0 ) {
  2150. break;
  2151. }
  2152. $diff .= $data;
  2153. } while ( true );
  2154. // Clean up
  2155. pclose( $h );
  2156. unlink( $oldtextName );
  2157. unlink( $newtextName );
  2158. // Kill the --- and +++ lines. They're not useful.
  2159. $diff_lines = explode( "\n", $diff );
  2160. if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) {
  2161. unset( $diff_lines[0] );
  2162. }
  2163. if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) {
  2164. unset( $diff_lines[1] );
  2165. }
  2166. $diff = implode( "\n", $diff_lines );
  2167. return $diff;
  2168. }
  2169. /**
  2170. * Return the final portion of a pathname.
  2171. * Reimplemented because PHP5's "basename()" is buggy with multibyte text.
  2172. * https://bugs.php.net/bug.php?id=33898
  2173. *
  2174. * PHP's basename() only considers '\' a pathchar on Windows and Netware.
  2175. * We'll consider it so always, as we don't want '\s' in our Unix paths either.
  2176. *
  2177. * @param string $path
  2178. * @param string $suffix String to remove if present
  2179. * @return string
  2180. */
  2181. function wfBaseName( $path, $suffix = '' ) {
  2182. if ( $suffix == '' ) {
  2183. $encSuffix = '';
  2184. } else {
  2185. $encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
  2186. }
  2187. $matches = [];
  2188. if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
  2189. return $matches[1];
  2190. } else {
  2191. return '';
  2192. }
  2193. }
  2194. /**
  2195. * Generate a relative path name to the given file.
  2196. * May explode on non-matching case-insensitive paths,
  2197. * funky symlinks, etc.
  2198. *
  2199. * @param string $path Absolute destination path including target filename
  2200. * @param string $from Absolute source path, directory only
  2201. * @return string
  2202. */
  2203. function wfRelativePath( $path, $from ) {
  2204. // Normalize mixed input on Windows...
  2205. $path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
  2206. $from = str_replace( '/', DIRECTORY_SEPARATOR, $from );
  2207. // Trim trailing slashes -- fix for drive root
  2208. $path = rtrim( $path, DIRECTORY_SEPARATOR );
  2209. $from = rtrim( $from, DIRECTORY_SEPARATOR );
  2210. $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) );
  2211. $against = explode( DIRECTORY_SEPARATOR, $from );
  2212. if ( $pieces[0] !== $against[0] ) {
  2213. // Non-matching Windows drive letters?
  2214. // Return a full path.
  2215. return $path;
  2216. }
  2217. // Trim off common prefix
  2218. while ( count( $pieces ) && count( $against )
  2219. && $pieces[0] == $against[0] ) {
  2220. array_shift( $pieces );
  2221. array_shift( $against );
  2222. }
  2223. // relative dots to bump us to the parent
  2224. while ( count( $against ) ) {
  2225. array_unshift( $pieces, '..' );
  2226. array_shift( $against );
  2227. }
  2228. array_push( $pieces, wfBaseName( $path ) );
  2229. return implode( DIRECTORY_SEPARATOR, $pieces );
  2230. }
  2231. /**
  2232. * Initialise php session
  2233. *
  2234. * @deprecated since 1.27, use MediaWiki\Session\SessionManager instead.
  2235. * Generally, "using" SessionManager will be calling ->getSessionById() or
  2236. * ::getGlobalSession() (depending on whether you were passing $sessionId
  2237. * here), then calling $session->persist().
  2238. * @param bool|string $sessionId
  2239. */
  2240. function wfSetupSession( $sessionId = false ) {
  2241. wfDeprecated( __FUNCTION__, '1.27' );
  2242. if ( $sessionId ) {
  2243. session_id( $sessionId );
  2244. }
  2245. $session = SessionManager::getGlobalSession();
  2246. $session->persist();
  2247. if ( session_id() !== $session->getId() ) {
  2248. session_id( $session->getId() );
  2249. }
  2250. AtEase::quietCall( 'session_start' );
  2251. }
  2252. /**
  2253. * Get an object from the precompiled serialized directory
  2254. *
  2255. * @param string $name
  2256. * @return mixed The variable on success, false on failure
  2257. */
  2258. function wfGetPrecompiledData( $name ) {
  2259. global $IP;
  2260. $file = "$IP/serialized/$name";
  2261. if ( file_exists( $file ) ) {
  2262. $blob = file_get_contents( $file );
  2263. if ( $blob ) {
  2264. return unserialize( $blob );
  2265. }
  2266. }
  2267. return false;
  2268. }
  2269. /**
  2270. * Make a cache key for the local wiki.
  2271. *
  2272. * @deprecated since 1.30 Call makeKey on a BagOStuff instance
  2273. * @param string ...$args
  2274. * @return string
  2275. */
  2276. function wfMemcKey( ...$args ) {
  2277. return ObjectCache::getLocalClusterInstance()->makeKey( ...$args );
  2278. }
  2279. /**
  2280. * Make a cache key for a foreign DB.
  2281. *
  2282. * Must match what wfMemcKey() would produce in context of the foreign wiki.
  2283. *
  2284. * @param string $db
  2285. * @param string $prefix
  2286. * @param string ...$args
  2287. * @return string
  2288. */
  2289. function wfForeignMemcKey( $db, $prefix, ...$args ) {
  2290. $keyspace = $prefix ? "$db-$prefix" : $db;
  2291. return ObjectCache::getLocalClusterInstance()->makeKeyInternal( $keyspace, $args );
  2292. }
  2293. /**
  2294. * Make a cache key with database-agnostic prefix.
  2295. *
  2296. * Doesn't have a wiki-specific namespace. Uses a generic 'global' prefix
  2297. * instead. Must have a prefix as otherwise keys that use a database name
  2298. * in the first segment will clash with wfMemcKey/wfForeignMemcKey.
  2299. *
  2300. * @deprecated since 1.30 Call makeGlobalKey on a BagOStuff instance
  2301. * @since 1.26
  2302. * @param string ...$args
  2303. * @return string
  2304. */
  2305. function wfGlobalCacheKey( ...$args ) {
  2306. wfDeprecated( __METHOD__, '1.30' );
  2307. return ObjectCache::getLocalClusterInstance()->makeGlobalKey( ...$args );
  2308. }
  2309. /**
  2310. * Get an ASCII string identifying this wiki
  2311. * This is used as a prefix in memcached keys
  2312. *
  2313. * @return string
  2314. */
  2315. function wfWikiID() {
  2316. global $wgDBprefix, $wgDBname;
  2317. if ( $wgDBprefix ) {
  2318. return "$wgDBname-$wgDBprefix";
  2319. } else {
  2320. return $wgDBname;
  2321. }
  2322. }
  2323. /**
  2324. * Get a Database object.
  2325. *
  2326. * @param int $db Index of the connection to get. May be DB_MASTER for the
  2327. * master (for write queries), DB_REPLICA for potentially lagged read
  2328. * queries, or an integer >= 0 for a particular server.
  2329. *
  2330. * @param string|string[] $groups Query groups. An array of group names that this query
  2331. * belongs to. May contain a single string if the query is only
  2332. * in one group.
  2333. *
  2334. * @param string|bool $wiki The wiki ID, or false for the current wiki
  2335. *
  2336. * Note: multiple calls to wfGetDB(DB_REPLICA) during the course of one request
  2337. * will always return the same object, unless the underlying connection or load
  2338. * balancer is manually destroyed.
  2339. *
  2340. * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
  2341. * updater to ensure that a proper database is being updated.
  2342. *
  2343. * @todo Replace calls to wfGetDB with calls to LoadBalancer::getConnection()
  2344. * on an injected instance of LoadBalancer.
  2345. *
  2346. * @return \Wikimedia\Rdbms\DBConnRef
  2347. */
  2348. function wfGetDB( $db, $groups = [], $wiki = false ) {
  2349. return wfGetLB( $wiki )->getMaintenanceConnectionRef( $db, $groups, $wiki );
  2350. }
  2351. /**
  2352. * Get a load balancer object.
  2353. *
  2354. * @deprecated since 1.27, use MediaWikiServices::getInstance()->getDBLoadBalancer()
  2355. * or MediaWikiServices::getInstance()->getDBLoadBalancerFactory() instead.
  2356. *
  2357. * @param string|bool $wiki Wiki ID, or false for the current wiki
  2358. * @return \Wikimedia\Rdbms\LoadBalancer
  2359. */
  2360. function wfGetLB( $wiki = false ) {
  2361. if ( $wiki === false ) {
  2362. return MediaWikiServices::getInstance()->getDBLoadBalancer();
  2363. } else {
  2364. $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
  2365. return $factory->getMainLB( $wiki );
  2366. }
  2367. }
  2368. /**
  2369. * Find a file.
  2370. * @deprecated since 1.34, use MediaWikiServices
  2371. * @param string|LinkTarget $title String or LinkTarget object
  2372. * @param array $options Associative array of options (see RepoGroup::findFile)
  2373. * @return File|bool File, or false if the file does not exist
  2374. */
  2375. function wfFindFile( $title, $options = [] ) {
  2376. return MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title, $options );
  2377. }
  2378. /**
  2379. * Get an object referring to a locally registered file.
  2380. * Returns a valid placeholder object if the file does not exist.
  2381. *
  2382. * @deprecated since 1.34, use MediaWikiServices
  2383. * @param Title|string $title
  2384. * @return LocalFile|null A File, or null if passed an invalid Title
  2385. */
  2386. function wfLocalFile( $title ) {
  2387. return MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()->newFile( $title );
  2388. }
  2389. /**
  2390. * Should low-performance queries be disabled?
  2391. *
  2392. * @return bool
  2393. * @codeCoverageIgnore
  2394. */
  2395. function wfQueriesMustScale() {
  2396. global $wgMiserMode;
  2397. return $wgMiserMode
  2398. || ( SiteStats::pages() > 100000
  2399. && SiteStats::edits() > 1000000
  2400. && SiteStats::users() > 10000 );
  2401. }
  2402. /**
  2403. * Get the path to a specified script file, respecting file
  2404. * extensions; this is a wrapper around $wgScriptPath etc.
  2405. * except for 'index' and 'load' which use $wgScript/$wgLoadScript
  2406. *
  2407. * @param string $script Script filename, sans extension
  2408. * @return string
  2409. */
  2410. function wfScript( $script = 'index' ) {
  2411. global $wgScriptPath, $wgScript, $wgLoadScript;
  2412. if ( $script === 'index' ) {
  2413. return $wgScript;
  2414. } elseif ( $script === 'load' ) {
  2415. return $wgLoadScript;
  2416. } else {
  2417. return "{$wgScriptPath}/{$script}.php";
  2418. }
  2419. }
  2420. /**
  2421. * Get the script URL.
  2422. *
  2423. * @return string Script URL
  2424. */
  2425. function wfGetScriptUrl() {
  2426. if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
  2427. /* as it was called, minus the query string.
  2428. *
  2429. * Some sites use Apache rewrite rules to handle subdomains,
  2430. * and have PHP set up in a weird way that causes PHP_SELF
  2431. * to contain the rewritten URL instead of the one that the
  2432. * outside world sees.
  2433. *
  2434. * If in this mode, use SCRIPT_URL instead, which mod_rewrite
  2435. * provides containing the "before" URL.
  2436. */
  2437. return $_SERVER['SCRIPT_NAME'];
  2438. } else {
  2439. return $_SERVER['URL'];
  2440. }
  2441. }
  2442. /**
  2443. * Convenience function converts boolean values into "true"
  2444. * or "false" (string) values
  2445. *
  2446. * @param bool $value
  2447. * @return string
  2448. */
  2449. function wfBoolToStr( $value ) {
  2450. return $value ? 'true' : 'false';
  2451. }
  2452. /**
  2453. * Get a platform-independent path to the null file, e.g. /dev/null
  2454. *
  2455. * @return string
  2456. */
  2457. function wfGetNull() {
  2458. return wfIsWindows() ? 'NUL' : '/dev/null';
  2459. }
  2460. /**
  2461. * Waits for the replica DBs to catch up to the master position
  2462. *
  2463. * Use this when updating very large numbers of rows, as in maintenance scripts,
  2464. * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
  2465. *
  2466. * By default this waits on the main DB cluster of the current wiki.
  2467. * If $cluster is set to "*" it will wait on all DB clusters, including
  2468. * external ones. If the lag being waiting on is caused by the code that
  2469. * does this check, it makes since to use $ifWritesSince, particularly if
  2470. * cluster is "*", to avoid excess overhead.
  2471. *
  2472. * Never call this function after a big DB write that is still in a transaction.
  2473. * This only makes sense after the possible lag inducing changes were committed.
  2474. *
  2475. * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp
  2476. * @param string|bool $wiki Wiki identifier accepted by wfGetLB
  2477. * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
  2478. * @param int|null $timeout Max wait time. Default: 60 seconds (cli), 1 second (web)
  2479. * @return bool Success (able to connect and no timeouts reached)
  2480. * @deprecated since 1.27 Use LBFactory::waitForReplication
  2481. */
  2482. function wfWaitForSlaves(
  2483. $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
  2484. ) {
  2485. $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
  2486. if ( $cluster === '*' ) {
  2487. $cluster = false;
  2488. $domain = false;
  2489. } elseif ( $wiki === false ) {
  2490. $domain = $lbFactory->getLocalDomainID();
  2491. } else {
  2492. $domain = $wiki;
  2493. }
  2494. $opts = [
  2495. 'domain' => $domain,
  2496. 'cluster' => $cluster,
  2497. // B/C: first argument used to be "max seconds of lag"; ignore such values
  2498. 'ifWritesSince' => ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null
  2499. ];
  2500. if ( $timeout !== null ) {
  2501. $opts['timeout'] = $timeout;
  2502. }
  2503. return $lbFactory->waitForReplication( $opts );
  2504. }
  2505. /**
  2506. * Replace all invalid characters with '-'.
  2507. * Additional characters can be defined in $wgIllegalFileChars (see T22489).
  2508. * By default, $wgIllegalFileChars includes ':', '/', '\'.
  2509. *
  2510. * @param string $name Filename to process
  2511. * @return string
  2512. */
  2513. function wfStripIllegalFilenameChars( $name ) {
  2514. global $wgIllegalFileChars;
  2515. $illegalFileChars = $wgIllegalFileChars ? "|[" . $wgIllegalFileChars . "]" : '';
  2516. $name = preg_replace(
  2517. "/[^" . Title::legalChars() . "]" . $illegalFileChars . "/",
  2518. '-',
  2519. $name
  2520. );
  2521. // $wgIllegalFileChars may not include '/' and '\', so we still need to do this
  2522. $name = wfBaseName( $name );
  2523. return $name;
  2524. }
  2525. /**
  2526. * Raise PHP's memory limit (if needed).
  2527. *
  2528. * @internal For use by Setup.php
  2529. */
  2530. function wfMemoryLimit( $newLimit ) {
  2531. $oldLimit = wfShorthandToInteger( ini_get( 'memory_limit' ) );
  2532. // If the INI config is already unlimited, there is nothing larger
  2533. if ( $oldLimit != -1 ) {
  2534. $newLimit = wfShorthandToInteger( $newLimit );
  2535. if ( $newLimit == -1 ) {
  2536. wfDebug( "Removing PHP's memory limit\n" );
  2537. Wikimedia\suppressWarnings();
  2538. ini_set( 'memory_limit', $newLimit );
  2539. Wikimedia\restoreWarnings();
  2540. } elseif ( $newLimit > $oldLimit ) {
  2541. wfDebug( "Raising PHP's memory limit to $newLimit bytes\n" );
  2542. Wikimedia\suppressWarnings();
  2543. ini_set( 'memory_limit', $newLimit );
  2544. Wikimedia\restoreWarnings();
  2545. }
  2546. }
  2547. }
  2548. /**
  2549. * Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit
  2550. *
  2551. * @return int Prior time limit
  2552. * @since 1.26
  2553. */
  2554. function wfTransactionalTimeLimit() {
  2555. global $wgTransactionalTimeLimit;
  2556. $timeLimit = (int)ini_get( 'max_execution_time' );
  2557. // Note that CLI scripts use 0
  2558. if ( $timeLimit > 0 && $wgTransactionalTimeLimit > $timeLimit ) {
  2559. set_time_limit( $wgTransactionalTimeLimit );
  2560. }
  2561. ignore_user_abort( true ); // ignore client disconnects
  2562. return $timeLimit;
  2563. }
  2564. /**
  2565. * Converts shorthand byte notation to integer form
  2566. *
  2567. * @param string $string
  2568. * @param int $default Returned if $string is empty
  2569. * @return int
  2570. */
  2571. function wfShorthandToInteger( $string = '', $default = -1 ) {
  2572. $string = trim( $string );
  2573. if ( $string === '' ) {
  2574. return $default;
  2575. }
  2576. $last = $string[strlen( $string ) - 1];
  2577. $val = intval( $string );
  2578. switch ( $last ) {
  2579. case 'g':
  2580. case 'G':
  2581. $val *= 1024;
  2582. // break intentionally missing
  2583. case 'm':
  2584. case 'M':
  2585. $val *= 1024;
  2586. // break intentionally missing
  2587. case 'k':
  2588. case 'K':
  2589. $val *= 1024;
  2590. }
  2591. return $val;
  2592. }
  2593. /**
  2594. * Get a specific cache object.
  2595. *
  2596. * @deprecated since 1.32, use ObjectCache::getInstance() instead
  2597. * @param int|string $cacheType A CACHE_* constants, or other key in $wgObjectCaches
  2598. * @return BagOStuff
  2599. */
  2600. function wfGetCache( $cacheType ) {
  2601. return ObjectCache::getInstance( $cacheType );
  2602. }
  2603. /**
  2604. * Get the main cache object
  2605. *
  2606. * @deprecated since 1.32, use ObjectCache::getLocalClusterInstance() instead
  2607. * @return BagOStuff
  2608. */
  2609. function wfGetMainCache() {
  2610. return ObjectCache::getLocalClusterInstance();
  2611. }
  2612. /**
  2613. * Get the cache object used by the message cache
  2614. *
  2615. * @return BagOStuff
  2616. */
  2617. function wfGetMessageCacheStorage() {
  2618. global $wgMessageCacheType;
  2619. return ObjectCache::getInstance( $wgMessageCacheType );
  2620. }
  2621. /**
  2622. * Wrapper around php's unpack.
  2623. *
  2624. * @param string $format The format string (See php's docs)
  2625. * @param string $data A binary string of binary data
  2626. * @param int|bool $length The minimum length of $data or false. This is to
  2627. * prevent reading beyond the end of $data. false to disable the check.
  2628. *
  2629. * Also be careful when using this function to read unsigned 32 bit integer
  2630. * because php might make it negative.
  2631. *
  2632. * @throws MWException If $data not long enough, or if unpack fails
  2633. * @return array Associative array of the extracted data
  2634. */
  2635. function wfUnpack( $format, $data, $length = false ) {
  2636. if ( $length !== false ) {
  2637. $realLen = strlen( $data );
  2638. if ( $realLen < $length ) {
  2639. throw new MWException( "Tried to use wfUnpack on a "
  2640. . "string of length $realLen, but needed one "
  2641. . "of at least length $length."
  2642. );
  2643. }
  2644. }
  2645. Wikimedia\suppressWarnings();
  2646. $result = unpack( $format, $data );
  2647. Wikimedia\restoreWarnings();
  2648. if ( $result === false ) {
  2649. // If it cannot extract the packed data.
  2650. throw new MWException( "unpack could not unpack binary data" );
  2651. }
  2652. return $result;
  2653. }
  2654. /**
  2655. * Determine if an image exists on the 'bad image list'.
  2656. *
  2657. * The format of MediaWiki:Bad_image_list is as follows:
  2658. * * Only list items (lines starting with "*") are considered
  2659. * * The first link on a line must be a link to a bad image
  2660. * * Any subsequent links on the same line are considered to be exceptions,
  2661. * i.e. articles where the image may occur inline.
  2662. *
  2663. * @deprecated since 1.34, use the BadFileLookup service directly instead
  2664. *
  2665. * @param string $name The image name to check
  2666. * @param Title|bool $contextTitle The page on which the image occurs, if known
  2667. * @param string|null $blacklist Wikitext of a file blacklist
  2668. * @return bool
  2669. */
  2670. function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
  2671. $services = MediaWikiServices::getInstance();
  2672. if ( $blacklist !== null ) {
  2673. wfDeprecated( __METHOD__ . ' with $blacklist parameter', '1.34' );
  2674. return ( new BadFileLookup(
  2675. function () use ( $blacklist ) {
  2676. return $blacklist;
  2677. },
  2678. $services->getLocalServerObjectCache(),
  2679. $services->getRepoGroup(),
  2680. $services->getTitleParser()
  2681. ) )->isBadFile( $name, $contextTitle ?: null );
  2682. }
  2683. return $services->getBadFileLookup()->isBadFile( $name, $contextTitle ?: null );
  2684. }
  2685. /**
  2686. * Determine whether the client at a given source IP is likely to be able to
  2687. * access the wiki via HTTPS.
  2688. *
  2689. * @param string $ip The IPv4/6 address in the normal human-readable form
  2690. * @return bool
  2691. */
  2692. function wfCanIPUseHTTPS( $ip ) {
  2693. $canDo = true;
  2694. Hooks::run( 'CanIPUseHTTPS', [ $ip, &$canDo ] );
  2695. return (bool)$canDo;
  2696. }
  2697. /**
  2698. * Determine input string is represents as infinity
  2699. *
  2700. * @param string $str The string to determine
  2701. * @return bool
  2702. * @since 1.25
  2703. */
  2704. function wfIsInfinity( $str ) {
  2705. // These are hardcoded elsewhere in MediaWiki (e.g. mediawiki.special.block.js).
  2706. $infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ];
  2707. return in_array( $str, $infinityValues );
  2708. }
  2709. /**
  2710. * Returns true if these thumbnail parameters match one that MediaWiki
  2711. * requests from file description pages and/or parser output.
  2712. *
  2713. * $params is considered non-standard if they involve a non-standard
  2714. * width or any non-default parameters aside from width and page number.
  2715. * The number of possible files with standard parameters is far less than
  2716. * that of all combinations; rate-limiting for them can thus be more generious.
  2717. *
  2718. * @param File $file
  2719. * @param array $params
  2720. * @return bool
  2721. * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25
  2722. */
  2723. function wfThumbIsStandard( File $file, array $params ) {
  2724. global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages;
  2725. $multipliers = [ 1 ];
  2726. if ( $wgResponsiveImages ) {
  2727. // These available sizes are hardcoded currently elsewhere in MediaWiki.
  2728. // @see Linker::processResponsiveImages
  2729. $multipliers[] = 1.5;
  2730. $multipliers[] = 2;
  2731. }
  2732. $handler = $file->getHandler();
  2733. if ( !$handler || !isset( $params['width'] ) ) {
  2734. return false;
  2735. }
  2736. $basicParams = [];
  2737. if ( isset( $params['page'] ) ) {
  2738. $basicParams['page'] = $params['page'];
  2739. }
  2740. $thumbLimits = [];
  2741. $imageLimits = [];
  2742. // Expand limits to account for multipliers
  2743. foreach ( $multipliers as $multiplier ) {
  2744. $thumbLimits = array_merge( $thumbLimits, array_map(
  2745. function ( $width ) use ( $multiplier ) {
  2746. return round( $width * $multiplier );
  2747. }, $wgThumbLimits )
  2748. );
  2749. $imageLimits = array_merge( $imageLimits, array_map(
  2750. function ( $pair ) use ( $multiplier ) {
  2751. return [
  2752. round( $pair[0] * $multiplier ),
  2753. round( $pair[1] * $multiplier ),
  2754. ];
  2755. }, $wgImageLimits )
  2756. );
  2757. }
  2758. // Check if the width matches one of $wgThumbLimits
  2759. if ( in_array( $params['width'], $thumbLimits ) ) {
  2760. $normalParams = $basicParams + [ 'width' => $params['width'] ];
  2761. // Append any default values to the map (e.g. "lossy", "lossless", ...)
  2762. $handler->normaliseParams( $file, $normalParams );
  2763. } else {
  2764. // If not, then check if the width matchs one of $wgImageLimits
  2765. $match = false;
  2766. foreach ( $imageLimits as $pair ) {
  2767. $normalParams = $basicParams + [ 'width' => $pair[0], 'height' => $pair[1] ];
  2768. // Decide whether the thumbnail should be scaled on width or height.
  2769. // Also append any default values to the map (e.g. "lossy", "lossless", ...)
  2770. $handler->normaliseParams( $file, $normalParams );
  2771. // Check if this standard thumbnail size maps to the given width
  2772. if ( $normalParams['width'] == $params['width'] ) {
  2773. $match = true;
  2774. break;
  2775. }
  2776. }
  2777. if ( !$match ) {
  2778. return false; // not standard for description pages
  2779. }
  2780. }
  2781. // Check that the given values for non-page, non-width, params are just defaults
  2782. foreach ( $params as $key => $value ) {
  2783. if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
  2784. return false;
  2785. }
  2786. }
  2787. return true;
  2788. }
  2789. /**
  2790. * Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
  2791. *
  2792. * Values that exist in both values will be combined with += (all values of the array
  2793. * of $newValues will be added to the values of the array of $baseArray, while values,
  2794. * that exists in both, the value of $baseArray will be used).
  2795. *
  2796. * @param array $baseArray The array where you want to add the values of $newValues to
  2797. * @param array $newValues An array with new values
  2798. * @return array The combined array
  2799. * @since 1.26
  2800. */
  2801. function wfArrayPlus2d( array $baseArray, array $newValues ) {
  2802. // First merge items that are in both arrays
  2803. foreach ( $baseArray as $name => &$groupVal ) {
  2804. if ( isset( $newValues[$name] ) ) {
  2805. $groupVal += $newValues[$name];
  2806. }
  2807. }
  2808. // Now add items that didn't exist yet
  2809. $baseArray += $newValues;
  2810. return $baseArray;
  2811. }
  2812. /**
  2813. * Get system resource usage of current request context.
  2814. * Invokes the getrusage(2) system call, requesting RUSAGE_SELF if on PHP5
  2815. * or RUSAGE_THREAD if on HHVM. Returns false if getrusage is not available.
  2816. *
  2817. * @since 1.24
  2818. * @return array|bool Resource usage data or false if no data available.
  2819. */
  2820. function wfGetRusage() {
  2821. if ( !function_exists( 'getrusage' ) ) {
  2822. return false;
  2823. } elseif ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) {
  2824. return getrusage( 2 /* RUSAGE_THREAD */ );
  2825. } else {
  2826. return getrusage( 0 /* RUSAGE_SELF */ );
  2827. }
  2828. }