/** * Convert passed image data, which is assumed to be SVG, to PNG. * * @param string $svg SVG image data * @return string|bool PNG image data, or false on failure */ protected function rasterize($svg) { // This code should be factored out to a separate method on SvgHandler, or perhaps a separate // class, with a separate set of configuration settings. // // This is a distinct use case from regular SVG rasterization: // * We can skip many sanity and security checks (as the images come from a trusted source, // rather than from the user). // * We need to provide extra options to some converters to achieve acceptable quality for very // small images, which might cause performance issues in the general case. // * We want to directly pass image data to the converter, rather than a file path. // // See https://phabricator.wikimedia.org/T76473#801446 for examples of what happens with the // default settings. // // For now, we special-case rsvg (used in WMF production) and do a messy workaround for other // converters. global $wgSVGConverter, $wgSVGConverterPath; $svg = $this->massageSvgPathdata($svg); // Sometimes this might be 'rsvg-secure'. Long as it's rsvg. if (strpos($wgSVGConverter, 'rsvg') === 0) { $command = 'rsvg-convert'; if ($wgSVGConverterPath) { $command = wfEscapeShellArg("{$wgSVGConverterPath}/") . $command; } $process = proc_open($command, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w')), $pipes); if (is_resource($process)) { fwrite($pipes[0], $svg); fclose($pipes[0]); $png = stream_get_contents($pipes[1]); fclose($pipes[1]); proc_close($process); return $png ?: false; } return false; } else { // Write input to and read output from a temporary file $tempFilenameSvg = tempnam(wfTempDir(), 'ResourceLoaderImage'); $tempFilenamePng = tempnam(wfTempDir(), 'ResourceLoaderImage'); file_put_contents($tempFilenameSvg, $svg); $metadata = SVGMetadataExtractor::getMetadata($tempFilenameSvg); if (!isset($metadata['width']) || !isset($metadata['height'])) { unlink($tempFilenameSvg); return false; } $handler = new SvgHandler(); $res = $handler->rasterize($tempFilenameSvg, $tempFilenamePng, $metadata['width'], $metadata['height']); unlink($tempFilenameSvg); $png = null; if ($res === true) { $png = file_get_contents($tempFilenamePng); unlink($tempFilenamePng); } return $png ?: false; } }
/** * Generate a graph for this tag * @param string $tag * @param string $filePath * @return bool, success */ public function makeSvgGraph( $tag, $filePath ) { global $wgSvgGraphDir, $wgContLang, $wgMemc; require_once( "$wgSvgGraphDir/svgGraph.php" ); // load classes require_once( "$wgSvgGraphDir/svgGraph2.php" ); // load classes // Define the object $plot = new svgGraph2(); // Set file path $dir = dirname($filePath); // Make sure directory exists if( !file_exists($dir) && !wfMkdirParents( $dir, 0777, __METHOD__ ) ) { throw new MWException( 'Could not create file directory!' ); } // Set some parameters $plot->graphicWidth = 1000; $plot->graphicHeight = 410; $plot->plotWidth = 930; $plot->plotHeight = 350; $plot->decimalPlacesY = 1; $plot->plotOffsetX = 40; $plot->plotOffsetY = 30; $plot->numGridlinesY = 10 + 1; $plot->innerPaddingX = 15; $plot->innerPaddingY = 10; $plot->outerPadding = 5; $plot->minY = 0; $plot->maxY = 5; // Define the data using the DB rows $dataX = $dave = $rave = $dcount = array(); $totalVal = $totalCount = $sd = $pts = $n = 0; // Define the data using the DB rows list($res,$u,$maxC,$days) = $this->doQuery( $tag ); if( !$maxC ) return false; // Label spacing $int = intval( ceil($days/10) ); // 10 labels at most foreach ( $res as $row ) { $totalVal += (int)$row->rfh_total; $totalCount += (int)$row->rfh_count; $dayCount = (real)$row->rfh_count; if( !$row->rfh_count ) continue; // bad data // Nudge values up by 1 to fit [1,5] $dayAve = 1 + (real)$row->rfh_total/(real)$row->rfh_count; $sd += pow($dayAve - $u,2); $cumAve = 1 + (real)$totalVal/(real)$totalCount; $year = intval( substr( $row->rfh_date, 0, 4 ) ); $month = intval( substr( $row->rfh_date, 4, 2 ) ); $day = intval( substr( $row->rfh_date, 6, 2 ) ); # Fill in days with no votes to keep spacing even if( isset($lastDate) ) { $dayGap = wfTimestamp(TS_UNIX,$row->rfh_date) - wfTimestamp(TS_UNIX,$lastDate); $x = intval( $dayGap/86400 ); # Day gaps... for( $x; $x > 1; --$x ) { $dataX[] = ""; $dave[] = $lastDAve; $rave[] = $lastRAve; $dcount[] = 0; $n++; } } $n++; # Label point? if( $n >= $int || !count($dataX) ) { $p = ($days > 31) ? "{$month}-".substr( $year, 2, 2 ) : "{$month}/{$day}"; $n = 0; } else { $p = ""; } $dataX[] = $p; $dave[] = $dayAve; $rave[] = $cumAve; $dcount[] = $dayCount; $lastDate = $row->rfh_date; $lastDAve = $dayAve; $lastRAve = $cumAve; $pts++; } // Minimum sample size if( $pts < 2 ) { return false; } $sd = sqrt($sd/$pts); // Round values for display $sd = round( $sd, 3 ); $u = round( $u, 3 ); // Re-scale voter count to fit to graph $this->dScale = ceil($maxC/5); // Cache the scale value to memory $key = wfMemcKey( 'feedback', 'scale', $this->page->getArticleId(), $this->period ); $wgMemc->set( $key, $this->dScale, 7*24*3600 ); // Fit to [0,4] foreach( $dcount as $x => $c ) { $dcount[$x] = $c/$this->dScale; } $plot->dataX = $dataX; $plot->dataY['dave'] = $dave; $plot->dataY['rave'] = $rave; $plot->dataY['dcount'] = $dcount; $plot->styleTagsX = 'font-family: monospace; font-size: 9pt;'; $plot->styleTagsY = 'font-family: sans-serif; font-size: 11pt;'; $plot->format['dave'] = array( 'style' => 'stroke:blue; stroke-width:1;' ); $plot->format['rave'] = array( 'style' => 'stroke:green; stroke-width:1;' ); $plot->format['dcount'] = array( 'style' => 'stroke:red; stroke-width:1;' ); #'attributes' => "marker-end='url(#circle)'"); $pageText = $wgContLang->truncate( $this->page->getPrefixedText(), 65 ); $plot->title = wfMsgExt('ratinghistory-graph',array('parsemag','content'), $totalCount, wfMsgForContent("readerfeedback-$tag"), $pageText ); $plot->styleTitle = 'font-family: sans-serif; font-weight: bold; font-size: 12pt;'; $plot->backgroundStyle = 'fill:#F0F0F0;'; // extra code for markers // FIXME: http://studio.imagemagick.org/pipermail/magick-bugs/2003-January/001038.html /* $plot->extraSVG = '<defs> <marker id="circle" style="stroke:red; stroke-width:0; fill:red;" viewBox="0 0 10 10" refX="5" refY="7" orient="0" markerUnits="strokeWidth" markerWidth="5" markerHeight="5"> <circle cx="5" cy="5" r="3"/> </marker> </defs>'; */ # Create the graph @$plot->init(); $plot->drawGraph(); if( !$plot->polyLine('dave') ) { throw new MWException( 'Could not generate "dave" line!' ); } if( !$plot->polyLine('rave') ) { throw new MWException( 'Could not generate "rave" line!' ); } if( !$plot->polyLine('dcount') ) { throw new MWException( 'Could not generate "dcount" line!' ); } #$plot->line('dcount'); // Render! $plot->generateSVG( "xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'" ); // Write to file for cache $svgPath = $this->getFilePath( $tag, 'svg' ); $svgHandler = new SvgHandler(); wfSuppressWarnings(); // FS notices possible if( !file_put_contents( $svgPath, $plot->svg ) ) { wfRestoreWarnings(); throw new MWException( 'Could not write SVG file!' ); } wfRestoreWarnings(); // Rasterize due to IE suckage $status = $svgHandler->rasterize( $svgPath, $filePath, 1000, 410 ); if( $status !== true ) { wfDebug( 'Could not rasterize SVG file from '.$filePath.'!' ); //return false; } return true; }