function wfCSSRender(&$parser, $css) { global $wgOut, $wgRequest; $parser->mOutput->mCacheTime = -1; $url = false; if (preg_match('|\\{|', $css)) { # Inline CSS $css = htmlspecialchars(trim(Sanitizer::checkCss($css))); $parser->mOutput->addHeadItem(<<<EOT <style type="text/css"> /*<![CDATA[*/ {$css} /*]]>*/ </style> EOT ); } elseif ($css[0] == '/') { # File $url = $css; } else { # Article? $title = Title::newFromText($css); if (is_object($title)) { $url = $title->getLocalURL('action=raw&ctype=text/css'); $url = str_replace("&", "&", $url); } } if ($url) { $wgOut->addScript("<link rel=\"stylesheet\" type=\"text/css\" href=\"{$url}\" />"); } return ''; }
public static function parse( $content, array $args, Parser $parser ) { $css = htmlspecialchars( trim( Sanitizer::checkCss( $content ) ) ); $parser->mOutput->addHeadItem( <<<EOT <style type="text/css"> /*<![CDATA[*/ {$css} /*]]>*/ </style> EOT ); }
/** * Override the title of the page when viewed, provided we've been given a * title which will normalise to the canonical title * * @param Parser $parser Parent parser * @param string $text Desired title text * @param string $uarg * @return string */ public static function displaytitle($parser, $text = '', $uarg = '') { global $wgRestrictDisplayTitle; static $magicWords = null; if (is_null($magicWords)) { $magicWords = new MagicWordArray(['displaytitle_noerror', 'displaytitle_noreplace']); } $arg = $magicWords->matchStartToEnd($uarg); // parse a limited subset of wiki markup (just the single quote items) $text = $parser->doQuotes($text); // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever $text = $parser->killMarkers($text); // list of disallowed tags for DISPLAYTITLE // these will be escaped even though they are allowed in normal wiki text $bad = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr', 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rtc', 'rp', 'br']; // disallow some styles that could be used to bypass $wgRestrictDisplayTitle if ($wgRestrictDisplayTitle) { $htmlTagsCallback = function (&$params) { $decoded = Sanitizer::decodeTagAttributes($params); if (isset($decoded['style'])) { // this is called later anyway, but we need it right now for the regexes below to be safe // calling it twice doesn't hurt $decoded['style'] = Sanitizer::checkCss($decoded['style']); if (preg_match('/(display|user-select|visibility)\\s*:/i', $decoded['style'])) { $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */'; } } $params = Sanitizer::safeEncodeTagAttributes($decoded); }; } else { $htmlTagsCallback = null; } // only requested titles that normalize to the actual title are allowed through // if $wgRestrictDisplayTitle is true (it is by default) // mimic the escaping process that occurs in OutputPage::setPageTitle $text = Sanitizer::normalizeCharReferences(Sanitizer::removeHTMLtags($text, $htmlTagsCallback, [], [], $bad)); $title = Title::newFromText(Sanitizer::stripAllTags($text)); if (!$wgRestrictDisplayTitle || $title instanceof Title && !$title->hasFragment() && $title->equals($parser->mTitle)) { $old = $parser->mOutput->getProperty('displaytitle'); if ($old === false || $arg !== 'displaytitle_noreplace') { $parser->mOutput->setDisplayTitle($text); } if ($old !== false && $old !== $text && !$arg) { $converter = $parser->getConverterLanguage()->getConverter(); return '<span class="error">' . wfMessage('duplicate-displaytitle', $converter->markNoConversion(wfEscapeWikiText($old)), $converter->markNoConversion(wfEscapeWikiText($text)))->inContentLanguage()->text() . '</span>'; } else { return ''; } } else { $parser->addTrackingCategory('restricted-displaytitle-ignored'); $converter = $parser->getConverterLanguage()->getConverter(); return '<span class="error">' . wfMessage('restricted-displaytitle', $converter->markNoConversion(wfEscapeWikiText($text)))->inContentLanguage()->text() . '</span>'; } }
/** * Take an array of attribute names and values and normalize or discard * illegal values for the given whitelist. * * - Discards attributes not the given whitelist * - Unsafe style attributes are discarded * - Invalid id attributes are re-encoded * * @param array $attribs * @param array $whitelist List of allowed attribute names * @return array * * @todo Check for legal values where the DTD limits things. * @todo Check for unique id attribute :P */ static function validateAttributes($attribs, $whitelist) { global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes; $whitelist = array_flip($whitelist); $hrefExp = '/^(' . wfUrlProtocols() . ')[^\\s]+$/'; $out = array(); foreach ($attribs as $attribute => $value) { #allow XML namespace declaration if RDFa is enabled if ($wgAllowRdfaAttributes && preg_match(self::XMLNS_ATTRIBUTE_PATTERN, $attribute)) { if (!preg_match(self::EVIL_URI_PATTERN, $value)) { $out[$attribute] = $value; } continue; } # Allow any attribute beginning with "data-" if (!preg_match('/^data-(?!ooui)/i', $attribute) && !isset($whitelist[$attribute])) { continue; } # Strip javascript "expression" from stylesheets. # http://msdn.microsoft.com/workshop/author/dhtml/overview/recalc.asp if ($attribute == 'style') { $value = Sanitizer::checkCss($value); } if ($attribute === 'id') { $value = Sanitizer::escapeId($value, 'noninitial'); } # WAI-ARIA # http://www.w3.org/TR/wai-aria/ # http://www.whatwg.org/html/elements.html#wai-aria # For now we only support role="presentation" until we work out what roles should be # usable by content and we ensure that our code explicitly rejects patterns that # violate HTML5's ARIA restrictions. if ($attribute === 'role' && $value !== 'presentation') { continue; } // RDFa and microdata properties allow URLs, URIs and/or CURIs. // Check them for sanity. if ($attribute === 'rel' || $attribute === 'rev' || $attribute === 'about' || $attribute === 'property' || $attribute === 'resource' || $attribute === 'datatype' || $attribute === 'typeof' || $attribute === 'itemid' || $attribute === 'itemprop' || $attribute === 'itemref' || $attribute === 'itemscope' || $attribute === 'itemtype') { //Paranoia. Allow "simple" values but suppress javascript if (preg_match(self::EVIL_URI_PATTERN, $value)) { continue; } } # NOTE: even though elements using href/src are not allowed directly, supply # validation code that can be used by tag hook handlers, etc if ($attribute === 'href' || $attribute === 'src') { if (!preg_match($hrefExp, $value)) { continue; //drop any href or src attributes not using an allowed protocol. // NOTE: this also drops all relative URLs } } // If this attribute was previously set, override it. // Output should only have one attribute of each name. $out[$attribute] = $value; } if ($wgAllowMicrodataAttributes) { # itemtype, itemid, itemref don't make sense without itemscope if (!array_key_exists('itemscope', $out)) { unset($out['itemtype']); unset($out['itemid']); unset($out['itemref']); } # TODO: Strip itemprop if we aren't descendants of an itemscope or pointed to by an itemref. } return $out; }
/** * Take an array of attribute names and values and normalize or discard * illegal values for the given whitelist. * * - Discards attributes not the given whitelist * - Unsafe style attributes are discarded * - Invalid id attributes are reencoded * * @param array $attribs * @param array $whitelist list of allowed attribute names * @return array * * @todo Check for legal values where the DTD limits things. * @todo Check for unique id attribute :P */ static function validateAttributes($attribs, $whitelist) { $whitelist = array_flip($whitelist); $out = array(); foreach ($attribs as $attribute => $value) { if (!isset($whitelist[$attribute])) { continue; } # Strip javascript "expression" from stylesheets. # http://msdn.microsoft.com/workshop/author/dhtml/overview/recalc.asp if ($attribute == 'style') { $value = Sanitizer::checkCss($value); if ($value === false) { # haxx0r continue; } } if ($attribute === 'id') { $value = Sanitizer::escapeId($value); } // If this attribute was previously set, override it. // Output should only have one attribute of each name. $out[$attribute] = $value; } return $out; }
/** * Take an array of attribute names and values and normalize or discard * illegal values for the given whitelist. * * - Discards attributes not the given whitelist * - Unsafe style attributes are discarded * - Invalid id attributes are reencoded * * @param $attribs Array * @param $whitelist Array: list of allowed attribute names * @return Array * * @todo Check for legal values where the DTD limits things. * @todo Check for unique id attribute :P */ static function validateAttributes($attribs, $whitelist) { global $wgAllowRdfaAttributes, $wgAllowMicrodataAttributes; $whitelist = array_flip($whitelist); $hrefExp = '/^(' . wfUrlProtocols() . ')[^\\s]+$/'; $out = array(); foreach ($attribs as $attribute => $value) { #allow XML namespace declaration if RDFa is enabled if ($wgAllowRdfaAttributes && preg_match(MW_XMLNS_ATTRIBUTE_PATTRN, $attribute)) { if (!preg_match(MW_EVIL_URI_PATTERN, $value)) { $out[$attribute] = $value; } continue; } if (!isset($whitelist[$attribute])) { continue; } # Strip javascript "expression" from stylesheets. # http://msdn.microsoft.com/workshop/author/dhtml/overview/recalc.asp if ($attribute == 'style') { $value = Sanitizer::checkCss($value); } if ($attribute === 'id') { $value = Sanitizer::escapeId($value, 'noninitial'); } //RDFa and microdata properties allow URLs, URIs and/or CURIs. check them for sanity if ($attribute === 'rel' || $attribute === 'rev' || $attribute === 'about' || $attribute === 'property' || $attribute === 'resource' || $attribute === 'datatype' || $attribute === 'typeof' || $attribute === 'itemid' || $attribute === 'itemprop' || $attribute === 'itemref' || $attribute === 'itemscope' || $attribute === 'itemtype') { #HTML5 microdata //Paranoia. Allow "simple" values but suppress javascript if (preg_match(MW_EVIL_URI_PATTERN, $value)) { continue; } } # NOTE: even though elements using href/src are not allowed directly, supply # validation code that can be used by tag hook handlers, etc if ($attribute === 'href' || $attribute === 'src') { if (!preg_match($hrefExp, $value)) { continue; //drop any href or src attributes not using an allowed protocol. //NOTE: this also drops all relative URLs } } // If this attribute was previously set, override it. // Output should only have one attribute of each name. $out[$attribute] = $value; } if ($wgAllowMicrodataAttributes) { # There are some complicated validity constraints we need to # enforce here. First of all, we don't want to allow non-standard # itemtypes. $allowedTypes = array('http://microformats.org/profile/hcard', 'http://microformats.org/profile/hcalendar#vevent', 'http://n.whatwg.org/work'); if (isset($out['itemtype']) && !in_array($out['itemtype'], $allowedTypes)) { # Kill everything unset($out['itemscope']); } # itemtype, itemid, itemref don't make sense without itemscope if (!array_key_exists('itemscope', $out)) { unset($out['itemtype']); unset($out['itemid']); unset($out['itemref']); } # TODO: Strip itemprop if we aren't descendants of an itemscope. } return $out; }
/** * @author Maciej Błaszkowski <marooned at wikia-inc.com> * TODO: because of late change in specs, this function has to handle parameters in different formats and thus it's little confused - would be good to clean up the code someday */ static function parseParameters($parametersIn) { global $wgContLang; wfProfileIn(__METHOD__); $excludeNamespaces = array(); $parameters = array(); //default values $parameters['maxElements'] = 10; $parameters['flags'] = array(); $parameters['includeNamespaces'] = null; if (is_array($parametersIn) && !empty($parametersIn)) { foreach ($parametersIn as $parameter => $value) { if (is_int($parameter)) { //ajax @(list($var, $val) = explode('=', $value)); } else { //parser tag $var = $parameter; $val = $value; } switch (trim($var)) { case 'size': if (!empty($val)) { $parameters['maxElements'] = intval($val); } break; case 'hideimages': if (!isset($val) || $val != 'false') { $parameters['flags'][] = 'hideimages'; } break; case 'hidevideos': if (!isset($val) || $val != 'false') { $parameters['flags'][] = 'hidevideos'; } break; case 'hidedetails': if (!isset($val) || $val != 'false') { $parameters['flags'][] = 'hidedetails'; } break; case 'hidecategories': if (!isset($val) || $val != 'false') { $parameters['flags'][] = 'hidecategories'; } break; case 'shortlist': if (!isset($val) || $val != 'false') { $parameters['flags'][] = 'shortlist'; } break; case 'hidenewpages': if (!isset($val) || $val != 'false') { $parameters['flags'][] = 'hidenewpages'; } break; case 'exclude': //only from tag if (!empty($val)) { $namespaces = explode(',', $val); $blankNamespace = wfMsgForContent('blanknamespace'); $blankNamespace = wfEmptyMsg('blanknamespace', $blankNamespace) ? null : $wgContLang->lc($blankNamespace); foreach ($namespaces as $namespace) { $namespace = trim($namespace); if (($namespaceIndex = $wgContLang->getNsIndex($namespace)) !== false && $namespaceIndex >= 0) { $excludeNamespaces[] = $namespaceIndex; } else { //check for main namespace which doesn't have formal name - try hardcoded 'main' and 'blanknamespace' message used in many places $namespaceLC = $wgContLang->lc($namespace); if ($namespaceLC == 'main' || $namespaceLC == $blankNamespace) { $excludeNamespaces[] = 0; } } } if (count($excludeNamespaces)) { global $wgCanonicalNamespaceNames; $allNS[0] = 'Main'; //hack to add main namespace $allNS += $wgCanonicalNamespaceNames; unset($allNS[-1]); unset($allNS[-2]); $parameters['includeNamespaces'] = implode('|', array_diff(array_keys($allNS), $excludeNamespaces)); } } break; case 'style': //only from tag if (!empty($val)) { $style = Sanitizer::checkCss($val); if ($style) { $parameters['style'] = $style; } } break; case 'uselang': //only from ajax if (!empty($val)) { $parameters['uselang'] = $val; } break; case 'ns': //only from ajax if (!empty($val)) { $parameters['includeNamespaces'] = $val; } break; case 'flags': //only from ajax if (!empty($val)) { $flags = explode('|', $val); if (in_array('hideimages', $flags)) { $parameters['flags'][] = 'hideimages'; } if (in_array('hidedetails', $flags)) { $parameters['flags'][] = 'hidedetails'; } if (in_array('hidevideos', $flags)) { $parameters['flags'][] = 'hidevideos'; } if (in_array('hidecategories', $flags)) { $parameters['flags'][] = 'hidecategories'; } if (in_array('shortlist', $flags)) { $parameters['flags'][] = 'shortlist'; } if (in_array('hidenewpages', $flags)) { $parameters['flags'][] = 'hidenewpages'; } } break; case 'tagid': //only from ajax if (!empty($val)) { $parameters['tagid'] = $val; } break; case 'type': //pick proper template for tag or widget if (!empty($val)) { $parameters['type'] = $val; } break; } } if (in_array('shortlist', $parameters['flags'])) { global $wgContentNamespaces; $parameters['includeNamespaces'] = implode('|', array_diff($wgContentNamespaces, $excludeNamespaces)); } } wfProfileOut(__METHOD__); return $parameters; }
/** * Take an array of attribute names and values and normalize or discard * illegal values for the given whitelist. * * - Discards attributes not on the given whitelist * - Unsafe style attributes are discarded * - Invalid id attributes are re-encoded * * @param array $attribs * @param array $whitelist List of allowed attribute names * @return array * * @todo Check for legal values where the DTD limits things. * @todo Check for unique id attribute :P */ static function validateAttributes($attribs, $whitelist) { $whitelist = array_flip($whitelist); $hrefExp = '/^(' . wfUrlProtocols() . ')[^\\s]+$/'; $out = []; foreach ($attribs as $attribute => $value) { # Allow XML namespace declaration to allow RDFa if (preg_match(self::XMLNS_ATTRIBUTE_PATTERN, $attribute)) { if (!preg_match(self::EVIL_URI_PATTERN, $value)) { $out[$attribute] = $value; } continue; } # Allow any attribute beginning with "data-" # However: # * data-ooui is reserved for ooui # * data-mw and data-parsoid are reserved for parsoid # * data-mw-<name here> is reserved for extensions (or core) if # they need to communicate some data to the client and want to be # sure that it isn't coming from an untrusted user. # * Ensure that the attribute is not namespaced by banning # colons. if (!preg_match('/^data-(?!ooui|mw|parsoid)[^:]*$/i', $attribute) && !isset($whitelist[$attribute])) { continue; } # Strip javascript "expression" from stylesheets. # http://msdn.microsoft.com/workshop/author/dhtml/overview/recalc.asp if ($attribute == 'style') { $value = Sanitizer::checkCss($value); } # Escape HTML id attributes if ($attribute === 'id') { $value = Sanitizer::escapeId($value, 'noninitial'); } # Escape HTML id reference lists if ($attribute === 'aria-describedby' || $attribute === 'aria-flowto' || $attribute === 'aria-labelledby' || $attribute === 'aria-owns') { $value = Sanitizer::escapeIdReferenceList($value, 'noninitial'); } // RDFa and microdata properties allow URLs, URIs and/or CURIs. // Check them for sanity. if ($attribute === 'rel' || $attribute === 'rev' || $attribute === 'about' || $attribute === 'property' || $attribute === 'resource' || $attribute === 'datatype' || $attribute === 'typeof' || $attribute === 'itemid' || $attribute === 'itemprop' || $attribute === 'itemref' || $attribute === 'itemscope' || $attribute === 'itemtype') { // Paranoia. Allow "simple" values but suppress javascript if (preg_match(self::EVIL_URI_PATTERN, $value)) { continue; } } # NOTE: even though elements using href/src are not allowed directly, supply # validation code that can be used by tag hook handlers, etc if ($attribute === 'href' || $attribute === 'src') { if (!preg_match($hrefExp, $value)) { continue; // drop any href or src attributes not using an allowed protocol. // NOTE: this also drops all relative URLs } } // If this attribute was previously set, override it. // Output should only have one attribute of each name. $out[$attribute] = $value; } # itemtype, itemid, itemref don't make sense without itemscope if (!array_key_exists('itemscope', $out)) { unset($out['itemtype']); unset($out['itemid']); unset($out['itemref']); } # TODO: Strip itemprop if we aren't descendants of an itemscope or pointed to by an itemref. return $out; }
/** * Get the JS and HTML that needs to be added to the output to create the chart. * * @since 1.7 * * @param array $data label => value */ protected function getFormatOutput( array $data ) { $json = array(); foreach ( $data as $name => $value ) { $json[] = array( $name, $value ); } $pie_data_str = '[' . FormatJson::encode( $json ) . ']'; $pieID = 'pie' . self::$m_piechartnum; self::$m_piechartnum++; $chartlegend = FormatJson::encode( $this->params['chartlegend'] ); $legendlocation = FormatJson::encode( $this->params['legendlocation'] ); $datalabels = FormatJson::encode( $this->params['datalabels'] ); $datalabeltype = FormatJson::encode( $this->params['datalabeltype'] ); $js_pie =<<<END <script type="text/javascript"> jQuery(document).ready(function(){ jQuery.jqplot.config.enablePlugins = true; plot1 = jQuery.jqplot('$pieID', $pie_data_str, { title: '$this->m_charttitle', seriesDefaults: { renderer: jQuery.jqplot.PieRenderer, rendererOptions: { showDataLabels: $datalabels, dataLabels: $datalabeltype, sliceMargin:2 } }, legend: { show:$chartlegend, location: $legendlocation } }); }); </script> END; global $wgOut; $wgOut->addScript( $js_pie ); $this->isHTML = true; return Html::element( 'div', array( 'id' => $pieID, 'style' => Sanitizer::checkCss( "margin-top: 20px; margin-left: 20px; width: {$this->m_width}px; height: {$this->m_height}px;" ) ) ); }
/** * Get the JS and HTML that needs to be added to the output to create the chart. * * @since 1.7 * * @param array $data label => value */ protected function getFormatOutput( array $data ) { global $wgOut; $this->isHTML = true; $maxValue = count( $data ) == 0 ? 0 : max( $data ); if ( $this->params['min'] === false ) { $minValue = count( $data ) == 0 ? 0 : min( $data ); } else { $minValue = $this->params['min']; } foreach ( $data as $i => &$nr ) { if ( $this->m_bardirection == 'horizontal' ) { $nr = array( $nr, $i ); } } $barID = 'bar' . self::$m_barchartnum; self::$m_barchartnum++; $labels_str = FormatJson::encode( array_keys( $data ) ); $numbers_str = FormatJson::encode( array_values( $data ) ); $labels_axis = 'xaxis'; $numbers_axis = 'yaxis'; $angle_val = -40; $barmargin = 6; if ( $this->m_bardirection == 'horizontal' ) { $labels_axis = 'yaxis'; $numbers_axis = 'xaxis'; $angle_val = 0; $barmargin = 8 ; } $barwidth = 20; // width of each bar $bardistance = 4; // distance between two bars // Calculate the tick values for the numbers, based on the // lowest and highest number. jqPlot has its own option for // calculating ticks automatically - "autoscale" - but it // currently (September 2010) fails for numbers less than 1, // and negative numbers. // If both max and min are 0, just escape now. if ( $maxValue == 0 && $minValue == 0 ) { return null; } // Make the max and min slightly larger and bigger than the // actual max and min, so that the bars don't directly touch // the top and bottom of the graph if ( $maxValue > 0 ) { $maxValue += .001; } if ( $minValue < 0 ) { $minValue -= .001; } if ( $maxValue == 0 ) { $multipleOf10 = 0; $maxAxis = 0; } else { $multipleOf10 = pow( 10, floor( log( $maxValue, 10 ) ) ); $maxAxis = ceil( $maxValue / $multipleOf10 ) * $multipleOf10; } if ( $minValue == 0 ) { $negativeMultipleOf10 = 0; $minAxis = 0; } else { $negativeMultipleOf10 = -1 * pow( 10, floor( log( $minValue, 10 ) ) ); $minAxis = ceil( $minValue / $negativeMultipleOf10 ) * $negativeMultipleOf10; } $numbers_ticks = ''; $biggerMultipleOf10 = max( $multipleOf10, -1 * $negativeMultipleOf10 ); $lowestTick = floor( $minAxis / $biggerMultipleOf10 + .001 ); $highestTick = ceil( $maxAxis / $biggerMultipleOf10 - .001 ); for ( $i = $lowestTick; $i <= $highestTick; $i++ ) { $numbers_ticks .= ($i * $biggerMultipleOf10) . ', '; } $pointlabels = FormatJson::encode( $this->params['pointlabels'] ); $js_bar =<<<END <script type="text/javascript"> jQuery(document).ready(function(){ jQuery.jqplot.config.enablePlugins = true; plot1 = jQuery.jqplot('$barID', [{$numbers_str}], { title: '{$this->m_charttitle}', seriesColors: ['$this->m_barcolor'], seriesDefaults: { fillToZero: true }, series: [ { renderer: jQuery.jqplot.BarRenderer, rendererOptions: { barDirection: '{$this->m_bardirection}', barPadding: 6, barMargin: $barmargin }, pointLabels: {show: $pointlabels} }], axes: { $labels_axis: { renderer: jQuery.jqplot.CategoryAxisRenderer, ticks: {$labels_str}, tickRenderer: jQuery.jqplot.CanvasAxisTickRenderer, tickOptions: { angle: $angle_val } }, $numbers_axis: { ticks: [$numbers_ticks], label: '{$this->m_numbersaxislabel}' } } }); }); </script> END; $wgOut->addScript( $js_bar ); $width = $this->params['width']; $height = $this->params['height']; return Html::element( 'div', array( 'id' => $barID, 'style' => Sanitizer::checkCss( "margin-top: 20px; margin-left: 20px; width: {$width}px; height: {$height}px;" ) ) ); }
/** * @dataProvider provideCssCommentsFixtures * @covers Sanitizer::checkCss */ function testCssCommentsChecking($expected, $css, $message = '') { $this->assertEquals($expected, Sanitizer::checkCss($css), $message); }
/** * Get the JS and HTML that needs to be added to the output to create the chart. * * @since 1.7 * * @param array $data label => value */ protected function getFormatOutput( array $data ) { global $wgOut; $this->isHTML = true; $maxValue = count( $data ) == 0 ? 0 : max( $data ); if ( $this->params['min'] === false ) { $minValue = count( $data ) == 0 ? 0 : min( $data ); } else { $minValue = $this->params['min']; } foreach ( $data as $i => &$nr ) { if ( $this->params['bardirection'] == 'horizontal' ) { $nr = array( $nr, $i ); } } $treemapID = 'treemap' . self::$m_barchartnum; self::$m_barchartnum++; $labels_str = FormatJson::encode( array_keys( $data ) ); $numbers_str = FormatJson::encode( array_values( $data ) ); $labels_axis = 'xaxis'; $numbers_axis = 'yaxis'; $angle_val = -40; $barmargin = 6; if ( $this->params['bardirection'] == 'horizontal' ) { $labels_axis = 'yaxis'; $numbers_axis = 'xaxis'; $angle_val = 0; $barmargin = 8 ; } $barwidth = 20; // width of each bar $bardistance = 4; // distance between two bars // Calculate the tick values for the numbers, based on the // lowest and highest number. jqPlot has its own option for // calculating ticks automatically - "autoscale" - but it // currently (September 2010) fails for numbers less than 1, // and negative numbers. // If both max and min are 0, just escape now. if ( $maxValue == 0 && $minValue == 0 ) { return null; } // Make the max and min slightly larger and bigger than the // actual max and min, so that the bars don't directly touch // the top and bottom of the graph if ( $maxValue > 0 ) { $maxValue += .001; } if ( $minValue < 0 ) { $minValue -= .001; } if ( $maxValue == 0 ) { $multipleOf10 = 0; $maxAxis = 0; } else { $multipleOf10 = pow( 10, floor( log( $maxValue, 10 ) ) ); $maxAxis = ceil( $maxValue / $multipleOf10 ) * $multipleOf10; } if ( $minValue == 0 ) { $negativeMultipleOf10 = 0; $minAxis = 0; } else { $negativeMultipleOf10 = -1 * pow( 10, floor( log( $minValue, 10 ) ) ); $minAxis = ceil( $minValue / $negativeMultipleOf10 ) * $negativeMultipleOf10; } $numbers_ticks = ''; $biggerMultipleOf10 = max( $multipleOf10, -1 * $negativeMultipleOf10 ); $lowestTick = floor( $minAxis / $biggerMultipleOf10 + .001 ); $highestTick = ceil( $maxAxis / $biggerMultipleOf10 - .001 ); for ( $i = $lowestTick; $i <= $highestTick; $i++ ) { $numbers_ticks .= ($i * $biggerMultipleOf10) . ', '; } # $pointlabels = FormatJson::encode( $this->params['pointlabels'] ); $width = $this->params['width']; $height = $this->params['height']; $js_treemap =<<<END <script type="text/javascript"> $(document).ready(function() { //http://dealloc.me/2011/06/24/d3-is-not-a-graphing-library.html var data, h, max, pb, pl, pr, pt, ticks, version, vis, w, x, y, _ref; version = Number(document.location.hash.replace('#', '')); data = {$numbers_str}; _ref = [20, 20, 20, 20], pt = _ref[0], pl = _ref[1], pr = _ref[2], pb = _ref[3]; w = $width - (pl + pr); h = $height - (pt + pb); max = d3.max(data); x = d3.scale.linear().domain([0, data.length - 1]).range([0, w]); y = d3.scale.linear().domain([0, max]).range([h, 0]); vis = d3.select('#$treemapID').style('margin', '20px auto').style('width', "" + w + "px").append('svg:svg').attr('width', w + (pl + pr)).attr('height', h + pt + pb).attr('class', 'viz').append('svg:g').attr('transform', "translate(" + pl + "," + pt + ")"); vis.selectAll('path.line').data([data]).enter().append("svg:path").attr("d", d3.svg.line().x(function(d, i) { return x(i); }).y(y)); if (version < 2 && version !== 0) { return; } ticks = vis.selectAll('.ticky').data(y.ticks(7)).enter().append('svg:g').attr('transform', function(d) { return "translate(0, " + (y(d)) + ")"; }).attr('class', 'ticky'); ticks.append('svg:line').attr('y1', 0).attr('y2', 0).attr('x1', 0).attr('x2', w); ticks.append('svg:text').text(function(d) { return d; }).attr('text-anchor', 'end').attr('dy', 2).attr('dx', -4); ticks = vis.selectAll('.tickx').data(x.ticks(data.length)).enter().append('svg:g').attr('transform', function(d, i) { return "translate(" + (x(i)) + ", 0)"; }).attr('class', 'tickx'); ticks.append('svg:line').attr('y1', h).attr('y2', 0).attr('x1', 0).attr('x2', 0); ticks.append('svg:text').text(function(d, i) { return i; }).attr('y', h).attr('dy', 15).attr('dx', -2); if (version < 3 && version !== 0) { return; } return vis.selectAll('.point').data(data).enter().append("svg:circle").attr("class", function(d, i) { if (d === max) { return 'point max'; } else { return 'point'; } }).attr("r", function(d, i) { if (d === max) { return 6; } else { return 4; } }).attr("cx", function(d, i) { return x(i); }).attr("cy", function(d) { return y(d); }).on('mouseover', function() { return d3.select(this).attr('r', 8); }).on('mouseout', function() { return d3.select(this).attr('r', 4); }).on('click', function(d, i) { return console.log(d, i); }); }); </script> END; $wgOut->addScript( $js_treemap ); return Html::element( 'div', array( 'id' => $treemapID, 'style' => Sanitizer::checkCss( "margin-top: 20px; margin-left: 20px; margin-right: 20px; width: {$width}px; height: {$height}px;" ) ) ); }
/** * Override the title of the page when viewed, provided we've been given a * title which will normalise to the canonical title * * @param $parser Parser: parent parser * @param string $text desired title text * @return String */ static function displaytitle($parser, $text = '') { global $wgRestrictDisplayTitle; // parse a limited subset of wiki markup (just the single quote items) $text = $parser->doQuotes($text); // remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever $text = preg_replace('/' . preg_quote($parser->uniqPrefix(), '/') . '.*?' . preg_quote(Parser::MARKER_SUFFIX, '/') . '/', '', $text); // list of disallowed tags for DISPLAYTITLE // these will be escaped even though they are allowed in normal wiki text $bad = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'blockquote', 'ol', 'ul', 'li', 'hr', 'table', 'tr', 'th', 'td', 'dl', 'dd', 'caption', 'p', 'ruby', 'rb', 'rt', 'rp', 'br'); // disallow some styles that could be used to bypass $wgRestrictDisplayTitle if ($wgRestrictDisplayTitle) { $htmlTagsCallback = function (&$params) { $decoded = Sanitizer::decodeTagAttributes($params); if (isset($decoded['style'])) { // this is called later anyway, but we need it right now for the regexes below to be safe // calling it twice doesn't hurt $decoded['style'] = Sanitizer::checkCss($decoded['style']); if (preg_match('/(display|user-select|visibility)\\s*:/i', $decoded['style'])) { $decoded['style'] = '/* attempt to bypass $wgRestrictDisplayTitle */'; } } $params = Sanitizer::safeEncodeTagAttributes($decoded); }; } else { $htmlTagsCallback = null; } // only requested titles that normalize to the actual title are allowed through // if $wgRestrictDisplayTitle is true (it is by default) // mimic the escaping process that occurs in OutputPage::setPageTitle $text = Sanitizer::normalizeCharReferences(Sanitizer::removeHTMLtags($text, $htmlTagsCallback, array(), array(), $bad)); $title = Title::newFromText(Sanitizer::stripAllTags($text)); if (!$wgRestrictDisplayTitle) { $parser->mOutput->setDisplayTitle($text); } elseif ($title instanceof Title && !$title->hasFragment() && $title->equals($parser->mTitle)) { $parser->mOutput->setDisplayTitle($text); } return ''; }
function wfCSSRawPageViewBeforeOutput(&$rawPage, &$text) { global $wgCSSIdentifier; if ($rawPage->getRequest()->getBool($wgCSSIdentifier)) { $text = Sanitizer::checkCss($text); } return true; }
/** * Get the JS and HTML that needs to be added to the output to create the chart. * * @since 1.7 * * @param array $data label => value */ protected function getFormatOutput( array $data ) { global $wgOut; $this->isHTML = true; $maxValue = count( $data ) == 0 ? 0 : max( $data ); if ( $this->params['min'] === false ) { $minValue = count( $data ) == 0 ? 0 : min( $data ); } else { $minValue = $this->params['min']; } $barID = 'bar' . self::$m_barchartnum; self::$m_barchartnum++; $labels_str = FormatJson::encode( array_keys( $data ) ); $numbers_str = FormatJson::encode( array_values( $data ) ); $labels_axis = 'xaxis'; $numbers_axis = 'yaxis'; $angle_val = -40; $barmargin = 6; $barwidth = 20; // width of each bar $bardistance = 4; // distance between two bars // Calculate the tick values for the numbers, based on the // lowest and highest number. jqPlot has its own option for // calculating ticks automatically - "autoscale" - but it // currently (September 2010) fails for numbers less than 1, // and negative numbers. // If both max and min are 0, just escape now. if ( $maxValue == 0 && $minValue == 0 ) { return null; } // Make the max and min slightly larger and bigger than the // actual max and min, so that the bars don't directly touch // the top and bottom of the graph if ( $maxValue > 0 ) { $maxValue += .001; } if ( $minValue < 0 ) { $minValue -= .001; } if ( $maxValue == 0 ) { $multipleOf10 = 0; $maxAxis = 0; } else { $multipleOf10 = pow( 10, floor( log( $maxValue, 10 ) ) ); $maxAxis = ceil( $maxValue / $multipleOf10 ) * $multipleOf10; } if ( $minValue == 0 ) { $negativeMultipleOf10 = 0; $minAxis = 0; } else { $negativeMultipleOf10 = -1 * pow( 10, floor( log( $minValue, 10 ) ) ); $minAxis = ceil( $minValue / $negativeMultipleOf10 ) * $negativeMultipleOf10; } $numbers_ticks = ''; $biggerMultipleOf10 = max( $multipleOf10, -1 * $negativeMultipleOf10 ); $lowestTick = floor( $minAxis / $biggerMultipleOf10 + .001 ); $highestTick = ceil( $maxAxis / $biggerMultipleOf10 - .001 ); for ( $i = $lowestTick; $i <= $highestTick; $i++ ) { $numbers_ticks .= ($i * $biggerMultipleOf10) . ', '; } # $pointlabels = FormatJson::encode( $this->params['pointlabels'] ); $width = $this->params['width']; $height = $this->params['height']; $charttitle = $this->patams['charttitle']; $js_bar =<<<END <script type="text/javascript"> $(document).ready(function() { //Examples based on http://www.verisi.com/resources/d3-tutorial-basic-charts.htm //Alternating to form a single series. Bar Color will switch back & forth //var data = d3.range(10).map(Math.random); var data = {$numbers_str}; var colorlist = ["steelblue", "lightblue"]; var labellist = ($labels_str); var w = $width, h = $height - 20 , labelpad = $width / 3, barwidth = 20, x = d3.scale.linear().domain([0, 100]).range([0, w]), y = d3.scale.ordinal().domain(d3.range(data.length)).rangeBands([0, h], .2); var vis = d3.select("#$barID") .append("svg:svg") .attr("width", $width - $barwidth ) .attr("height", h + 20) .append("svg:g") .attr("transform", "translate(20,0)") .attr("class", "chart"); var bars = vis.selectAll("g.bar") .data(data) .enter().append("svg:g") .attr("class", "bar") .attr("transform", function(d, i) { return "translate(" + labelpad + "," + y(i) + ")"; }); bars.append("svg:rect") .attr("fill", function(d, i) { return colorlist[i % 2]; } ) //Alternate colors .attr("width", x ) .attr("height", y.rangeBand()) .text(function(d) { return d; }); bars.append("svg:text") .attr("x", 0) .attr("y", -2 + y.rangeBand() / 2) .attr("dx", -16) .attr("dy", ".55em") .attr("class", "barlabel") .attr("text-anchor", "end") .text(function(d, i) { return labellist[i]; }); //Generate labels for each bar var labels = vis.selectAll("g.bar") .append("svg:text") .attr("class", "barvalue") .attr("x", 3) // .attr("x", function(d) { return x(d) + 2; }) .attr("y", -5 + y.rangeBand() / 2 + 10 ) .attr("text-anchor", "right") .attr("transform", function(d) { return "translate(" + x(d) + ", 0)"; }) .style("width", function(d) { return d * 10 + "px"; }) .text(function(d) { return d; }); //END Generate labels for each bar var rules = vis.selectAll("g.rule") .data(x.ticks(10)) .enter().append("svg:g") .attr("class", "rule") .attr("transform", function(d) { return "translate(" + x(d) + ", 0)"; }); // --------------------------------------- // Add Title, then Legend // --------------------------------------- vis.append("svg:text") .attr("x", 0) .attr("y", 25 ) .attr("class", "chartitle") .text('{$charttitle}'); rules.append("svg:line") .attr("y1", h) .attr("y2", h + 6) .attr("x1", labelpad) .attr("x2", labelpad) .attr("stroke", "black"); rules.append("svg:line") .attr("y1", 0) .attr("y2", h) .attr("x1", labelpad) .attr("x2", labelpad) .attr("stroke", "white") .attr("stroke-opacity", .3); rules.append("svg:text") .attr("y", h + 2 ) .attr("x", labelpad) .attr("dy", ".71em") .attr("text-anchor", "middle") .text(x.tickFormat(10)); }); </script> END; $wgOut->addScript( $js_bar ); return Html::element( 'div', array( 'id' => $barID, 'style' => Sanitizer::checkCss( "margin-top: 20px; margin-left: 20px; margin-right: 20px; width: {$width}px; height: {$height}px;" ) ) ); }