function __construct($key, $archive = 0) { $this->app = F::app(); switch ($key) { case 'edit': $this->mKey = self::EDIT_CATEGORY; $this->mEventType = self::EDIT_CATEGORY_INT; break; case 'create': $this->mKey = self::CREATEPAGE_CATEGORY; $this->mEventType = self::CREATEPAGE_CATEGORY_INT; break; case 'delete': $this->mKey = self::DELETE_CATEGORY; $this->mEventType = self::DELETE_CATEGORY_INT; break; case 'undelete': $this->mKey = self::UNDELETE_CATEGORY; $this->mEventType = self::UNDELETE_CATEGORY_INT; break; } $this->setCityId($this->app->wg->CityId); $this->setServerName($this->app->wg->Server); $this->setIp($this->app->wg->Request->getIP()); $this->setHostname(wfHostname()); $this->setBeaconId(wfGetBeaconId()); $this->setArchive($archive); $this->setLanguage(); $this->setCategory(); }
public function log(array $stats) { $pfhost = $this->perHost ? wfHostname() : ''; try { $dbw = wfGetDB(DB_MASTER); $useTrx = $dbw->getType() === 'sqlite'; // much faster if ($useTrx) { $dbw->startAtomic(__METHOD__); } foreach ($stats as $data) { $name = $data['name']; $eventCount = $data['calls']; $timeSum = (double) $data['real']; $memorySum = (double) $data['memory']; $name = substr($name, 0, 255); // Kludge $timeSum = $timeSum >= 0 ? $timeSum : 0; $memorySum = $memorySum >= 0 ? $memorySum : 0; $dbw->upsert('profiling', array('pf_name' => $name, 'pf_count' => $eventCount, 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost), array(array('pf_name', 'pf_server')), array("pf_count=pf_count+{$eventCount}", "pf_time=pf_time+{$timeSum}", "pf_memory=pf_memory+{$memorySum}"), __METHOD__); } if ($useTrx) { $dbw->endAtomic(__METHOD__); } } catch (DBError $e) { } }
function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath; if (!$this->normaliseParams($image, $params)) { return new TransformParameterError($params); } $clientWidth = $params['width']; $clientHeight = $params['height']; $physicalWidth = $params['physicalWidth']; $physicalHeight = $params['physicalHeight']; $srcPath = $image->getPath(); if ($flags & self::TRANSFORM_LATER) { return new ThumbnailImage($image, $dstUrl, $clientWidth, $clientHeight, $dstPath); } if (!wfMkdirParents(dirname($dstPath))) { return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, wfMsg('thumbnail_dest_directory')); } $err = false; if (isset($wgSVGConverters[$wgSVGConverter])) { $cmd = str_replace(array('$path/', '$width', '$height', '$input', '$output'), array($wgSVGConverterPath ? wfEscapeShellArg("{$wgSVGConverterPath}/") : "", intval($physicalWidth), intval($physicalHeight), wfEscapeShellArg($srcPath), wfEscapeShellArg($dstPath)), $wgSVGConverters[$wgSVGConverter]) . " 2>&1"; wfProfileIn('rsvg'); wfDebug(__METHOD__ . ": {$cmd}\n"); $err = wfShellExec($cmd, $retval); wfProfileOut('rsvg'); } $removed = $this->removeBadFile($dstPath, $retval); if ($retval != 0 || $removed) { wfDebugLog('thumbnail', sprintf('thumbnail failed on %s: error %d "%s" from "%s"', wfHostname(), $retval, trim($err), $cmd)); return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, $err); } else { return new ThumbnailImage($image, $dstUrl, $clientWidth, $clientHeight, $dstPath); } }
function logToFile($msg, $file) { $header = '[' . date('d\\TH:i:s') . '] ' . wfHostname() . ' ' . posix_getpid(); if ($this->slaveId !== false) { $header .= "({$this->slaveId})"; } $header .= ' ' . wfWikiID(); wfErrorLog(sprintf("%-50s %s\n", $header, $msg), $file); }
/** * @param array $lbConf Config for LBFactory::__construct() * @param Config $mainConfig Main config object from MediaWikiServices * @return array */ public static function applyDefaultConfig(array $lbConf, Config $mainConfig) { global $wgCommandLineMode; $lbConf += ['localDomain' => new DatabaseDomain($mainConfig->get('DBname'), null, $mainConfig->get('DBprefix')), 'profiler' => Profiler::instance(), 'trxProfiler' => Profiler::instance()->getTransactionProfiler(), 'replLogger' => LoggerFactory::getInstance('DBReplication'), 'queryLogger' => LoggerFactory::getInstance('DBQuery'), 'connLogger' => LoggerFactory::getInstance('DBConnection'), 'perfLogger' => LoggerFactory::getInstance('DBPerformance'), 'errorLogger' => [MWExceptionHandler::class, 'logException'], 'cliMode' => $wgCommandLineMode, 'hostname' => wfHostname(), 'readOnlyReason' => wfConfiguredReadOnlyReason()]; if ($lbConf['class'] === 'LBFactorySimple') { if (isset($lbConf['servers'])) { // Server array is already explicitly configured; leave alone } elseif (is_array($mainConfig->get('DBservers'))) { foreach ($mainConfig->get('DBservers') as $i => $server) { if ($server['type'] === 'sqlite') { $server += ['dbDirectory' => $mainConfig->get('SQLiteDataDir')]; } elseif ($server['type'] === 'postgres') { $server += ['port' => $mainConfig->get('DBport')]; } $lbConf['servers'][$i] = $server + ['schema' => $mainConfig->get('DBmwschema'), 'tablePrefix' => $mainConfig->get('DBprefix'), 'flags' => DBO_DEFAULT, 'sqlMode' => $mainConfig->get('SQLMode'), 'utf8Mode' => $mainConfig->get('DBmysql5')]; } } else { $flags = DBO_DEFAULT; $flags |= $mainConfig->get('DebugDumpSql') ? DBO_DEBUG : 0; $flags |= $mainConfig->get('DBssl') ? DBO_SSL : 0; $flags |= $mainConfig->get('DBcompress') ? DBO_COMPRESS : 0; $server = ['host' => $mainConfig->get('DBserver'), 'user' => $mainConfig->get('DBuser'), 'password' => $mainConfig->get('DBpassword'), 'dbname' => $mainConfig->get('DBname'), 'schema' => $mainConfig->get('DBmwschema'), 'tablePrefix' => $mainConfig->get('DBprefix'), 'type' => $mainConfig->get('DBtype'), 'load' => 1, 'flags' => $flags, 'sqlMode' => $mainConfig->get('SQLMode'), 'utf8Mode' => $mainConfig->get('DBmysql5')]; if ($server['type'] === 'sqlite') { $server['dbDirectory'] = $mainConfig->get('SQLiteDataDir'); } elseif ($server['type'] === 'postgres') { $server['port'] = $mainConfig->get('DBport'); } $lbConf['servers'] = [$server]; } if (!isset($lbConf['externalClusters'])) { $lbConf['externalClusters'] = $mainConfig->get('ExternalServers'); } } elseif ($lbConf['class'] === 'LBFactoryMulti') { if (isset($lbConf['serverTemplate'])) { $lbConf['serverTemplate']['schema'] = $mainConfig->get('DBmwschema'); $lbConf['serverTemplate']['sqlMode'] = $mainConfig->get('SQLMode'); $lbConf['serverTemplate']['utf8Mode'] = $mainConfig->get('DBmysql5'); } } // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804) $sCache = MediaWikiServices::getInstance()->getLocalServerObjectCache(); if ($sCache->getQoS($sCache::ATTR_EMULATION) > $sCache::QOS_EMULATION_SQL) { $lbConf['srvCache'] = $sCache; } $cCache = ObjectCache::getLocalClusterInstance(); if ($cCache->getQoS($cCache::ATTR_EMULATION) > $cCache::QOS_EMULATION_SQL) { $lbConf['memCache'] = $cCache; } $wCache = MediaWikiServices::getInstance()->getMainWANObjectCache(); if ($wCache->getQoS($wCache::ATTR_EMULATION) > $wCache::QOS_EMULATION_SQL) { $lbConf['wanCache'] = $wCache; } return $lbConf; }
/** * storeData -- push data frame to Stomp * * @param String $type * @author Piotr Molski (MoLi) * @access private * */ private function send_log() { wfProfileIn(__METHOD__); try { $data = json_encode(array('cityId' => $this->mCityId, 'pageId' => $this->mPageId, 'revId' => $this->mRevId, 'logId' => $this->mLogId, 'serverName' => $this->mServerName, 'archive' => $this->mArchive, 'hostname' => wfHostname(), 'beaconId' => wfGetBeaconId())); WScribeClient::singleton($this->mKey)->send($data); } catch (TException $e) { Wikia::log(__METHOD__, 'scribeClient exception', $e->getMessage()); } wfProfileOut(__METHOD__); }
/** * Returns TransactionTrace singleton instance * * @return TransactionTrace */ public static function getInstance() { static $instance; if ($instance === null) { global $wgWikiaEnvironment; $instance = new TransactionTrace(array(new TransactionTraceNewrelic(), new TransactionTraceScribe())); $instance->set(self::PARAM_ENVIRONMENT, $wgWikiaEnvironment); $instance->set(self::PARAM_HOSTNAME, wfHostname()); $instance->set(self::PARAM_PHP_VERSION, explode('-', phpversion())[0]); // report "5.4.17-1~precise+1" as "5.4.17" } return $instance; }
/** * Log the whole profiling data into the database. */ public function logData() { global $wgProfilePerHost; # Do not log anything if database is readonly (bug 5375) if (wfReadOnly()) { return; } if ($wgProfilePerHost) { $pfhost = wfHostname(); } else { $pfhost = ''; } try { $this->collateData(); $dbw = wfGetDB(DB_MASTER); $useTrx = $dbw->getType() === 'sqlite'; // much faster if ($useTrx) { $dbw->begin(); } foreach ($this->mCollated as $name => $data) { $eventCount = $data['count']; $timeSum = (double) ($data['real'] * 1000); $memorySum = (double) $data['memory']; $name = substr($name, 0, 255); // Kludge $timeSum = $timeSum >= 0 ? $timeSum : 0; $memorySum = $memorySum >= 0 ? $memorySum : 0; $dbw->update('profiling', array("pf_count=pf_count+{$eventCount}", "pf_time=pf_time+{$timeSum}", "pf_memory=pf_memory+{$memorySum}"), array('pf_name' => $name, 'pf_server' => $pfhost), __METHOD__); $rc = $dbw->affectedRows(); if ($rc == 0) { $dbw->insert('profiling', array('pf_name' => $name, 'pf_count' => $eventCount, 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost), __METHOD__, array('IGNORE')); } // When we upgrade to mysql 4.1, the insert+update // can be merged into just a insert with this construct added: // "ON DUPLICATE KEY UPDATE ". // "pf_count=pf_count + VALUES(pf_count), ". // "pf_time=pf_time + VALUES(pf_time)"; } if ($useTrx) { $dbw->commit(); } } catch (DBError $e) { } }
function wfCheckPAD($url, $pad) { global $wgServer, $wgTitle, $wgCookieDomain; if ($wgServer == "http://testers.wikihow.com") { return true; } if (($wgServer == "http://www.wikihow.com" || strpos(wfHostname(), "wikihow.com") !== false) && strpos($pad, "whstatic") === false) { $alerts = new MailAddress("*****@*****.**"); // format of date to correspond to varnish file 04/Dec/2010:07:19:06 -0800 $now = date("d/M/Y:h:i:s O"); $subject = "Not using PAD for thumbnail on " . wfHostname() . " - " . $now; $body = "article {$wgTitle->getFullURL()}\n\n\nUrl: {$url} \n\n \npad {$pad} \n\n\nserver variables " . print_r($_SERVER, true) . "\n\n allheaders: " . print_r(getallheaders(), true) . "\n\n wgserver {$wgServer}\n\ncookie domain {$wgCookieDomain}\n\n Title " . print_r($wgTitle, true) . "\n\nbacktrace: " . strip_tags(wfBacktrace()); UserMailer::send($alerts, $alerts, $subject, $body, $alerts); error_log($body); wfDebug($body); } return true; }
/** */ function wfStreamFile($fname, $headers = array()) { $stat = @stat($fname); if (!$stat) { header('HTTP/1.0 404 Not Found'); header('Cache-Control: no-cache'); header('Content-Type: text/html; charset=utf-8'); $encFile = htmlspecialchars($fname); $encScript = htmlspecialchars($_SERVER['SCRIPT_NAME']); echo "<html><body>\n<h1>File not found</h1>\n<p>Although this PHP script ({$encScript}) exists, the file requested for output \n({$encFile}) does not.</p>\n</body></html>\n"; return; } header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stat['mtime']) . ' GMT'); // Cancel output buffering and gzipping if set wfResetOutputBuffers(); $type = wfGetType($fname); if ($type and $type != "unknown/unknown") { header("Content-type: {$type}"); } else { header('Content-type: application/x-wiki'); } global $wgContLanguageCode; header("Content-Disposition: inline;filename*=utf-8'{$wgContLanguageCode}'" . urlencode(basename($fname))); foreach ($headers as $header) { header($header); } if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { $modsince = preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']); $sinceTime = strtotime($modsince); if ($stat['mtime'] <= $sinceTime) { header("HTTP/1.0 304 Not Modified"); return; } } $h = ""; foreach ($stat as $k => $v) { $h .= "{$k}={$v},"; } header('Content-Length: ' . $stat['size']); header('X-stat-results: ' . $h); header('X-Serving-host: ' . wfHostname()); header('Content-Length-true: ' . $stat['size']); readfile($fname); }
/** * Convert wikitext to HTML * Do not call this function recursively. * * @param string $text text we want to parse * @param $title Title object * @param $options ParserOptions * @param $linestart boolean * @param $clearState boolean * @param int $revid number to pass in {{REVISIONID}} * @return ParserOutput a ParserOutput */ public function parse($text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null) { /** * First pass--just handle <nowiki> sections, pass the rest off * to internalParse() which does all the real work. */ global $wgUseTidy, $wgAlwaysUseTidy, $wgShowHostnames; $fname = __METHOD__ . '-' . wfGetCaller(); wfProfileIn(__METHOD__); wfProfileIn($fname); $this->startParse($title, $options, self::OT_HTML, $clearState); $this->mInputSize = strlen($text); if ($this->mOptions->getEnableLimitReport()) { $this->mOutput->resetParseStartTime(); } # Remove the strip marker tag prefix from the input, if present. if ($clearState) { $text = str_replace($this->mUniqPrefix, '', $text); } $oldRevisionId = $this->mRevisionId; $oldRevisionObject = $this->mRevisionObject; $oldRevisionTimestamp = $this->mRevisionTimestamp; $oldRevisionUser = $this->mRevisionUser; $oldRevisionSize = $this->mRevisionSize; if ($revid !== null) { $this->mRevisionId = $revid; $this->mRevisionObject = null; $this->mRevisionTimestamp = null; $this->mRevisionUser = null; $this->mRevisionSize = null; } wfRunHooks('ParserBeforeStrip', array(&$this, &$text, &$this->mStripState)); # No more strip! wfRunHooks('ParserAfterStrip', array(&$this, &$text, &$this->mStripState)); $text = $this->internalParse($text); wfRunHooks('ParserAfterParse', array(&$this, &$text, &$this->mStripState)); $text = $this->mStripState->unstripGeneral($text); # Clean up special characters, only run once, next-to-last before doBlockLevels $fixtags = array('/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1 ', '/(\\302\\253) /' => '\\1 ', '/ (!\\s*important)/' => ' \\1'); $text = preg_replace(array_keys($fixtags), array_values($fixtags), $text); $text = $this->doBlockLevels($text, $linestart); $this->replaceLinkHolders($text); /** * The input doesn't get language converted if * a) It's disabled * b) Content isn't converted * c) It's a conversion table * d) it is an interface message (which is in the user language) */ if (!($options->getDisableContentConversion() || isset($this->mDoubleUnderscores['nocontentconvert']))) { if (!$this->mOptions->getInterfaceMessage()) { # The position of the convert() call should not be changed. it # assumes that the links are all replaced and the only thing left # is the <nowiki> mark. $text = $this->getConverterLanguage()->convert($text); } } /** * A converted title will be provided in the output object if title and * content conversion are enabled, the article text does not contain * a conversion-suppressing double-underscore tag, and no * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over * automatic link conversion. */ if (!($options->getDisableTitleConversion() || isset($this->mDoubleUnderscores['nocontentconvert']) || isset($this->mDoubleUnderscores['notitleconvert']) || $this->mOutput->getDisplayTitle() !== false)) { $convruletitle = $this->getConverterLanguage()->getConvRuleTitle(); if ($convruletitle) { $this->mOutput->setTitleText($convruletitle); } else { $titleText = $this->getConverterLanguage()->convertTitle($title); $this->mOutput->setTitleText($titleText); } } $text = $this->mStripState->unstripNoWiki($text); wfRunHooks('ParserBeforeTidy', array(&$this, &$text)); $text = $this->replaceTransparentTags($text); $text = $this->mStripState->unstripGeneral($text); $text = Sanitizer::normalizeCharReferences($text); if ($wgUseTidy && $this->mOptions->getTidy() || $wgAlwaysUseTidy) { $text = MWTidy::tidy($text); } else { # attempt to sanitize at least some nesting problems # (bug #2702 and quite a few others) $tidyregs = array('/(<([bi])>)(<([bi])>)?([^<]*)(<\\/?a[^<]*>)([^<]*)(<\\/\\4>)?(<\\/\\2>)/' => '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9', '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\\/a>(.*)<\\/a>/' => '\\1\\2</a>\\3</a>\\1\\4</a>', '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\\/div>)([^<]*)(<\\/\\2>)/' => '\\1\\3<div\\5>\\6</div>\\8\\9', '/<([bi])><\\/\\1>/' => ''); $text = preg_replace(array_keys($tidyregs), array_values($tidyregs), $text); } if ($this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit()) { $this->limitationWarn('expensive-parserfunction', $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit()); } wfRunHooks('ParserAfterTidy', array(&$this, &$text)); # Information on include size limits, for the benefit of users who try to skirt them if ($this->mOptions->getEnableLimitReport()) { $max = $this->mOptions->getMaxIncludeSize(); $cpuTime = $this->mOutput->getTimeSinceStart('cpu'); if ($cpuTime !== null) { $this->mOutput->setLimitReportData('limitreport-cputime', sprintf("%.3f", $cpuTime)); } $wallTime = $this->mOutput->getTimeSinceStart('wall'); $this->mOutput->setLimitReportData('limitreport-walltime', sprintf("%.3f", $wallTime)); $this->mOutput->setLimitReportData('limitreport-ppvisitednodes', array($this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount())); $this->mOutput->setLimitReportData('limitreport-ppgeneratednodes', array($this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount())); $this->mOutput->setLimitReportData('limitreport-postexpandincludesize', array($this->mIncludeSizes['post-expand'], $max)); $this->mOutput->setLimitReportData('limitreport-templateargumentsize', array($this->mIncludeSizes['arg'], $max)); $this->mOutput->setLimitReportData('limitreport-expansiondepth', array($this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth())); $this->mOutput->setLimitReportData('limitreport-expensivefunctioncount', array($this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit())); wfRunHooks('ParserLimitReportPrepare', array($this, $this->mOutput)); $limitReport = "NewPP limit report\n"; if ($wgShowHostnames) { $limitReport .= 'Parsed by ' . wfHostname() . "\n"; } foreach ($this->mOutput->getLimitReportData() as $key => $value) { if (wfRunHooks('ParserLimitReportFormat', array($key, &$value, &$limitReport, false, false))) { $keyMsg = wfMessage($key)->inLanguage('en')->useDatabase(false); $valueMsg = wfMessage(array("{$key}-value-text", "{$key}-value"))->inLanguage('en')->useDatabase(false); if (!$valueMsg->exists()) { $valueMsg = new RawMessage('$1'); } if (!$keyMsg->isDisabled() && !$valueMsg->isDisabled()) { $valueMsg->params($value); $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n"; } } } // Since we're not really outputting HTML, decode the entities and // then re-encode the things that need hiding inside HTML comments. $limitReport = htmlspecialchars_decode($limitReport); wfRunHooks('ParserLimitReport', array($this, &$limitReport)); // Sanitize for comment. Note '‐' in the replacement is U+2010, // which looks much like the problematic '-'. $limitReport = str_replace(array('-', '&'), array('‐', '&'), $limitReport); $text .= "\n<!-- \n{$limitReport}-->\n"; if ($this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10) { wfDebugLog('generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' . $this->mTitle->getPrefixedDBkey()); } } $this->mOutput->setText($text); $this->mRevisionId = $oldRevisionId; $this->mRevisionObject = $oldRevisionObject; $this->mRevisionTimestamp = $oldRevisionTimestamp; $this->mRevisionUser = $oldRevisionUser; $this->mRevisionSize = $oldRevisionSize; $this->mInputSize = false; wfProfileOut($fname); wfProfileOut(__METHOD__); return $this->mOutput; }
/** * Open a connection to a foreign DB, or return one if it is already open. * * Increments a reference count on the returned connection which locks the * connection to the requested wiki. This reference count can be * decremented by calling reuseConnection(). * * If a connection is open to the appropriate server already, but with the wrong * database, it will be switched to the right database and returned, as long as * it has been freed first with reuseConnection(). * * On error, returns false, and the connection which caused the * error will be available via $this->mErrorConnection. * * @param int $i Server index * @param string $wiki Wiki ID to open * @return DatabaseBase */ private function openForeignConnection($i, $wiki) { list($dbName, $prefix) = wfSplitWikiID($wiki); if (isset($this->mConns['foreignUsed'][$i][$wiki])) { // Reuse an already-used connection $conn = $this->mConns['foreignUsed'][$i][$wiki]; wfDebug(__METHOD__ . ": reusing connection {$i}/{$wiki}\n"); } elseif (isset($this->mConns['foreignFree'][$i][$wiki])) { // Reuse a free connection for the same wiki $conn = $this->mConns['foreignFree'][$i][$wiki]; unset($this->mConns['foreignFree'][$i][$wiki]); $this->mConns['foreignUsed'][$i][$wiki] = $conn; wfDebug(__METHOD__ . ": reusing free connection {$i}/{$wiki}\n"); } elseif (!empty($this->mConns['foreignFree'][$i])) { // Reuse a connection from another wiki $conn = reset($this->mConns['foreignFree'][$i]); $oldWiki = key($this->mConns['foreignFree'][$i]); // The empty string as a DB name means "don't care". // DatabaseMysqlBase::open() already handle this on connection. if ($dbName !== '' && !$conn->selectDB($dbName)) { $this->mLastError = "Error selecting database {$dbName} on server " . $conn->getServer() . " from client host " . wfHostname() . "\n"; $this->mErrorConnection = $conn; $conn = false; } else { $conn->tablePrefix($prefix); unset($this->mConns['foreignFree'][$i][$oldWiki]); $this->mConns['foreignUsed'][$i][$wiki] = $conn; wfDebug(__METHOD__ . ": reusing free connection from {$oldWiki} for {$wiki}\n"); } } else { // Open a new connection $server = $this->mServers[$i]; $server['serverIndex'] = $i; $server['foreignPoolRefCount'] = 0; $server['foreign'] = true; $conn = $this->reallyOpenConnection($server, $dbName); if (!$conn->isOpen()) { wfDebug(__METHOD__ . ": error opening connection for {$i}/{$wiki}\n"); $this->mErrorConnection = $conn; $conn = false; } else { $conn->tablePrefix($prefix); $this->mConns['foreignUsed'][$i][$wiki] = $conn; wfDebug(__METHOD__ . ": opened new connection for {$i}/{$wiki}\n"); } } // Increment reference count if ($conn) { $refCount = $conn->getLBInfo('foreignPoolRefCount'); $conn->setLBInfo('foreignPoolRefCount', $refCount + 1); } return $conn; }
/** * Convert wikitext to HTML * Do not call this function recursively. * * @param string $text Text we want to parse * @param Title $title * @param ParserOptions $options * @param bool $linestart * @param bool $clearState * @param int $revid Number to pass in {{REVISIONID}} * @return ParserOutput A ParserOutput */ public function parse($text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null) { /** * First pass--just handle <nowiki> sections, pass the rest off * to internalParse() which does all the real work. */ global $wgShowHostnames; if ($clearState) { // We use U+007F DELETE to construct strip markers, so we have to make // sure that this character does not occur in the input text. $text = strtr($text, "", "?"); $magicScopeVariable = $this->lock(); } $this->startParse($title, $options, self::OT_HTML, $clearState); $this->currentRevisionCache = null; $this->mInputSize = strlen($text); if ($this->mOptions->getEnableLimitReport()) { $this->mOutput->resetParseStartTime(); } $oldRevisionId = $this->mRevisionId; $oldRevisionObject = $this->mRevisionObject; $oldRevisionTimestamp = $this->mRevisionTimestamp; $oldRevisionUser = $this->mRevisionUser; $oldRevisionSize = $this->mRevisionSize; if ($revid !== null) { $this->mRevisionId = $revid; $this->mRevisionObject = null; $this->mRevisionTimestamp = null; $this->mRevisionUser = null; $this->mRevisionSize = null; } Hooks::run('ParserBeforeStrip', array(&$this, &$text, &$this->mStripState)); # No more strip! Hooks::run('ParserAfterStrip', array(&$this, &$text, &$this->mStripState)); $text = $this->internalParse($text); Hooks::run('ParserAfterParse', array(&$this, &$text, &$this->mStripState)); $text = $this->internalParseHalfParsed($text, true, $linestart); /** * A converted title will be provided in the output object if title and * content conversion are enabled, the article text does not contain * a conversion-suppressing double-underscore tag, and no * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over * automatic link conversion. */ if (!($options->getDisableTitleConversion() || isset($this->mDoubleUnderscores['nocontentconvert']) || isset($this->mDoubleUnderscores['notitleconvert']) || $this->mOutput->getDisplayTitle() !== false)) { $convruletitle = $this->getConverterLanguage()->getConvRuleTitle(); if ($convruletitle) { $this->mOutput->setTitleText($convruletitle); } else { $titleText = $this->getConverterLanguage()->convertTitle($title); $this->mOutput->setTitleText($titleText); } } if ($this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit()) { $this->limitationWarn('expensive-parserfunction', $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit()); } # Information on include size limits, for the benefit of users who try to skirt them if ($this->mOptions->getEnableLimitReport()) { $max = $this->mOptions->getMaxIncludeSize(); $cpuTime = $this->mOutput->getTimeSinceStart('cpu'); if ($cpuTime !== null) { $this->mOutput->setLimitReportData('limitreport-cputime', sprintf("%.3f", $cpuTime)); } $wallTime = $this->mOutput->getTimeSinceStart('wall'); $this->mOutput->setLimitReportData('limitreport-walltime', sprintf("%.3f", $wallTime)); $this->mOutput->setLimitReportData('limitreport-ppvisitednodes', array($this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount())); $this->mOutput->setLimitReportData('limitreport-ppgeneratednodes', array($this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount())); $this->mOutput->setLimitReportData('limitreport-postexpandincludesize', array($this->mIncludeSizes['post-expand'], $max)); $this->mOutput->setLimitReportData('limitreport-templateargumentsize', array($this->mIncludeSizes['arg'], $max)); $this->mOutput->setLimitReportData('limitreport-expansiondepth', array($this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth())); $this->mOutput->setLimitReportData('limitreport-expensivefunctioncount', array($this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit())); Hooks::run('ParserLimitReportPrepare', array($this, $this->mOutput)); $limitReport = "NewPP limit report\n"; if ($wgShowHostnames) { $limitReport .= 'Parsed by ' . wfHostname() . "\n"; } $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n"; $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n"; $limitReport .= 'Dynamic content: ' . ($this->mOutput->hasDynamicContent() ? 'true' : 'false') . "\n"; foreach ($this->mOutput->getLimitReportData() as $key => $value) { if (Hooks::run('ParserLimitReportFormat', array($key, &$value, &$limitReport, false, false))) { $keyMsg = wfMessage($key)->inLanguage('en')->useDatabase(false); $valueMsg = wfMessage(array("{$key}-value-text", "{$key}-value"))->inLanguage('en')->useDatabase(false); if (!$valueMsg->exists()) { $valueMsg = new RawMessage('$1'); } if (!$keyMsg->isDisabled() && !$valueMsg->isDisabled()) { $valueMsg->params($value); $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n"; } } } // Since we're not really outputting HTML, decode the entities and // then re-encode the things that need hiding inside HTML comments. $limitReport = htmlspecialchars_decode($limitReport); Hooks::run('ParserLimitReport', array($this, &$limitReport)); // Sanitize for comment. Note '‐' in the replacement is U+2010, // which looks much like the problematic '-'. $limitReport = str_replace(array('-', '&'), array('‐', '&'), $limitReport); $text .= "\n<!-- \n{$limitReport}-->\n"; // Add on template profiling data $dataByFunc = $this->mProfiler->getFunctionStats(); uasort($dataByFunc, function ($a, $b) { return $a['real'] < $b['real']; // descending order }); $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n"; foreach (array_slice($dataByFunc, 0, 10) as $item) { $profileReport .= sprintf("%6.2f%% %8.3f %6d - %s\n", $item['%real'], $item['real'], $item['calls'], htmlspecialchars($item['name'])); } $text .= "\n<!-- \n{$profileReport}-->\n"; if ($this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10) { wfDebugLog('generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' . $this->mTitle->getPrefixedDBkey()); } } $this->mOutput->setText($text); $this->mRevisionId = $oldRevisionId; $this->mRevisionObject = $oldRevisionObject; $this->mRevisionTimestamp = $oldRevisionTimestamp; $this->mRevisionUser = $oldRevisionUser; $this->mRevisionSize = $oldRevisionSize; $this->mInputSize = false; $this->currentRevisionCache = null; return $this->mOutput; }
function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { global $wgDjvuRenderer, $wgDjvuPostProcessor; // Fetch XML and check it, to give a more informative error message than the one which // normaliseParams will inevitably give. $xml = $image->getMetadata(); if (!$xml) { return new MediaTransformError('thumbnail_error', @$params['width'], @$params['height'], wfMsg('djvu_no_xml')); } if (!$this->normaliseParams($image, $params)) { return new TransformParameterError($params); } $width = $params['width']; $height = $params['height']; $srcPath = $image->getPath(); $page = $params['page']; if ($page > $this->pageCount($image)) { return new MediaTransformError('thumbnail_error', $width, $height, wfMsg('djvu_page_error')); } if ($flags & self::TRANSFORM_LATER) { return new ThumbnailImage($image, $dstUrl, $width, $height, $dstPath, $page); } if (!wfMkdirParents(dirname($dstPath))) { return new MediaTransformError('thumbnail_error', $width, $height, wfMsg('thumbnail_dest_directory')); } # Use a subshell (brackets) to aggregate stderr from both pipeline commands # before redirecting it to the overall stdout. This works in both Linux and Windows XP. $cmd = '(' . wfEscapeShellArg($wgDjvuRenderer) . " -format=ppm -page={$page} -size={$width}x{$height} " . wfEscapeShellArg($srcPath); if ($wgDjvuPostProcessor) { $cmd .= " | {$wgDjvuPostProcessor}"; } $cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1'; wfProfileIn('ddjvu'); wfDebug(__METHOD__ . ": {$cmd}\n"); $err = wfShellExec($cmd, $retval); wfProfileOut('ddjvu'); $removed = $this->removeBadFile($dstPath, $retval); if ($retval != 0 || $removed) { wfDebugLog('thumbnail', sprintf('thumbnail failed on %s: error %d "%s" from "%s"', wfHostname(), $retval, trim($err), $cmd)); return new MediaTransformError('thumbnail_error', $width, $height, $err); } else { return new ThumbnailImage($image, $dstUrl, $width, $height, $dstPath, $page); } }
/** * Returns a script tag that stores the amount of time it took MediaWiki to * handle the request in milliseconds as 'wgBackendResponseTime'. * * If $wgShowHostnames is true, the script will also set 'wgHostname' to the * hostname of the server handling the request. * * @return string */ function wfReportTime() { global $wgRequestTime, $wgShowHostnames; $responseTime = round((microtime(true) - $wgRequestTime) * 1000); $reportVars = array('wgBackendResponseTime' => $responseTime); if ($wgShowHostnames) { $reportVars['wgHostname'] = wfHostname(); } return Skin::makeVariablesScript($reportVars); }
/** * Output a thumbnail generation error message * * @param $status integer * @param $msg string * @return void */ function wfThumbError($status, $msg) { global $wgShowHostnames; header('Cache-Control: no-cache'); header('Content-Type: text/html; charset=utf-8'); if ($status == 404) { header('HTTP/1.1 404 Not found'); } elseif ($status == 403) { header('HTTP/1.1 403 Forbidden'); header('Vary: Cookie'); } else { header('HTTP/1.1 500 Internal server error'); } if ($wgShowHostnames) { $url = htmlspecialchars(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''); $hostname = htmlspecialchars(wfHostname()); $debug = "<!-- {$url} -->\n<!-- {$hostname} -->\n"; } else { $debug = ''; } echo <<<EOT <html><head><title>Error generating thumbnail</title></head> <body> <h1>Error generating thumbnail</h1> <p> {$msg} </p> {$debug} </body> </html> EOT; }
/** * Log a function into the database. * * @param $name String: function name * @param $timeSum Float * @param $eventCount Integer: number of times that function was called * @param $memorySum Integer: memory used by the function */ static function logToDB($name, $timeSum, $eventCount, $memorySum) { # Do not log anything if database is readonly (bug 5375) if (wfReadOnly()) { return; } global $wgProfilePerHost; $dbw = wfGetDB(DB_MASTER); if (!is_object($dbw)) { return false; } $errorState = $dbw->ignoreErrors(true); $name = substr($name, 0, 255); if ($wgProfilePerHost) { $pfhost = wfHostname(); } else { $pfhost = ''; } // Kludge $timeSum = $timeSum >= 0 ? $timeSum : 0; $memorySum = $memorySum >= 0 ? $memorySum : 0; $dbw->update('profiling', array("pf_count=pf_count+{$eventCount}", "pf_time=pf_time+{$timeSum}", "pf_memory=pf_memory+{$memorySum}"), array('pf_name' => $name, 'pf_server' => $pfhost), __METHOD__); $rc = $dbw->affectedRows(); if ($rc == 0) { $dbw->insert('profiling', array('pf_name' => $name, 'pf_count' => $eventCount, 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost), __METHOD__, array('IGNORE')); } // When we upgrade to mysql 4.1, the insert+update // can be merged into just a insert with this construct added: // "ON DUPLICATE KEY UPDATE ". // "pf_count=pf_count + VALUES(pf_count), ". // "pf_time=pf_time + VALUES(pf_time)"; $dbw->ignoreErrors($errorState); }
/** * Log an error that occurred in an external process * * Moved from BitmapHandler to MediaHandler with MediaWiki 1.23 * * @since 1.23 * @param int $retval * @param string $err Error reported by command. Anything longer than * MediaHandler::MAX_ERR_LOG_SIZE is stripped off. * @param string $cmd */ protected function logErrorForExternalProcess($retval, $err, $cmd) { # Keep error output limited (bug 57985) $errMessage = trim(substr($err, 0, self::MAX_ERR_LOG_SIZE)); wfDebugLog('thumbnail', sprintf('thumbnail failed on %s: error %d "%s" from "%s"', wfHostname(), $retval, $errMessage, $cmd)); }
/** * Create a thumbnail. * * This sets up various parameters, and then calls a helper method * based on $this->getScalerType in order to scale the image. * * @param File $image * @param string $dstPath * @param string $dstUrl * @param array $params * @param int $flags * @return MediaTransformError|ThumbnailImage|TransformParameterError */ function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { if (!$this->normaliseParams($image, $params)) { return new TransformParameterError($params); } # Create a parameter array to pass to the scaler $scalerParams = array('physicalWidth' => $params['physicalWidth'], 'physicalHeight' => $params['physicalHeight'], 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}", 'clientWidth' => $params['width'], 'clientHeight' => $params['height'], 'comment' => isset($params['descriptionUrl']) ? "File source: {$params['descriptionUrl']}" : '', 'srcWidth' => $image->getWidth(), 'srcHeight' => $image->getHeight(), 'mimeType' => $image->getMimeType(), 'dstPath' => $dstPath, 'dstUrl' => $dstUrl); if (isset($params['quality']) && $params['quality'] === 'low') { $scalerParams['quality'] = 30; } // For subclasses that might be paged. if ($image->isMultipage() && isset($params['page'])) { $scalerParams['page'] = intval($params['page']); } # Determine scaler type $scaler = $this->getScalerType($dstPath); if (is_array($scaler)) { $scalerName = get_class($scaler[0]); } else { $scalerName = $scaler; } wfDebug(__METHOD__ . ": creating {$scalerParams['physicalDimensions']} " . "thumbnail at {$dstPath} using scaler {$scalerName}\n"); if (!$image->mustRender() && $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] && !isset($scalerParams['quality'])) { # normaliseParams (or the user) wants us to return the unscaled image wfDebug(__METHOD__ . ": returning unscaled image\n"); return $this->getClientScalingThumbnailImage($image, $scalerParams); } if ($scaler == 'client') { # Client-side image scaling, use the source URL # Using the destination URL in a TRANSFORM_LATER request would be incorrect return $this->getClientScalingThumbnailImage($image, $scalerParams); } if ($image->isTransformedLocally() && !$this->isImageAreaOkForThumbnaling($image, $params)) { global $wgMaxImageArea; return new TransformTooBigImageAreaError($params, $wgMaxImageArea); } if ($flags & self::TRANSFORM_LATER) { wfDebug(__METHOD__ . ": Transforming later per flags.\n"); $newParams = array('width' => $scalerParams['clientWidth'], 'height' => $scalerParams['clientHeight']); if (isset($params['quality'])) { $newParams['quality'] = $params['quality']; } if (isset($params['page']) && $params['page']) { $newParams['page'] = $params['page']; } return new ThumbnailImage($image, $dstUrl, false, $newParams); } # Try to make a target path for the thumbnail if (!wfMkdirParents(dirname($dstPath), null, __METHOD__)) { wfDebug(__METHOD__ . ": Unable to create thumbnail destination " . "directory, falling back to client scaling\n"); return $this->getClientScalingThumbnailImage($image, $scalerParams); } # Transform functions and binaries need a FS source file $thumbnailSource = $this->getThumbnailSource($image, $params); // If the source isn't the original, disable EXIF rotation because it's already been applied if ($scalerParams['srcWidth'] != $thumbnailSource['width'] || $scalerParams['srcHeight'] != $thumbnailSource['height']) { $scalerParams['disableRotation'] = true; } $scalerParams['srcPath'] = $thumbnailSource['path']; $scalerParams['srcWidth'] = $thumbnailSource['width']; $scalerParams['srcHeight'] = $thumbnailSource['height']; if ($scalerParams['srcPath'] === false) { // Failed to get local copy wfDebugLog('thumbnail', sprintf('Thumbnail failed on %s: could not get local copy of "%s"', wfHostname(), $image->getName())); return new MediaTransformError('thumbnail_error', $scalerParams['clientWidth'], $scalerParams['clientHeight'], wfMessage('filemissing')->text()); } # Try a hook. Called "Bitmap" for historical reasons. /** @var $mto MediaTransformOutput */ $mto = null; Hooks::run('BitmapHandlerTransform', array($this, $image, &$scalerParams, &$mto)); if (!is_null($mto)) { wfDebug(__METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n"); $scaler = 'hookaborted'; } // $scaler will return a MediaTransformError on failure, or false on success. // If the scaler is succesful, it will have created a thumbnail at the destination // path. if (is_array($scaler) && is_callable($scaler)) { // Allow subclasses to specify their own rendering methods. $err = call_user_func($scaler, $image, $scalerParams); } else { switch ($scaler) { case 'hookaborted': # Handled by the hook above $err = $mto->isError() ? $mto : false; break; case 'im': $err = $this->transformImageMagick($image, $scalerParams); break; case 'custom': $err = $this->transformCustom($image, $scalerParams); break; case 'imext': $err = $this->transformImageMagickExt($image, $scalerParams); break; case 'gd': default: $err = $this->transformGd($image, $scalerParams); break; } } # Remove the file if a zero-byte thumbnail was created, or if there was an error $removed = $this->removeBadFile($dstPath, (bool) $err); if ($err) { # transform returned MediaTransforError return $err; } elseif ($removed) { # Thumbnail was zero-byte and had to be removed return new MediaTransformError('thumbnail_error', $scalerParams['clientWidth'], $scalerParams['clientHeight'], wfMessage('unknown-error')->text()); } elseif ($mto) { return $mto; } else { $newParams = array('width' => $scalerParams['clientWidth'], 'height' => $scalerParams['clientHeight']); if (isset($params['quality'])) { $newParams['quality'] = $params['quality']; } if (isset($params['page']) && $params['page']) { $newParams['page'] = $params['page']; } return new ThumbnailImage($image, $dstUrl, $dstPath, $newParams); } }
/** * Format a message as `wfDebugLog() would have formatted it. * * @param string $channel * @param string $message * @param array $context * @return null */ protected static function formatAsWfDebugLog($channel, $message, $context) { $time = wfTimestamp(TS_DB); $wiki = wfWikiID(); $host = wfHostname(); $text = "{$time} {$host} {$wiki}: {$message}\n"; return $text; }
/** * Add response headers with debug data and statistics * * @param WebResponse $response * @author macbre */ private static function addExtraHeaders(WebResponse $response) { global $wgRequestTime; $elapsed = microtime(true) - $wgRequestTime; $response->header(sprintf('X-Served-By:%s', wfHostname())); $response->header(sprintf('X-Backend-Response-Time:%01.3f', $elapsed)); $response->header('X-Cache: ORIGIN'); $response->header('X-Cache-Hits: ORIGIN'); }
/** Open an MSSQL database and return a resource handle to it * NOTE: only $dbName is used, the other parameters are irrelevant for MSSQL databases */ function open($server, $user, $password, $dbName) { wfProfileIn(__METHOD__); # Test for missing mysql.so # First try to load it if (!@extension_loaded('mssql')) { @dl('mssql.so'); } # Fail now # Otherwise we get a suppressed fatal error, which is very hard to track down if (!function_exists('mssql_connect')) { throw new DBConnectionError($this, "MSSQL functions missing, have you compiled PHP with the --with-mssql option?\n"); } $this->close(); $this->mServer = $server; $this->mUser = $user; $this->mPassword = $password; $this->mDBname = $dbName; wfProfileIn("dbconnect-{$server}"); # Try to connect up to three times # The kernel's default SYN retransmission period is far too slow for us, # so we use a short timeout plus a manual retry. $this->mConn = false; $max = 3; for ($i = 0; $i < $max && !$this->mConn; $i++) { if ($i > 1) { usleep(1000); } if ($this->mFlags & DBO_PERSISTENT) { @($this->mConn = mssql_pconnect($server, $user, $password)); } else { # Create a new connection... @($this->mConn = mssql_connect($server, $user, $password, true)); } } wfProfileOut("dbconnect-{$server}"); if ($dbName != '') { if ($this->mConn !== false) { $success = @mssql_select_db($dbName, $this->mConn); if (!$success) { $error = "Error selecting database {$dbName} on server {$this->mServer} " . "from client host " . wfHostname() . "\n"; wfLogDBError(" Error selecting database {$dbName} on server {$this->mServer} \n"); wfDebug($error); } } else { wfDebug("DB connection error\n"); wfDebug("Server: {$server}, User: {$user}, Password: "******"...\n"); $success = false; } } else { # Delay USE query $success = (bool) $this->mConn; } if (!$success) { $this->reportConnectionError(); } $this->mOpened = $success; wfProfileOut(__METHOD__); return $success; }
/** * @param $server string * @param $user string * @param $password string * @param $dbName string * @return bool * @throws DBConnectionError */ function open( $server, $user, $password, $dbName ) { global $wgAllDBsAreLocalhost, $wgDBmysql5, $wgSQLMode; wfProfileIn( __METHOD__ ); # Debugging hack -- fake cluster if ( $wgAllDBsAreLocalhost ) { $realServer = 'localhost'; } else { $realServer = $server; } $this->close(); $this->mServer = $server; $this->mUser = $user; $this->mPassword = $password; $this->mDBname = $dbName; wfProfileIn( "dbconnect-$server" ); # The kernel's default SYN retransmission period is far too slow for us, # so we use a short timeout plus a manual retry. Retrying means that a small # but finite rate of SYN packet loss won't cause user-visible errors. $this->mConn = false; $this->installErrorHandler(); try { $this->mConn = $this->mysqlConnect( $realServer ); } catch ( Exception $ex ) { wfProfileOut( "dbconnect-$server" ); wfProfileOut( __METHOD__ ); throw $ex; } $error = $this->restoreErrorHandler(); wfProfileOut( "dbconnect-$server" ); # Always log connection errors if ( !$this->mConn ) { if ( !$error ) { $error = $this->lastError(); } wfLogDBError( "Error connecting to {$this->mServer}: $error\n" ); wfDebug( "DB connection error\n" . "Server: $server, User: $user, Password: "******"..., error: " . $error . "\n" ); wfProfileOut( __METHOD__ ); return $this->reportConnectionError( $error ); } if ( $dbName != '' ) { wfSuppressWarnings(); $success = $this->selectDB( $dbName ); wfRestoreWarnings(); if ( !$success ) { wfLogDBError( "Error selecting database $dbName on server {$this->mServer}\n" ); wfDebug( "Error selecting database $dbName on server {$this->mServer} " . "from client host " . wfHostname() . "\n" ); wfProfileOut( __METHOD__ ); return $this->reportConnectionError( "Error selecting database $dbName" ); } } // Tell the server we're communicating with it in UTF-8. // This may engage various charset conversions. if ( $wgDBmysql5 ) { $this->query( 'SET NAMES utf8', __METHOD__ ); } else { $this->query( 'SET NAMES binary', __METHOD__ ); } // Set SQL mode, default is turning them all off, can be overridden or skipped with null if ( is_string( $wgSQLMode ) ) { $mode = $this->addQuotes( $wgSQLMode ); $this->query( "SET sql_mode = $mode", __METHOD__ ); } $this->mOpened = true; wfProfileOut( __METHOD__ ); return true; }
/** * Convert wikitext to HTML * Do not call this function recursively. * * @param string $text Text we want to parse * @param Title $title * @param ParserOptions $options * @param bool $linestart * @param bool $clearState * @param int $revid Number to pass in {{REVISIONID}} * @return ParserOutput A ParserOutput */ public function parse($text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null) { /** * First pass--just handle <nowiki> sections, pass the rest off * to internalParse() which does all the real work. */ global $wgShowHostnames; if ($clearState) { // We use U+007F DELETE to construct strip markers, so we have to make // sure that this character does not occur in the input text. $text = strtr($text, "", "?"); $magicScopeVariable = $this->lock(); } $this->startParse($title, $options, self::OT_HTML, $clearState); $this->currentRevisionCache = null; $this->mInputSize = strlen($text); if ($this->mOptions->getEnableLimitReport()) { $this->mOutput->resetParseStartTime(); } $oldRevisionId = $this->mRevisionId; $oldRevisionObject = $this->mRevisionObject; $oldRevisionTimestamp = $this->mRevisionTimestamp; $oldRevisionUser = $this->mRevisionUser; $oldRevisionSize = $this->mRevisionSize; if ($revid !== null) { $this->mRevisionId = $revid; $this->mRevisionObject = null; $this->mRevisionTimestamp = null; $this->mRevisionUser = null; $this->mRevisionSize = null; } Hooks::run('ParserBeforeStrip', [&$this, &$text, &$this->mStripState]); # No more strip! Hooks::run('ParserAfterStrip', [&$this, &$text, &$this->mStripState]); $text = $this->internalParse($text); Hooks::run('ParserAfterParse', [&$this, &$text, &$this->mStripState]); $text = $this->internalParseHalfParsed($text, true, $linestart); /** * A converted title will be provided in the output object if title and * content conversion are enabled, the article text does not contain * a conversion-suppressing double-underscore tag, and no * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over * automatic link conversion. */ if (!($options->getDisableTitleConversion() || isset($this->mDoubleUnderscores['nocontentconvert']) || isset($this->mDoubleUnderscores['notitleconvert']) || $this->mOutput->getDisplayTitle() !== false)) { $convruletitle = $this->getConverterLanguage()->getConvRuleTitle(); if ($convruletitle) { $this->mOutput->setTitleText($convruletitle); } else { $titleText = $this->getConverterLanguage()->convertTitle($title); $this->mOutput->setTitleText($titleText); } } # Done parsing! Compute runtime adaptive expiry if set $this->mOutput->finalizeAdaptiveCacheExpiry(); # Warn if too many heavyweight parser functions were used if ($this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit()) { $this->limitationWarn('expensive-parserfunction', $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit()); } # Information on include size limits, for the benefit of users who try to skirt them if ($this->mOptions->getEnableLimitReport()) { $max = $this->mOptions->getMaxIncludeSize(); $cpuTime = $this->mOutput->getTimeSinceStart('cpu'); if ($cpuTime !== null) { $this->mOutput->setLimitReportData('limitreport-cputime', sprintf("%.3f", $cpuTime)); } $wallTime = $this->mOutput->getTimeSinceStart('wall'); $this->mOutput->setLimitReportData('limitreport-walltime', sprintf("%.3f", $wallTime)); $this->mOutput->setLimitReportData('limitreport-ppvisitednodes', [$this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount()]); $this->mOutput->setLimitReportData('limitreport-ppgeneratednodes', [$this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount()]); $this->mOutput->setLimitReportData('limitreport-postexpandincludesize', [$this->mIncludeSizes['post-expand'], $max]); $this->mOutput->setLimitReportData('limitreport-templateargumentsize', [$this->mIncludeSizes['arg'], $max]); $this->mOutput->setLimitReportData('limitreport-expansiondepth', [$this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth()]); $this->mOutput->setLimitReportData('limitreport-expensivefunctioncount', [$this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit()]); Hooks::run('ParserLimitReportPrepare', [$this, $this->mOutput]); $limitReport = ''; Hooks::run('ParserLimitReport', [$this, &$limitReport]); if ($limitReport != '') { // Sanitize for comment. Note '‐' in the replacement is U+2010, // which looks much like the problematic '-'. $limitReport = str_replace(['-', '&'], ['‐', '&'], $limitReport); $text .= "\n<!-- \nNewPP limit report\n{$limitReport}-->\n"; } // Add on template profiling data in human/machine readable way $dataByFunc = $this->mProfiler->getFunctionStats(); uasort($dataByFunc, function ($a, $b) { return $a['real'] < $b['real']; // descending order }); $profileReport = []; foreach (array_slice($dataByFunc, 0, 10) as $item) { $profileReport[] = sprintf("%6.2f%% %8.3f %6d %s", $item['%real'], $item['real'], $item['calls'], $item['name']); } $this->mOutput->setLimitReportData('limitreport-timingprofile', $profileReport); // Add other cache related metadata if ($wgShowHostnames) { $this->mOutput->setLimitReportData('cachereport-origin', wfHostname()); } $this->mOutput->setLimitReportData('cachereport-timestamp', $this->mOutput->getCacheTime()); $this->mOutput->setLimitReportData('cachereport-ttl', $this->mOutput->getCacheExpiry()); $this->mOutput->setLimitReportData('cachereport-transientcontent', $this->mOutput->hasDynamicContent()); if ($this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10) { wfDebugLog('generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' . $this->mTitle->getPrefixedDBkey()); } } $this->mOutput->setText($text); $this->mRevisionId = $oldRevisionId; $this->mRevisionObject = $oldRevisionObject; $this->mRevisionTimestamp = $oldRevisionTimestamp; $this->mRevisionUser = $oldRevisionUser; $this->mRevisionSize = $oldRevisionSize; $this->mInputSize = false; $this->currentRevisionCache = null; return $this->mOutput; }
/** * Log an error that occured in an external process * * @param $retval int * @param $err int * @param $cmd string */ protected function logErrorForExternalProcess($retval, $err, $cmd) { wfDebugLog('thumbnail', sprintf('thumbnail failed on %s: error %d "%s" from "%s"', wfHostname(), $retval, trim($err), $cmd)); }
/** * @param File $image * @param string $dstPath * @param string $dstUrl * @param array $params * @param int $flags * @return bool|MediaTransformError|ThumbnailImage|TransformParameterError */ function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { if (!$this->normaliseParams($image, $params)) { return new TransformParameterError($params); } $clientWidth = $params['width']; $clientHeight = $params['height']; $physicalWidth = $params['physicalWidth']; $physicalHeight = $params['physicalHeight']; $lang = isset($params['lang']) ? $params['lang'] : $this->getDefaultRenderLanguage($image); if ($flags & self::TRANSFORM_LATER) { return new ThumbnailImage($image, $dstUrl, $dstPath, $params); } $metadata = $this->unpackMetadata($image->getMetadata()); if (isset($metadata['error'])) { // sanity check $err = wfMessage('svg-long-error', $metadata['error']['message'])->text(); return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, $err); } if (!wfMkdirParents(dirname($dstPath), null, __METHOD__)) { return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, wfMessage('thumbnail_dest_directory')->text()); } $srcPath = $image->getLocalRefPath(); if ($srcPath === false) { // Failed to get local copy wfDebugLog('thumbnail', sprintf('Thumbnail failed on %s: could not get local copy of "%s"', wfHostname(), $image->getName())); return new MediaTransformError('thumbnail_error', $params['width'], $params['height'], wfMessage('filemissing')->text()); } // Make a temp dir with a symlink to the local copy in it. // This plays well with rsvg-convert policy for external entities. // https://git.gnome.org/browse/librsvg/commit/?id=f01aded72c38f0e18bc7ff67dee800e380251c8e $tmpDir = wfTempDir() . '/svg_' . wfRandomString(24); $lnPath = "{$tmpDir}/" . basename($srcPath); $ok = mkdir($tmpDir, 0771) && symlink($srcPath, $lnPath); /** @noinspection PhpUnusedLocalVariableInspection */ $cleaner = new ScopedCallback(function () use($tmpDir, $lnPath) { MediaWiki\suppressWarnings(); unlink($lnPath); rmdir($tmpDir); MediaWiki\restoreWarnings(); }); if (!$ok) { wfDebugLog('thumbnail', sprintf('Thumbnail failed on %s: could not link %s to %s', wfHostname(), $lnPath, $srcPath)); return new MediaTransformError('thumbnail_error', $params['width'], $params['height'], wfMessage('thumbnail-temp-create')->text()); } $status = $this->rasterize($lnPath, $dstPath, $physicalWidth, $physicalHeight, $lang); if ($status === true) { return new ThumbnailImage($image, $dstUrl, $dstPath, $params); } else { return $status; // MediaTransformError } }
/** * Generate a debug comment indicating diff generating time, * server node, and generator backend. */ protected function debug($generator = "internal") { global $wgShowHostnames; if (!$this->enableDebugComment) { return ''; } $data = array($generator); if ($wgShowHostnames) { $data[] = wfHostname(); } $data[] = wfTimestamp(TS_DB); return "<!-- diff generator: " . implode(" ", array_map("htmlspecialchars", $data)) . " -->\n"; }
function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir; global $wgCustomConvertCommand; global $wgSharpenParameter, $wgSharpenReductionThreshold; global $wgMaxAnimatedGifArea; if (!$this->normaliseParams($image, $params)) { return new TransformParameterError($params); } $physicalWidth = $params['physicalWidth']; $physicalHeight = $params['physicalHeight']; $clientWidth = $params['width']; $clientHeight = $params['height']; $srcWidth = $image->getWidth(); $srcHeight = $image->getHeight(); $mimeType = $image->getMimeType(); $srcPath = $image->getPath(); $retval = 0; wfDebug(__METHOD__ . ": creating {$physicalWidth}x{$physicalHeight} thumbnail at {$dstPath}\n"); if (!$image->mustRender() && $physicalWidth == $srcWidth && $physicalHeight == $srcHeight) { # normaliseParams (or the user) wants us to return the unscaled image wfDebug(__METHOD__ . ": returning unscaled image\n"); return new ThumbnailImage($image, $image->getURL(), $clientWidth, $clientHeight, $srcPath); } if (!$dstPath) { // No output path available, client side scaling only $scaler = 'client'; } elseif ($wgUseImageMagick) { $scaler = 'im'; } elseif ($wgCustomConvertCommand) { $scaler = 'custom'; } elseif (function_exists('imagecreatetruecolor')) { $scaler = 'gd'; } else { $scaler = 'client'; } wfDebug(__METHOD__ . ": scaler {$scaler}\n"); if ($scaler == 'client') { # Client-side image scaling, use the source URL # Using the destination URL in a TRANSFORM_LATER request would be incorrect return new ThumbnailImage($image, $image->getURL(), $clientWidth, $clientHeight, $srcPath); } if ($flags & self::TRANSFORM_LATER) { wfDebug(__METHOD__ . ": Transforming later per flags.\n"); return new ThumbnailImage($image, $dstUrl, $clientWidth, $clientHeight, $dstPath); } if (!wfMkdirParents(dirname($dstPath))) { wfDebug(__METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n"); return new ThumbnailImage($image, $image->getURL(), $clientWidth, $clientHeight, $srcPath); } if ($scaler == 'im') { # use ImageMagick $quality = ''; $sharpen = ''; $frame = ''; $animation = ''; if ($mimeType == 'image/jpeg') { $quality = "-quality 80"; // 80% # Sharpening, see bug 6193 if (($physicalWidth + $physicalHeight) / ($srcWidth + $srcHeight) < $wgSharpenReductionThreshold) { $sharpen = "-sharpen " . wfEscapeShellArg($wgSharpenParameter); } } elseif ($mimeType == 'image/png') { $quality = "-quality 95"; // zlib 9, adaptive filtering } elseif ($mimeType == 'image/gif') { if ($srcWidth * $srcHeight > $wgMaxAnimatedGifArea) { // Extract initial frame only; we're so big it'll // be a total drag. :P $frame = '[0]'; } else { // Coalesce is needed to scale animated GIFs properly (bug 1017). $animation = ' -coalesce '; } } if (strval($wgImageMagickTempDir) !== '') { $tempEnv = 'MAGICK_TMPDIR=' . wfEscapeShellArg($wgImageMagickTempDir) . ' '; } else { $tempEnv = ''; } # Specify white background color, will be used for transparent images # in Internet Explorer/Windows instead of default black. # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}". # It seems that ImageMagick has a bug wherein it produces thumbnails of # the wrong size in the second case. $cmd = $tempEnv . wfEscapeShellArg($wgImageMagickConvertCommand) . " {$quality} -background white -size {$physicalWidth} " . wfEscapeShellArg($srcPath . $frame) . $animation . " -thumbnail " . wfEscapeShellArg("{$physicalWidth}x{$physicalHeight}!") . " -depth 8 {$sharpen} " . wfEscapeShellArg($dstPath) . " 2>&1"; wfDebug(__METHOD__ . ": running ImageMagick: {$cmd}\n"); wfProfileIn('convert'); $err = wfShellExec($cmd, $retval); wfProfileOut('convert'); } elseif ($scaler == 'custom') { # Use a custom convert command # Variables: %s %d %w %h $src = wfEscapeShellArg($srcPath); $dst = wfEscapeShellArg($dstPath); $cmd = $wgCustomConvertCommand; $cmd = str_replace('%s', $src, str_replace('%d', $dst, $cmd)); # Filenames $cmd = str_replace('%h', $physicalHeight, str_replace('%w', $physicalWidth, $cmd)); # Size wfDebug(__METHOD__ . ": Running custom convert command {$cmd}\n"); wfProfileIn('convert'); $err = wfShellExec($cmd, $retval); wfProfileOut('convert'); } else { # Use PHP's builtin GD library functions. # # First find out what kind of file this is, and select the correct # input routine for this. $typemap = array('image/gif' => array('imagecreatefromgif', 'palette', 'imagegif'), 'image/jpeg' => array('imagecreatefromjpeg', 'truecolor', array(__CLASS__, 'imageJpegWrapper')), 'image/png' => array('imagecreatefrompng', 'bits', 'imagepng'), 'image/vnd.wap.wbmp' => array('imagecreatefromwbmp', 'palette', 'imagewbmp'), 'image/xbm' => array('imagecreatefromxbm', 'palette', 'imagexbm')); if (!isset($typemap[$mimeType])) { $err = 'Image type not supported'; wfDebug("{$err}\n"); return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, $err); } list($loader, $colorStyle, $saveType) = $typemap[$mimeType]; if (!function_exists($loader)) { $err = "Incomplete GD library configuration: missing function {$loader}"; wfDebug("{$err}\n"); return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, $err); } $src_image = call_user_func($loader, $srcPath); $dst_image = imagecreatetruecolor($physicalWidth, $physicalHeight); // Initialise the destination image to transparent instead of // the default solid black, to support PNG and GIF transparency nicely $background = imagecolorallocate($dst_image, 0, 0, 0); imagecolortransparent($dst_image, $background); imagealphablending($dst_image, false); if ($colorStyle == 'palette') { // Don't resample for paletted GIF images. // It may just uglify them, and completely breaks transparency. imagecopyresized($dst_image, $src_image, 0, 0, 0, 0, $physicalWidth, $physicalHeight, imagesx($src_image), imagesy($src_image)); } else { imagecopyresampled($dst_image, $src_image, 0, 0, 0, 0, $physicalWidth, $physicalHeight, imagesx($src_image), imagesy($src_image)); } imagesavealpha($dst_image, true); call_user_func($saveType, $dst_image, $dstPath); imagedestroy($dst_image); imagedestroy($src_image); $retval = 0; } $removed = $this->removeBadFile($dstPath, $retval); if ($retval != 0 || $removed) { wfDebugLog('thumbnail', sprintf('thumbnail failed on %s: error %d "%s" from "%s"', wfHostname(), $retval, trim($err), $cmd)); return new MediaTransformError('thumbnail_error', $clientWidth, $clientHeight, $err); } else { return new ThumbnailImage($image, $dstUrl, $clientWidth, $clientHeight, $dstPath); } }
/** * @param string $server * @param string $user * @param string $password * @param string $dbName * @throws Exception|DBConnectionError * @return bool */ function open($server, $user, $password, $dbName) { global $wgAllDBsAreLocalhost, $wgSQLMode; # Close/unset connection handle $this->close(); # Debugging hack -- fake cluster $realServer = $wgAllDBsAreLocalhost ? 'localhost' : $server; $this->mServer = $server; $this->mUser = $user; $this->mPassword = $password; $this->mDBname = $dbName; $this->installErrorHandler(); try { $this->mConn = $this->mysqlConnect($realServer); } catch (Exception $ex) { $this->restoreErrorHandler(); throw $ex; } $error = $this->restoreErrorHandler(); # Always log connection errors if (!$this->mConn) { if (!$error) { $error = $this->lastError(); } wfLogDBError("Error connecting to {db_server}: {error}", $this->getLogContext(array('method' => __METHOD__, 'error' => $error))); wfDebug("DB connection error\n" . "Server: {$server}, User: {$user}, Password: "******"..., error: " . $error . "\n"); $this->reportConnectionError($error); } if ($dbName != '') { MediaWiki\suppressWarnings(); $success = $this->selectDB($dbName); MediaWiki\restoreWarnings(); if (!$success) { wfLogDBError("Error selecting database {db_name} on server {db_server}", $this->getLogContext(array('method' => __METHOD__))); wfDebug("Error selecting database {$dbName} on server {$this->mServer} " . "from client host " . wfHostname() . "\n"); $this->reportConnectionError("Error selecting database {$dbName}"); } } // Tell the server what we're communicating with if (!$this->connectInitCharset()) { $this->reportConnectionError("Error setting character set"); } // Abstract over any insane MySQL defaults $set = array('group_concat_max_len = 262144'); // Set SQL mode, default is turning them all off, can be overridden or skipped with null if (is_string($wgSQLMode)) { $set[] = 'sql_mode = ' . $this->addQuotes($wgSQLMode); } // Set any custom settings defined by site config // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html) foreach ($this->mSessionVars as $var => $val) { // Escape strings but not numbers to avoid MySQL complaining if (!is_int($val) && !is_float($val)) { $val = $this->addQuotes($val); } $set[] = $this->addIdentifierQuotes($var) . ' = ' . $val; } if ($set) { // Use doQuery() to avoid opening implicit transactions (DBO_TRX) $success = $this->doQuery('SET ' . implode(', ', $set)); if (!$success) { wfLogDBError('Error setting MySQL variables on server {db_server} (check $wgSQLMode)', $this->getLogContext(array('method' => __METHOD__))); $this->reportConnectionError('Error setting MySQL variables on server {db_server} (check $wgSQLMode)'); } } $this->mOpened = true; return true; }
/** * Initialize an initial random state based off of whatever we can find * @return string */ protected function initialRandomState() { // $_SERVER contains a variety of unstable user and system specific information // It'll vary a little with each page, and vary even more with separate users // It'll also vary slightly across different machines $state = serialize($_SERVER); // To try vary the system information of the state a bit more // by including the system's hostname into the state $state .= wfHostname(); // Try to gather a little entropy from the different php rand sources $state .= rand() . uniqid(mt_rand(), true); // Include some information about the filesystem's current state in the random state $files = array(); // We know this file is here so grab some info about ourselves $files[] = __FILE__; // We must also have a parent folder, and with the usual file structure, a grandparent $files[] = __DIR__; $files[] = dirname(__DIR__); // The config file is likely the most often edited file we know should // be around so include its stat info into the state. // The constant with its location will almost always be defined, as // WebStart.php defines MW_CONFIG_FILE to $IP/LocalSettings.php unless // being configured with MW_CONFIG_CALLBACK (e.g. the installer). if (defined('MW_CONFIG_FILE')) { $files[] = MW_CONFIG_FILE; } foreach ($files as $file) { MediaWiki\suppressWarnings(); $stat = stat($file); MediaWiki\restoreWarnings(); if ($stat) { // stat() duplicates data into numeric and string keys so kill off all the numeric ones foreach ($stat as $k => $v) { if (is_numeric($k)) { unset($k); } } // The absolute filename itself will differ from install to install so don't leave it out $path = realpath($file); if ($path !== false) { $state .= $path; } else { $state .= $file; } $state .= implode('', $stat); } else { // The fact that the file isn't there is worth at least a // minuscule amount of entropy. $state .= '0'; } } // Try and make this a little more unstable by including the varying process // id of the php process we are running inside of if we are able to access it if (function_exists('getmypid')) { $state .= getmypid(); } // If available try to increase the instability of the data by throwing in // the precise amount of memory that we happen to be using at the moment. if (function_exists('memory_get_usage')) { $state .= memory_get_usage(true); } // It's mostly worthless but throw the wiki's id into the data for a little more variance $state .= wfWikiID(); // If we have a secret key set then throw it into the state as well global $wgSecretKey; if ($wgSecretKey) { $state .= $wgSecretKey; } return $state; }