From: Timo Tijhof Date: Tue, 15 Jul 2014 20:58:51 +0000 (+0200) Subject: Rename MWNamespace, MWDebug and MWTidy files to match their class X-Git-Tag: 1.31.0-rc.0~14935 X-Git-Url: https://git.cyclocoop.org/%27.WWW_URL.%27admin/config/%7B%24admin_url%7D?a=commitdiff_plain;h=8ad5719e2cda021ac7f1477d2e058f0383175d7b;p=lhc%2Fweb%2Fwiklou.git Rename MWNamespace, MWDebug and MWTidy files to match their class Change-Id: I3e6d13ce366861c865401dde272bc2834a1de670 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index ff016744e7..8b4895a174 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -136,7 +136,7 @@ $wgAutoloadLocalClasses = array( 'MimeMagic' => 'includes/MimeMagic.php', 'MWHookException' => 'includes/Hooks.php', 'MWHttpRequest' => 'includes/HttpFunctions.php', - 'MWNamespace' => 'includes/Namespace.php', + 'MWNamespace' => 'includes/MWNamespace.php', 'OutputPage' => 'includes/OutputPage.php', 'Pager' => 'includes/Pager.php', 'PasswordError' => 'includes/User.php', @@ -463,7 +463,7 @@ $wgAutoloadLocalClasses = array( 'SQLiteField' => 'includes/db/DatabaseSqlite.php', # includes/debug - 'MWDebug' => 'includes/debug/Debug.php', + 'MWDebug' => 'includes/debug/MWDebug.php', # includes/deferred 'DataUpdate' => 'includes/deferred/DataUpdate.php', @@ -798,8 +798,8 @@ $wgAutoloadLocalClasses = array( 'CoreTagHooks' => 'includes/parser/CoreTagHooks.php', 'DateFormatter' => 'includes/parser/DateFormatter.php', 'LinkHolderArray' => 'includes/parser/LinkHolderArray.php', - 'MWTidy' => 'includes/parser/Tidy.php', - 'MWTidyWrapper' => 'includes/parser/Tidy.php', + 'MWTidy' => 'includes/parser/MWTidy.php', + 'MWTidyWrapper' => 'includes/parser/MWTidy.php', 'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', 'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', 'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php', diff --git a/includes/MWNamespace.php b/includes/MWNamespace.php new file mode 100644 index 0000000000..392f5582f8 --- /dev/null +++ b/includes/MWNamespace.php @@ -0,0 +1,496 @@ + NS_MAIN + && $index % 2; + } + + /** + * Get the talk namespace index for a given namespace + * + * @param int $index Namespace index + * @return int + */ + public static function getTalk( $index ) { + self::isMethodValidFor( $index, __METHOD__ ); + return self::isTalk( $index ) + ? $index + : $index + 1; + } + + /** + * Get the subject namespace index for a given namespace + * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject. + * + * @param int $index Namespace index + * @return int + */ + public static function getSubject( $index ) { + # Handle special namespaces + if ( $index < NS_MAIN ) { + return $index; + } + + return self::isTalk( $index ) + ? $index - 1 + : $index; + } + + /** + * Get the associated namespace. + * For talk namespaces, returns the subject (non-talk) namespace + * For subject (non-talk) namespaces, returns the talk namespace + * + * @param int $index Namespace index + * @return int|null If no associated namespace could be found + */ + public static function getAssociated( $index ) { + self::isMethodValidFor( $index, __METHOD__ ); + + if ( self::isSubject( $index ) ) { + return self::getTalk( $index ); + } elseif ( self::isTalk( $index ) ) { + return self::getSubject( $index ); + } else { + return null; + } + } + + /** + * Returns whether the specified namespace exists + * + * @param int $index + * + * @return bool + * @since 1.19 + */ + public static function exists( $index ) { + $nslist = self::getCanonicalNamespaces(); + return isset( $nslist[$index] ); + } + + /** + * Returns whether the specified namespaces are the same namespace + * + * @note It's possible that in the future we may start using something + * other than just namespace indexes. Under that circumstance making use + * of this function rather than directly doing comparison will make + * sure that code will not potentially break. + * + * @param int $ns1 The first namespace index + * @param int $ns2 The second namespace index + * + * @return bool + * @since 1.19 + */ + public static function equals( $ns1, $ns2 ) { + return $ns1 == $ns2; + } + + /** + * Returns whether the specified namespaces share the same subject. + * eg: NS_USER and NS_USER wil return true, as well + * NS_USER and NS_USER_TALK will return true. + * + * @param int $ns1 The first namespace index + * @param int $ns2 The second namespace index + * + * @return bool + * @since 1.19 + */ + public static function subjectEquals( $ns1, $ns2 ) { + return self::getSubject( $ns1 ) == self::getSubject( $ns2 ); + } + + /** + * Returns array of all defined namespaces with their canonical + * (English) names. + * + * @param bool $rebuild Rebuild namespace list (default = false). Used for testing. + * + * @return array + * @since 1.17 + */ + public static function getCanonicalNamespaces( $rebuild = false ) { + static $namespaces = null; + if ( $namespaces === null || $rebuild ) { + global $wgExtraNamespaces, $wgCanonicalNamespaceNames; + $namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames; + if ( is_array( $wgExtraNamespaces ) ) { + $namespaces += $wgExtraNamespaces; + } + wfRunHooks( 'CanonicalNamespaces', array( &$namespaces ) ); + } + return $namespaces; + } + + /** + * Returns the canonical (English) name for a given index + * + * @param int $index Namespace index + * @return string|bool If no canonical definition. + */ + public static function getCanonicalName( $index ) { + $nslist = self::getCanonicalNamespaces(); + if ( isset( $nslist[$index] ) ) { + return $nslist[$index]; + } else { + return false; + } + } + + /** + * Returns the index for a given canonical name, or NULL + * The input *must* be converted to lower case first + * + * @param string $name Namespace name + * @return int + */ + public static function getCanonicalIndex( $name ) { + static $xNamespaces = false; + if ( $xNamespaces === false ) { + $xNamespaces = array(); + foreach ( self::getCanonicalNamespaces() as $i => $text ) { + $xNamespaces[strtolower( $text )] = $i; + } + } + if ( array_key_exists( $name, $xNamespaces ) ) { + return $xNamespaces[$name]; + } else { + return null; + } + } + + /** + * Returns an array of the namespaces (by integer id) that exist on the + * wiki. Used primarily by the api in help documentation. + * @return array + */ + public static function getValidNamespaces() { + static $mValidNamespaces = null; + + if ( is_null( $mValidNamespaces ) ) { + foreach ( array_keys( self::getCanonicalNamespaces() ) as $ns ) { + if ( $ns >= 0 ) { + $mValidNamespaces[] = $ns; + } + } + } + + return $mValidNamespaces; + } + + /** + * Can this namespace ever have a talk namespace? + * + * @param int $index Namespace index + * @return bool + */ + public static function canTalk( $index ) { + return $index >= NS_MAIN; + } + + /** + * Does this namespace contain content, for the purposes of calculating + * statistics, etc? + * + * @param int $index Index to check + * @return bool + */ + public static function isContent( $index ) { + global $wgContentNamespaces; + return $index == NS_MAIN || in_array( $index, $wgContentNamespaces ); + } + + /** + * Can pages in a namespace be watched? + * + * @param int $index + * @return bool + */ + public static function isWatchable( $index ) { + return $index >= NS_MAIN; + } + + /** + * Does the namespace allow subpages? + * + * @param int $index Index to check + * @return bool + */ + public static function hasSubpages( $index ) { + global $wgNamespacesWithSubpages; + return !empty( $wgNamespacesWithSubpages[$index] ); + } + + /** + * Get a list of all namespace indices which are considered to contain content + * @return array Array of namespace indices + */ + public static function getContentNamespaces() { + global $wgContentNamespaces; + if ( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === array() ) { + return array( NS_MAIN ); + } elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) { + // always force NS_MAIN to be part of array (to match the algorithm used by isContent) + return array_merge( array( NS_MAIN ), $wgContentNamespaces ); + } else { + return $wgContentNamespaces; + } + } + + /** + * List all namespace indices which are considered subject, aka not a talk + * or special namespace. See also MWNamespace::isSubject + * + * @return array Array of namespace indices + */ + public static function getSubjectNamespaces() { + return array_filter( + MWNamespace::getValidNamespaces(), + 'MWNamespace::isSubject' + ); + } + + /** + * List all namespace indices which are considered talks, aka not a subject + * or special namespace. See also MWNamespace::isTalk + * + * @return array Array of namespace indices + */ + public static function getTalkNamespaces() { + return array_filter( + MWNamespace::getValidNamespaces(), + 'MWNamespace::isTalk' + ); + } + + /** + * Is the namespace first-letter capitalized? + * + * @param int $index Index to check + * @return bool + */ + public static function isCapitalized( $index ) { + global $wgCapitalLinks, $wgCapitalLinkOverrides; + // Turn NS_MEDIA into NS_FILE + $index = $index === NS_MEDIA ? NS_FILE : $index; + + // Make sure to get the subject of our namespace + $index = self::getSubject( $index ); + + // Some namespaces are special and should always be upper case + if ( in_array( $index, self::$alwaysCapitalizedNamespaces ) ) { + return true; + } + if ( isset( $wgCapitalLinkOverrides[$index] ) ) { + // $wgCapitalLinkOverrides is explicitly set + return $wgCapitalLinkOverrides[$index]; + } + // Default to the global setting + return $wgCapitalLinks; + } + + /** + * Does the namespace (potentially) have different aliases for different + * genders. Not all languages make a distinction here. + * + * @since 1.18 + * @param int $index Index to check + * @return bool + */ + public static function hasGenderDistinction( $index ) { + return $index == NS_USER || $index == NS_USER_TALK; + } + + /** + * It is not possible to use pages from this namespace as template? + * + * @since 1.20 + * @param int $index Index to check + * @return bool + */ + public static function isNonincludable( $index ) { + global $wgNonincludableNamespaces; + return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces ); + } + + /** + * Get the default content model for a namespace + * This does not mean that all pages in that namespace have the model + * + * @since 1.21 + * @param int $index Index to check + * @return null|string Default model name for the given namespace, if set + */ + public static function getNamespaceContentModel( $index ) { + global $wgNamespaceContentModels; + return isset( $wgNamespaceContentModels[$index] ) + ? $wgNamespaceContentModels[$index] + : null; + } + + /** + * Determine which restriction levels it makes sense to use in a namespace, + * optionally filtered by a user's rights. + * + * @since 1.23 + * @param int $index Index to check + * @param User $user User to check + * @return array + */ + public static function getRestrictionLevels( $index, User $user = null ) { + global $wgNamespaceProtection, $wgRestrictionLevels; + + if ( !isset( $wgNamespaceProtection[$index] ) ) { + // All levels are valid if there's no namespace restriction. + // But still filter by user, if necessary + $levels = $wgRestrictionLevels; + if ( $user ) { + $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) { + $right = $level; + if ( $right == 'sysop' ) { + $right = 'editprotected'; // BC + } + if ( $right == 'autoconfirmed' ) { + $right = 'editsemiprotected'; // BC + } + return ( $right == '' || $user->isAllowed( $right ) ); + } ) ); + } + return $levels; + } + + // First, get the list of groups that can edit this namespace. + $namespaceGroups = array(); + $combine = 'array_merge'; + foreach ( (array)$wgNamespaceProtection[$index] as $right ) { + if ( $right == 'sysop' ) { + $right = 'editprotected'; // BC + } + if ( $right == 'autoconfirmed' ) { + $right = 'editsemiprotected'; // BC + } + if ( $right != '' ) { + $namespaceGroups = call_user_func( $combine, $namespaceGroups, + User::getGroupsWithPermission( $right ) ); + $combine = 'array_intersect'; + } + } + + // Now, keep only those restriction levels where there is at least one + // group that can edit the namespace but would be blocked by the + // restriction. + $usableLevels = array( '' ); + foreach ( $wgRestrictionLevels as $level ) { + $right = $level; + if ( $right == 'sysop' ) { + $right = 'editprotected'; // BC + } + if ( $right == 'autoconfirmed' ) { + $right = 'editsemiprotected'; // BC + } + if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) && + array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) ) + ) { + $usableLevels[] = $level; + } + } + + return $usableLevels; + } +} diff --git a/includes/Namespace.php b/includes/Namespace.php deleted file mode 100644 index 392f5582f8..0000000000 --- a/includes/Namespace.php +++ /dev/null @@ -1,496 +0,0 @@ - NS_MAIN - && $index % 2; - } - - /** - * Get the talk namespace index for a given namespace - * - * @param int $index Namespace index - * @return int - */ - public static function getTalk( $index ) { - self::isMethodValidFor( $index, __METHOD__ ); - return self::isTalk( $index ) - ? $index - : $index + 1; - } - - /** - * Get the subject namespace index for a given namespace - * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject. - * - * @param int $index Namespace index - * @return int - */ - public static function getSubject( $index ) { - # Handle special namespaces - if ( $index < NS_MAIN ) { - return $index; - } - - return self::isTalk( $index ) - ? $index - 1 - : $index; - } - - /** - * Get the associated namespace. - * For talk namespaces, returns the subject (non-talk) namespace - * For subject (non-talk) namespaces, returns the talk namespace - * - * @param int $index Namespace index - * @return int|null If no associated namespace could be found - */ - public static function getAssociated( $index ) { - self::isMethodValidFor( $index, __METHOD__ ); - - if ( self::isSubject( $index ) ) { - return self::getTalk( $index ); - } elseif ( self::isTalk( $index ) ) { - return self::getSubject( $index ); - } else { - return null; - } - } - - /** - * Returns whether the specified namespace exists - * - * @param int $index - * - * @return bool - * @since 1.19 - */ - public static function exists( $index ) { - $nslist = self::getCanonicalNamespaces(); - return isset( $nslist[$index] ); - } - - /** - * Returns whether the specified namespaces are the same namespace - * - * @note It's possible that in the future we may start using something - * other than just namespace indexes. Under that circumstance making use - * of this function rather than directly doing comparison will make - * sure that code will not potentially break. - * - * @param int $ns1 The first namespace index - * @param int $ns2 The second namespace index - * - * @return bool - * @since 1.19 - */ - public static function equals( $ns1, $ns2 ) { - return $ns1 == $ns2; - } - - /** - * Returns whether the specified namespaces share the same subject. - * eg: NS_USER and NS_USER wil return true, as well - * NS_USER and NS_USER_TALK will return true. - * - * @param int $ns1 The first namespace index - * @param int $ns2 The second namespace index - * - * @return bool - * @since 1.19 - */ - public static function subjectEquals( $ns1, $ns2 ) { - return self::getSubject( $ns1 ) == self::getSubject( $ns2 ); - } - - /** - * Returns array of all defined namespaces with their canonical - * (English) names. - * - * @param bool $rebuild Rebuild namespace list (default = false). Used for testing. - * - * @return array - * @since 1.17 - */ - public static function getCanonicalNamespaces( $rebuild = false ) { - static $namespaces = null; - if ( $namespaces === null || $rebuild ) { - global $wgExtraNamespaces, $wgCanonicalNamespaceNames; - $namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames; - if ( is_array( $wgExtraNamespaces ) ) { - $namespaces += $wgExtraNamespaces; - } - wfRunHooks( 'CanonicalNamespaces', array( &$namespaces ) ); - } - return $namespaces; - } - - /** - * Returns the canonical (English) name for a given index - * - * @param int $index Namespace index - * @return string|bool If no canonical definition. - */ - public static function getCanonicalName( $index ) { - $nslist = self::getCanonicalNamespaces(); - if ( isset( $nslist[$index] ) ) { - return $nslist[$index]; - } else { - return false; - } - } - - /** - * Returns the index for a given canonical name, or NULL - * The input *must* be converted to lower case first - * - * @param string $name Namespace name - * @return int - */ - public static function getCanonicalIndex( $name ) { - static $xNamespaces = false; - if ( $xNamespaces === false ) { - $xNamespaces = array(); - foreach ( self::getCanonicalNamespaces() as $i => $text ) { - $xNamespaces[strtolower( $text )] = $i; - } - } - if ( array_key_exists( $name, $xNamespaces ) ) { - return $xNamespaces[$name]; - } else { - return null; - } - } - - /** - * Returns an array of the namespaces (by integer id) that exist on the - * wiki. Used primarily by the api in help documentation. - * @return array - */ - public static function getValidNamespaces() { - static $mValidNamespaces = null; - - if ( is_null( $mValidNamespaces ) ) { - foreach ( array_keys( self::getCanonicalNamespaces() ) as $ns ) { - if ( $ns >= 0 ) { - $mValidNamespaces[] = $ns; - } - } - } - - return $mValidNamespaces; - } - - /** - * Can this namespace ever have a talk namespace? - * - * @param int $index Namespace index - * @return bool - */ - public static function canTalk( $index ) { - return $index >= NS_MAIN; - } - - /** - * Does this namespace contain content, for the purposes of calculating - * statistics, etc? - * - * @param int $index Index to check - * @return bool - */ - public static function isContent( $index ) { - global $wgContentNamespaces; - return $index == NS_MAIN || in_array( $index, $wgContentNamespaces ); - } - - /** - * Can pages in a namespace be watched? - * - * @param int $index - * @return bool - */ - public static function isWatchable( $index ) { - return $index >= NS_MAIN; - } - - /** - * Does the namespace allow subpages? - * - * @param int $index Index to check - * @return bool - */ - public static function hasSubpages( $index ) { - global $wgNamespacesWithSubpages; - return !empty( $wgNamespacesWithSubpages[$index] ); - } - - /** - * Get a list of all namespace indices which are considered to contain content - * @return array Array of namespace indices - */ - public static function getContentNamespaces() { - global $wgContentNamespaces; - if ( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === array() ) { - return array( NS_MAIN ); - } elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) { - // always force NS_MAIN to be part of array (to match the algorithm used by isContent) - return array_merge( array( NS_MAIN ), $wgContentNamespaces ); - } else { - return $wgContentNamespaces; - } - } - - /** - * List all namespace indices which are considered subject, aka not a talk - * or special namespace. See also MWNamespace::isSubject - * - * @return array Array of namespace indices - */ - public static function getSubjectNamespaces() { - return array_filter( - MWNamespace::getValidNamespaces(), - 'MWNamespace::isSubject' - ); - } - - /** - * List all namespace indices which are considered talks, aka not a subject - * or special namespace. See also MWNamespace::isTalk - * - * @return array Array of namespace indices - */ - public static function getTalkNamespaces() { - return array_filter( - MWNamespace::getValidNamespaces(), - 'MWNamespace::isTalk' - ); - } - - /** - * Is the namespace first-letter capitalized? - * - * @param int $index Index to check - * @return bool - */ - public static function isCapitalized( $index ) { - global $wgCapitalLinks, $wgCapitalLinkOverrides; - // Turn NS_MEDIA into NS_FILE - $index = $index === NS_MEDIA ? NS_FILE : $index; - - // Make sure to get the subject of our namespace - $index = self::getSubject( $index ); - - // Some namespaces are special and should always be upper case - if ( in_array( $index, self::$alwaysCapitalizedNamespaces ) ) { - return true; - } - if ( isset( $wgCapitalLinkOverrides[$index] ) ) { - // $wgCapitalLinkOverrides is explicitly set - return $wgCapitalLinkOverrides[$index]; - } - // Default to the global setting - return $wgCapitalLinks; - } - - /** - * Does the namespace (potentially) have different aliases for different - * genders. Not all languages make a distinction here. - * - * @since 1.18 - * @param int $index Index to check - * @return bool - */ - public static function hasGenderDistinction( $index ) { - return $index == NS_USER || $index == NS_USER_TALK; - } - - /** - * It is not possible to use pages from this namespace as template? - * - * @since 1.20 - * @param int $index Index to check - * @return bool - */ - public static function isNonincludable( $index ) { - global $wgNonincludableNamespaces; - return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces ); - } - - /** - * Get the default content model for a namespace - * This does not mean that all pages in that namespace have the model - * - * @since 1.21 - * @param int $index Index to check - * @return null|string Default model name for the given namespace, if set - */ - public static function getNamespaceContentModel( $index ) { - global $wgNamespaceContentModels; - return isset( $wgNamespaceContentModels[$index] ) - ? $wgNamespaceContentModels[$index] - : null; - } - - /** - * Determine which restriction levels it makes sense to use in a namespace, - * optionally filtered by a user's rights. - * - * @since 1.23 - * @param int $index Index to check - * @param User $user User to check - * @return array - */ - public static function getRestrictionLevels( $index, User $user = null ) { - global $wgNamespaceProtection, $wgRestrictionLevels; - - if ( !isset( $wgNamespaceProtection[$index] ) ) { - // All levels are valid if there's no namespace restriction. - // But still filter by user, if necessary - $levels = $wgRestrictionLevels; - if ( $user ) { - $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) { - $right = $level; - if ( $right == 'sysop' ) { - $right = 'editprotected'; // BC - } - if ( $right == 'autoconfirmed' ) { - $right = 'editsemiprotected'; // BC - } - return ( $right == '' || $user->isAllowed( $right ) ); - } ) ); - } - return $levels; - } - - // First, get the list of groups that can edit this namespace. - $namespaceGroups = array(); - $combine = 'array_merge'; - foreach ( (array)$wgNamespaceProtection[$index] as $right ) { - if ( $right == 'sysop' ) { - $right = 'editprotected'; // BC - } - if ( $right == 'autoconfirmed' ) { - $right = 'editsemiprotected'; // BC - } - if ( $right != '' ) { - $namespaceGroups = call_user_func( $combine, $namespaceGroups, - User::getGroupsWithPermission( $right ) ); - $combine = 'array_intersect'; - } - } - - // Now, keep only those restriction levels where there is at least one - // group that can edit the namespace but would be blocked by the - // restriction. - $usableLevels = array( '' ); - foreach ( $wgRestrictionLevels as $level ) { - $right = $level; - if ( $right == 'sysop' ) { - $right = 'editprotected'; // BC - } - if ( $right == 'autoconfirmed' ) { - $right = 'editsemiprotected'; // BC - } - if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) && - array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) ) - ) { - $usableLevels[] = $level; - } - } - - return $usableLevels; - } -} diff --git a/includes/debug/Debug.php b/includes/debug/Debug.php deleted file mode 100644 index 0cea658908..0000000000 --- a/includes/debug/Debug.php +++ /dev/null @@ -1,562 +0,0 @@ -addModules( 'mediawiki.debug.init' ); - } - } - - /** - * Adds a line to the log - * - * @todo Add support for passing objects - * - * @since 1.19 - * @param string $str - */ - public static function log( $str ) { - if ( !self::$enabled ) { - return; - } - - self::$log[] = array( - 'msg' => htmlspecialchars( $str ), - 'type' => 'log', - 'caller' => wfGetCaller(), - ); - } - - /** - * Returns internal log array - * @since 1.19 - * @return array - */ - public static function getLog() { - return self::$log; - } - - /** - * Clears internal log array and deprecation tracking - * @since 1.19 - */ - public static function clearLog() { - self::$log = array(); - self::$deprecationWarnings = array(); - } - - /** - * Adds a warning entry to the log - * - * @since 1.19 - * @param string $msg - * @param int $callerOffset - * @param int $level A PHP error level. See sendMessage() - * @param string $log 'production' will always trigger a php error, 'auto' - * will trigger an error if $wgDevelopmentWarnings is true, and 'debug' - * will only write to the debug log(s). - * - * @return mixed - */ - public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) { - global $wgDevelopmentWarnings; - - if ( $log === 'auto' && !$wgDevelopmentWarnings ) { - $log = 'debug'; - } - - if ( $log === 'debug' ) { - $level = false; - } - - $callerDescription = self::getCallerDescription( $callerOffset ); - - self::sendMessage( $msg, $callerDescription, 'warning', $level ); - - if ( self::$enabled ) { - self::$log[] = array( - 'msg' => htmlspecialchars( $msg ), - 'type' => 'warn', - 'caller' => $callerDescription['func'], - ); - } - } - - /** - * Show a warning that $function is deprecated. - * This will send it to the following locations: - * - Debug toolbar, with one item per function and caller, if $wgDebugToolbar - * is set to true. - * - PHP's error log, with level E_USER_DEPRECATED, if $wgDevelopmentWarnings - * is set to true. - * - MediaWiki's debug log, if $wgDevelopmentWarnings is set to false. - * - * @since 1.19 - * @param string $function Function that is deprecated. - * @param string|bool $version Version in which the function was deprecated. - * @param string|bool $component Component to which the function belongs. - * If false, it is assumbed the function is in MediaWiki core. - * @param int $callerOffset How far up the callstack is the original - * caller. 2 = function that called the function that called - * MWDebug::deprecated() (Added in 1.20). - * @return mixed - */ - public static function deprecated( $function, $version = false, - $component = false, $callerOffset = 2 - ) { - $callerDescription = self::getCallerDescription( $callerOffset ); - $callerFunc = $callerDescription['func']; - - $sendToLog = true; - - // Check to see if there already was a warning about this function - if ( isset( self::$deprecationWarnings[$function][$callerFunc] ) ) { - return; - } elseif ( isset( self::$deprecationWarnings[$function] ) ) { - if ( self::$enabled ) { - $sendToLog = false; - } else { - return; - } - } - - self::$deprecationWarnings[$function][$callerFunc] = true; - - if ( $version ) { - global $wgDeprecationReleaseLimit; - if ( $wgDeprecationReleaseLimit && $component === false ) { - # Strip -* off the end of $version so that branches can use the - # format #.##-branchname to avoid issues if the branch is merged into - # a version of MediaWiki later than what it was branched from - $comparableVersion = preg_replace( '/-.*$/', '', $version ); - - # If the comparableVersion is larger than our release limit then - # skip the warning message for the deprecation - if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) { - $sendToLog = false; - } - } - - $component = $component === false ? 'MediaWiki' : $component; - $msg = "Use of $function was deprecated in $component $version."; - } else { - $msg = "Use of $function is deprecated."; - } - - if ( $sendToLog ) { - global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting. - self::sendMessage( - $msg, - $callerDescription, - 'deprecated', - $wgDevelopmentWarnings ? E_USER_DEPRECATED : false - ); - } - - if ( self::$enabled ) { - $logMsg = htmlspecialchars( $msg ) . - Html::rawElement( 'div', array( 'class' => 'mw-debug-backtrace' ), - Html::element( 'span', array(), 'Backtrace:' ) . wfBacktrace() - ); - - self::$log[] = array( - 'msg' => $logMsg, - 'type' => 'deprecated', - 'caller' => $callerFunc, - ); - } - } - - /** - * Get an array describing the calling function at a specified offset. - * - * @param int $callerOffset How far up the callstack is the original - * caller. 0 = function that called getCallerDescription() - * @return array Array with two keys: 'file' and 'func' - */ - private static function getCallerDescription( $callerOffset ) { - $callers = wfDebugBacktrace(); - - if ( isset( $callers[$callerOffset] ) ) { - $callerfile = $callers[$callerOffset]; - if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) { - $file = $callerfile['file'] . ' at line ' . $callerfile['line']; - } else { - $file = '(internal function)'; - } - } else { - $file = '(unknown location)'; - } - - if ( isset( $callers[$callerOffset + 1] ) ) { - $callerfunc = $callers[$callerOffset + 1]; - $func = ''; - if ( isset( $callerfunc['class'] ) ) { - $func .= $callerfunc['class'] . '::'; - } - if ( isset( $callerfunc['function'] ) ) { - $func .= $callerfunc['function']; - } - } else { - $func = 'unknown'; - } - - return array( 'file' => $file, 'func' => $func ); - } - - /** - * Send a message to the debug log and optionally also trigger a PHP - * error, depending on the $level argument. - * - * @param string $msg Message to send - * @param array $caller Caller description get from getCallerDescription() - * @param string $group Log group on which to send the message - * @param int|bool $level Error level to use; set to false to not trigger an error - */ - private static function sendMessage( $msg, $caller, $group, $level ) { - $msg .= ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']'; - - if ( $level !== false ) { - trigger_error( $msg, $level ); - } - - wfDebugLog( $group, $msg, 'log' ); - } - - /** - * This is a method to pass messages from wfDebug to the pretty debugger. - * Do NOT use this method, use MWDebug::log or wfDebug() - * - * @since 1.19 - * @param string $str - */ - public static function debugMsg( $str ) { - global $wgDebugComments, $wgShowDebug; - - if ( self::$enabled || $wgDebugComments || $wgShowDebug ) { - self::$debug[] = rtrim( UtfNormal::cleanUp( $str ) ); - } - } - - /** - * Begins profiling on a database query - * - * @since 1.19 - * @param string $sql - * @param string $function - * @param bool $isMaster - * @return int ID number of the query to pass to queryTime or -1 if the - * debugger is disabled - */ - public static function query( $sql, $function, $isMaster ) { - if ( !self::$enabled ) { - return -1; - } - - self::$query[] = array( - 'sql' => $sql, - 'function' => $function, - 'master' => (bool)$isMaster, - 'time' => 0.0, - '_start' => microtime( true ), - ); - - return count( self::$query ) - 1; - } - - /** - * Calculates how long a query took. - * - * @since 1.19 - * @param int $id - */ - public static function queryTime( $id ) { - if ( $id === -1 || !self::$enabled ) { - return; - } - - self::$query[$id]['time'] = microtime( true ) - self::$query[$id]['_start']; - unset( self::$query[$id]['_start'] ); - } - - /** - * Returns a list of files included, along with their size - * - * @param IContextSource $context - * @return array - */ - protected static function getFilesIncluded( IContextSource $context ) { - $files = get_included_files(); - $fileList = array(); - foreach ( $files as $file ) { - $size = filesize( $file ); - $fileList[] = array( - 'name' => $file, - 'size' => $context->getLanguage()->formatSize( $size ), - ); - } - - return $fileList; - } - - /** - * Returns the HTML to add to the page for the toolbar - * - * @since 1.19 - * @param IContextSource $context - * @return string - */ - public static function getDebugHTML( IContextSource $context ) { - global $wgDebugComments; - - $html = ''; - - if ( self::$enabled ) { - MWDebug::log( 'MWDebug output complete' ); - $debugInfo = self::getDebugInfo( $context ); - - // Cannot use OutputPage::addJsConfigVars because those are already outputted - // by the time this method is called. - $html = Html::inlineScript( - ResourceLoader::makeLoaderConditionalScript( - ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) ) - ) - ); - } - - if ( $wgDebugComments ) { - $html .= ""; - } - - return $html; - } - - /** - * Generate debug log in HTML for displaying at the bottom of the main - * content area. - * If $wgShowDebug is false, an empty string is always returned. - * - * @since 1.20 - * @return string HTML fragment - */ - public static function getHTMLDebugLog() { - global $wgDebugTimestamps, $wgShowDebug; - - if ( !$wgShowDebug ) { - return ''; - } - - $curIdent = 0; - $ret = "\n
\nDebug data:\n", -$diff ) . "
  • \n"; - } elseif ( $diff == 0 ) { - $ret .= "
  • \n"; - } else { - $ret .= str_repeat( "', $curIdent ) . "
  • \n\n"; - - return $ret; - } - - /** - * Append the debug info to given ApiResult - * - * @param IContextSource $context - * @param ApiResult $result - */ - public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) { - if ( !self::$enabled ) { - return; - } - - // output errors as debug info, when display_errors is on - // this is necessary for all non html output of the api, because that clears all errors first - $obContents = ob_get_contents(); - if ( $obContents ) { - $obContentArray = explode( '
    ', $obContents ); - foreach ( $obContentArray as $obContent ) { - if ( trim( $obContent ) ) { - self::debugMsg( Sanitizer::stripAllTags( $obContent ) ); - } - } - } - - MWDebug::log( 'MWDebug output complete' ); - $debugInfo = self::getDebugInfo( $context ); - - $result->setIndexedTagName( $debugInfo, 'debuginfo' ); - $result->setIndexedTagName( $debugInfo['log'], 'line' ); - $result->setIndexedTagName( $debugInfo['debugLog'], 'msg' ); - $result->setIndexedTagName( $debugInfo['queries'], 'query' ); - $result->setIndexedTagName( $debugInfo['includes'], 'queries' ); - $result->setIndexedTagName( $debugInfo['profile'], 'function' ); - $result->addValue( null, 'debuginfo', $debugInfo ); - } - - /** - * Returns the HTML to add to the page for the toolbar - * - * @param IContextSource $context - * @return array - */ - public static function getDebugInfo( IContextSource $context ) { - if ( !self::$enabled ) { - return array(); - } - - global $wgVersion, $wgRequestTime; - $request = $context->getRequest(); - - // HHVM's reported memory usage from memory_get_peak_usage() - // is not useful when passing false, but we continue passing - // false for consistency of historical data in zend. - // see: https://github.com/facebook/hhvm/issues/2257#issuecomment-39362246 - $realMemoryUsage = wfIsHHVM(); - - return array( - 'mwVersion' => $wgVersion, - 'phpVersion' => PHP_VERSION, - 'gitRevision' => GitInfo::headSHA1(), - 'gitBranch' => GitInfo::currentBranch(), - 'gitViewUrl' => GitInfo::headViewUrl(), - 'time' => microtime( true ) - $wgRequestTime, - 'log' => self::$log, - 'debugLog' => self::$debug, - 'queries' => self::$query, - 'request' => array( - 'method' => $request->getMethod(), - 'url' => $request->getRequestURL(), - 'headers' => $request->getAllHeaders(), - 'params' => $request->getValues(), - ), - 'memory' => $context->getLanguage()->formatSize( memory_get_usage( $realMemoryUsage ) ), - 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage( $realMemoryUsage ) ), - 'includes' => self::getFilesIncluded( $context ), - 'profile' => Profiler::instance()->getRawData(), - ); - } -} diff --git a/includes/debug/MWDebug.php b/includes/debug/MWDebug.php new file mode 100644 index 0000000000..0cea658908 --- /dev/null +++ b/includes/debug/MWDebug.php @@ -0,0 +1,562 @@ +addModules( 'mediawiki.debug.init' ); + } + } + + /** + * Adds a line to the log + * + * @todo Add support for passing objects + * + * @since 1.19 + * @param string $str + */ + public static function log( $str ) { + if ( !self::$enabled ) { + return; + } + + self::$log[] = array( + 'msg' => htmlspecialchars( $str ), + 'type' => 'log', + 'caller' => wfGetCaller(), + ); + } + + /** + * Returns internal log array + * @since 1.19 + * @return array + */ + public static function getLog() { + return self::$log; + } + + /** + * Clears internal log array and deprecation tracking + * @since 1.19 + */ + public static function clearLog() { + self::$log = array(); + self::$deprecationWarnings = array(); + } + + /** + * Adds a warning entry to the log + * + * @since 1.19 + * @param string $msg + * @param int $callerOffset + * @param int $level A PHP error level. See sendMessage() + * @param string $log 'production' will always trigger a php error, 'auto' + * will trigger an error if $wgDevelopmentWarnings is true, and 'debug' + * will only write to the debug log(s). + * + * @return mixed + */ + public static function warning( $msg, $callerOffset = 1, $level = E_USER_NOTICE, $log = 'auto' ) { + global $wgDevelopmentWarnings; + + if ( $log === 'auto' && !$wgDevelopmentWarnings ) { + $log = 'debug'; + } + + if ( $log === 'debug' ) { + $level = false; + } + + $callerDescription = self::getCallerDescription( $callerOffset ); + + self::sendMessage( $msg, $callerDescription, 'warning', $level ); + + if ( self::$enabled ) { + self::$log[] = array( + 'msg' => htmlspecialchars( $msg ), + 'type' => 'warn', + 'caller' => $callerDescription['func'], + ); + } + } + + /** + * Show a warning that $function is deprecated. + * This will send it to the following locations: + * - Debug toolbar, with one item per function and caller, if $wgDebugToolbar + * is set to true. + * - PHP's error log, with level E_USER_DEPRECATED, if $wgDevelopmentWarnings + * is set to true. + * - MediaWiki's debug log, if $wgDevelopmentWarnings is set to false. + * + * @since 1.19 + * @param string $function Function that is deprecated. + * @param string|bool $version Version in which the function was deprecated. + * @param string|bool $component Component to which the function belongs. + * If false, it is assumbed the function is in MediaWiki core. + * @param int $callerOffset How far up the callstack is the original + * caller. 2 = function that called the function that called + * MWDebug::deprecated() (Added in 1.20). + * @return mixed + */ + public static function deprecated( $function, $version = false, + $component = false, $callerOffset = 2 + ) { + $callerDescription = self::getCallerDescription( $callerOffset ); + $callerFunc = $callerDescription['func']; + + $sendToLog = true; + + // Check to see if there already was a warning about this function + if ( isset( self::$deprecationWarnings[$function][$callerFunc] ) ) { + return; + } elseif ( isset( self::$deprecationWarnings[$function] ) ) { + if ( self::$enabled ) { + $sendToLog = false; + } else { + return; + } + } + + self::$deprecationWarnings[$function][$callerFunc] = true; + + if ( $version ) { + global $wgDeprecationReleaseLimit; + if ( $wgDeprecationReleaseLimit && $component === false ) { + # Strip -* off the end of $version so that branches can use the + # format #.##-branchname to avoid issues if the branch is merged into + # a version of MediaWiki later than what it was branched from + $comparableVersion = preg_replace( '/-.*$/', '', $version ); + + # If the comparableVersion is larger than our release limit then + # skip the warning message for the deprecation + if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) { + $sendToLog = false; + } + } + + $component = $component === false ? 'MediaWiki' : $component; + $msg = "Use of $function was deprecated in $component $version."; + } else { + $msg = "Use of $function is deprecated."; + } + + if ( $sendToLog ) { + global $wgDevelopmentWarnings; // we could have a more specific $wgDeprecationWarnings setting. + self::sendMessage( + $msg, + $callerDescription, + 'deprecated', + $wgDevelopmentWarnings ? E_USER_DEPRECATED : false + ); + } + + if ( self::$enabled ) { + $logMsg = htmlspecialchars( $msg ) . + Html::rawElement( 'div', array( 'class' => 'mw-debug-backtrace' ), + Html::element( 'span', array(), 'Backtrace:' ) . wfBacktrace() + ); + + self::$log[] = array( + 'msg' => $logMsg, + 'type' => 'deprecated', + 'caller' => $callerFunc, + ); + } + } + + /** + * Get an array describing the calling function at a specified offset. + * + * @param int $callerOffset How far up the callstack is the original + * caller. 0 = function that called getCallerDescription() + * @return array Array with two keys: 'file' and 'func' + */ + private static function getCallerDescription( $callerOffset ) { + $callers = wfDebugBacktrace(); + + if ( isset( $callers[$callerOffset] ) ) { + $callerfile = $callers[$callerOffset]; + if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) { + $file = $callerfile['file'] . ' at line ' . $callerfile['line']; + } else { + $file = '(internal function)'; + } + } else { + $file = '(unknown location)'; + } + + if ( isset( $callers[$callerOffset + 1] ) ) { + $callerfunc = $callers[$callerOffset + 1]; + $func = ''; + if ( isset( $callerfunc['class'] ) ) { + $func .= $callerfunc['class'] . '::'; + } + if ( isset( $callerfunc['function'] ) ) { + $func .= $callerfunc['function']; + } + } else { + $func = 'unknown'; + } + + return array( 'file' => $file, 'func' => $func ); + } + + /** + * Send a message to the debug log and optionally also trigger a PHP + * error, depending on the $level argument. + * + * @param string $msg Message to send + * @param array $caller Caller description get from getCallerDescription() + * @param string $group Log group on which to send the message + * @param int|bool $level Error level to use; set to false to not trigger an error + */ + private static function sendMessage( $msg, $caller, $group, $level ) { + $msg .= ' [Called from ' . $caller['func'] . ' in ' . $caller['file'] . ']'; + + if ( $level !== false ) { + trigger_error( $msg, $level ); + } + + wfDebugLog( $group, $msg, 'log' ); + } + + /** + * This is a method to pass messages from wfDebug to the pretty debugger. + * Do NOT use this method, use MWDebug::log or wfDebug() + * + * @since 1.19 + * @param string $str + */ + public static function debugMsg( $str ) { + global $wgDebugComments, $wgShowDebug; + + if ( self::$enabled || $wgDebugComments || $wgShowDebug ) { + self::$debug[] = rtrim( UtfNormal::cleanUp( $str ) ); + } + } + + /** + * Begins profiling on a database query + * + * @since 1.19 + * @param string $sql + * @param string $function + * @param bool $isMaster + * @return int ID number of the query to pass to queryTime or -1 if the + * debugger is disabled + */ + public static function query( $sql, $function, $isMaster ) { + if ( !self::$enabled ) { + return -1; + } + + self::$query[] = array( + 'sql' => $sql, + 'function' => $function, + 'master' => (bool)$isMaster, + 'time' => 0.0, + '_start' => microtime( true ), + ); + + return count( self::$query ) - 1; + } + + /** + * Calculates how long a query took. + * + * @since 1.19 + * @param int $id + */ + public static function queryTime( $id ) { + if ( $id === -1 || !self::$enabled ) { + return; + } + + self::$query[$id]['time'] = microtime( true ) - self::$query[$id]['_start']; + unset( self::$query[$id]['_start'] ); + } + + /** + * Returns a list of files included, along with their size + * + * @param IContextSource $context + * @return array + */ + protected static function getFilesIncluded( IContextSource $context ) { + $files = get_included_files(); + $fileList = array(); + foreach ( $files as $file ) { + $size = filesize( $file ); + $fileList[] = array( + 'name' => $file, + 'size' => $context->getLanguage()->formatSize( $size ), + ); + } + + return $fileList; + } + + /** + * Returns the HTML to add to the page for the toolbar + * + * @since 1.19 + * @param IContextSource $context + * @return string + */ + public static function getDebugHTML( IContextSource $context ) { + global $wgDebugComments; + + $html = ''; + + if ( self::$enabled ) { + MWDebug::log( 'MWDebug output complete' ); + $debugInfo = self::getDebugInfo( $context ); + + // Cannot use OutputPage::addJsConfigVars because those are already outputted + // by the time this method is called. + $html = Html::inlineScript( + ResourceLoader::makeLoaderConditionalScript( + ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) ) + ) + ); + } + + if ( $wgDebugComments ) { + $html .= ""; + } + + return $html; + } + + /** + * Generate debug log in HTML for displaying at the bottom of the main + * content area. + * If $wgShowDebug is false, an empty string is always returned. + * + * @since 1.20 + * @return string HTML fragment + */ + public static function getHTMLDebugLog() { + global $wgDebugTimestamps, $wgShowDebug; + + if ( !$wgShowDebug ) { + return ''; + } + + $curIdent = 0; + $ret = "\n
    \nDebug data:\n", -$diff ) . "
  • \n"; + } elseif ( $diff == 0 ) { + $ret .= "
  • \n"; + } else { + $ret .= str_repeat( "', $curIdent ) . "
  • \n\n"; + + return $ret; + } + + /** + * Append the debug info to given ApiResult + * + * @param IContextSource $context + * @param ApiResult $result + */ + public static function appendDebugInfoToApiResult( IContextSource $context, ApiResult $result ) { + if ( !self::$enabled ) { + return; + } + + // output errors as debug info, when display_errors is on + // this is necessary for all non html output of the api, because that clears all errors first + $obContents = ob_get_contents(); + if ( $obContents ) { + $obContentArray = explode( '
    ', $obContents ); + foreach ( $obContentArray as $obContent ) { + if ( trim( $obContent ) ) { + self::debugMsg( Sanitizer::stripAllTags( $obContent ) ); + } + } + } + + MWDebug::log( 'MWDebug output complete' ); + $debugInfo = self::getDebugInfo( $context ); + + $result->setIndexedTagName( $debugInfo, 'debuginfo' ); + $result->setIndexedTagName( $debugInfo['log'], 'line' ); + $result->setIndexedTagName( $debugInfo['debugLog'], 'msg' ); + $result->setIndexedTagName( $debugInfo['queries'], 'query' ); + $result->setIndexedTagName( $debugInfo['includes'], 'queries' ); + $result->setIndexedTagName( $debugInfo['profile'], 'function' ); + $result->addValue( null, 'debuginfo', $debugInfo ); + } + + /** + * Returns the HTML to add to the page for the toolbar + * + * @param IContextSource $context + * @return array + */ + public static function getDebugInfo( IContextSource $context ) { + if ( !self::$enabled ) { + return array(); + } + + global $wgVersion, $wgRequestTime; + $request = $context->getRequest(); + + // HHVM's reported memory usage from memory_get_peak_usage() + // is not useful when passing false, but we continue passing + // false for consistency of historical data in zend. + // see: https://github.com/facebook/hhvm/issues/2257#issuecomment-39362246 + $realMemoryUsage = wfIsHHVM(); + + return array( + 'mwVersion' => $wgVersion, + 'phpVersion' => PHP_VERSION, + 'gitRevision' => GitInfo::headSHA1(), + 'gitBranch' => GitInfo::currentBranch(), + 'gitViewUrl' => GitInfo::headViewUrl(), + 'time' => microtime( true ) - $wgRequestTime, + 'log' => self::$log, + 'debugLog' => self::$debug, + 'queries' => self::$query, + 'request' => array( + 'method' => $request->getMethod(), + 'url' => $request->getRequestURL(), + 'headers' => $request->getAllHeaders(), + 'params' => $request->getValues(), + ), + 'memory' => $context->getLanguage()->formatSize( memory_get_usage( $realMemoryUsage ) ), + 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage( $realMemoryUsage ) ), + 'includes' => self::getFilesIncluded( $context ), + 'profile' => Profiler::instance()->getRawData(), + ); + } +} diff --git a/includes/parser/MWTidy.php b/includes/parser/MWTidy.php new file mode 100644 index 0000000000..f7fe5a8d91 --- /dev/null +++ b/includes/parser/MWTidy.php @@ -0,0 +1,289 @@ +mTokens = null; + $this->mUniqPrefix = null; + } + + /** + * @param string $text + * @return string + */ + public function getWrapped( $text ) { + $this->mTokens = new ReplacementArray; + $this->mUniqPrefix = "\x7fUNIQ" . + dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) ); + $this->mMarkerIndex = 0; + + // Replace elements with placeholders + $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX, + array( &$this, 'replaceCallback' ), $text ); + // ...and markers + $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/', + array( &$this, 'replaceCallback' ), $wrappedtext ); + + // Modify inline Microdata and elements so they say and so + // we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config + $wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '' . + 'test' . $wrappedtext . ''; + + return $wrappedtext; + } + + /** + * @param array $m + * + * @return string + */ + function replaceCallback( $m ) { + $marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX; + $this->mMarkerIndex++; + $this->mTokens->setPair( $marker, $m[0] ); + return $marker; + } + + /** + * @param string $text + * @return string + */ + public function postprocess( $text ) { + // Revert back to <{link,meta}> + $text = preg_replace( '!]*?)(/{0,1}>)!', '<$1$2$3', $text ); + + // Restore the contents of placeholder tokens + $text = $this->mTokens->replace( $text ); + + return $text; + } + +} + +/** + * Class to interact with HTML tidy + * + * Either the external tidy program or the in-process tidy extension + * will be used depending on availability. Override the default + * $wgTidyInternal setting to disable the internal if it's not working. + * + * @ingroup Parser + */ +class MWTidy { + /** + * Interface with html tidy, used if $wgUseTidy = true. + * If tidy isn't able to correct the markup, the original will be + * returned in all its glory with a warning comment appended. + * + * @param string $text Hideous HTML input + * @return string Corrected HTML output + */ + public static function tidy( $text ) { + global $wgTidyInternal; + + $wrapper = new MWTidyWrapper; + $wrappedtext = $wrapper->getWrapped( $text ); + + $retVal = null; + if ( $wgTidyInternal ) { + $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal ); + } else { + $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal ); + } + + if ( $retVal < 0 ) { + wfDebug( "Possible tidy configuration error!\n" ); + return $text . "\n\n"; + } elseif ( is_null( $correctedtext ) ) { + wfDebug( "Tidy error detected!\n" ); + return $text . "\n\n"; + } + + $correctedtext = $wrapper->postprocess( $correctedtext ); // restore any hidden tokens + + return $correctedtext; + } + + /** + * Check HTML for errors, used if $wgValidateAllHtml = true. + * + * @param string $text + * @param string &$errorStr Return the error string + * @return bool Whether the HTML is valid + */ + public static function checkErrors( $text, &$errorStr = null ) { + global $wgTidyInternal; + + $retval = 0; + if ( $wgTidyInternal ) { + $errorStr = self::execInternalTidy( $text, true, $retval ); + } else { + $errorStr = self::execExternalTidy( $text, true, $retval ); + } + + return ( $retval < 0 && $errorStr == '' ) || $retval == 0; + } + + /** + * Spawn an external HTML tidy process and get corrected markup back from it. + * Also called in OutputHandler.php for full page validation + * + * @param string $text HTML to check + * @param bool $stderr Whether to read result from STDERR rather than STDOUT + * @param int &$retval Exit code (-1 on internal error) + * @return string|null + */ + private static function execExternalTidy( $text, $stderr = false, &$retval = null ) { + global $wgTidyConf, $wgTidyBin, $wgTidyOpts; + wfProfileIn( __METHOD__ ); + + $cleansource = ''; + $opts = ' -utf8'; + + if ( $stderr ) { + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'file', wfGetNull(), 'a' ), + 2 => array( 'pipe', 'w' ) + ); + } else { + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'pipe', 'w' ), + 2 => array( 'file', wfGetNull(), 'a' ) + ); + } + + $readpipe = $stderr ? 2 : 1; + $pipes = array(); + + $process = proc_open( + "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes ); + + //NOTE: At least on linux, the process will be created even if tidy is not installed. + // This means that missing tidy will be treated as a validation failure. + + if ( is_resource( $process ) ) { + // Theoretically, this style of communication could cause a deadlock + // here. If the stdout buffer fills up, then writes to stdin could + // block. This doesn't appear to happen with tidy, because tidy only + // writes to stdout after it's finished reading from stdin. Search + // for tidyParseStdin and tidySaveStdout in console/tidy.c + fwrite( $pipes[0], $text ); + fclose( $pipes[0] ); + while ( !feof( $pipes[$readpipe] ) ) { + $cleansource .= fgets( $pipes[$readpipe], 1024 ); + } + fclose( $pipes[$readpipe] ); + $retval = proc_close( $process ); + } else { + wfWarn( "Unable to start external tidy process" ); + $retval = -1; + } + + if ( !$stderr && $cleansource == '' && $text != '' ) { + // Some kind of error happened, so we couldn't get the corrected text. + // Just give up; we'll use the source text and append a warning. + $cleansource = null; + } + + wfProfileOut( __METHOD__ ); + return $cleansource; + } + + /** + * Use the HTML tidy extension to use the tidy library in-process, + * saving the overhead of spawning a new process. + * + * @param string $text HTML to check + * @param bool $stderr Whether to read result from error status instead of output + * @param int &$retval Exit code (-1 on internal error) + * @return string|null + */ + private static function execInternalTidy( $text, $stderr = false, &$retval = null ) { + global $wgTidyConf, $wgDebugTidy; + wfProfileIn( __METHOD__ ); + + if ( !class_exists( 'tidy' ) ) { + wfWarn( "Unable to load internal tidy class." ); + $retval = -1; + + wfProfileOut( __METHOD__ ); + return null; + } + + $tidy = new tidy; + $tidy->parseString( $text, $wgTidyConf, 'utf8' ); + + if ( $stderr ) { + $retval = $tidy->getStatus(); + + wfProfileOut( __METHOD__ ); + return $tidy->errorBuffer; + } + + $tidy->cleanRepair(); + $retval = $tidy->getStatus(); + if ( $retval == 2 ) { + // 2 is magic number for fatal error + // http://www.php.net/manual/en/function.tidy-get-status.php + $cleansource = null; + } else { + $cleansource = tidy_get_output( $tidy ); + if ( $wgDebugTidy && $retval > 0 ) { + $cleansource .= "', '-->', $tidy->errorBuffer ) . + "\n-->"; + } + } + + wfProfileOut( __METHOD__ ); + return $cleansource; + } +} diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php deleted file mode 100644 index f7fe5a8d91..0000000000 --- a/includes/parser/Tidy.php +++ /dev/null @@ -1,289 +0,0 @@ -mTokens = null; - $this->mUniqPrefix = null; - } - - /** - * @param string $text - * @return string - */ - public function getWrapped( $text ) { - $this->mTokens = new ReplacementArray; - $this->mUniqPrefix = "\x7fUNIQ" . - dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) ); - $this->mMarkerIndex = 0; - - // Replace elements with placeholders - $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX, - array( &$this, 'replaceCallback' ), $text ); - // ...and markers - $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/', - array( &$this, 'replaceCallback' ), $wrappedtext ); - - // Modify inline Microdata and elements so they say and so - // we can trick Tidy into not stripping them out by including them in tidy's new-empty-tags config - $wrappedtext = preg_replace( '!<(link|meta)([^>]*?)(/{0,1}>)!', '' . - 'test' . $wrappedtext . ''; - - return $wrappedtext; - } - - /** - * @param array $m - * - * @return string - */ - function replaceCallback( $m ) { - $marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX; - $this->mMarkerIndex++; - $this->mTokens->setPair( $marker, $m[0] ); - return $marker; - } - - /** - * @param string $text - * @return string - */ - public function postprocess( $text ) { - // Revert back to <{link,meta}> - $text = preg_replace( '!]*?)(/{0,1}>)!', '<$1$2$3', $text ); - - // Restore the contents of placeholder tokens - $text = $this->mTokens->replace( $text ); - - return $text; - } - -} - -/** - * Class to interact with HTML tidy - * - * Either the external tidy program or the in-process tidy extension - * will be used depending on availability. Override the default - * $wgTidyInternal setting to disable the internal if it's not working. - * - * @ingroup Parser - */ -class MWTidy { - /** - * Interface with html tidy, used if $wgUseTidy = true. - * If tidy isn't able to correct the markup, the original will be - * returned in all its glory with a warning comment appended. - * - * @param string $text Hideous HTML input - * @return string Corrected HTML output - */ - public static function tidy( $text ) { - global $wgTidyInternal; - - $wrapper = new MWTidyWrapper; - $wrappedtext = $wrapper->getWrapped( $text ); - - $retVal = null; - if ( $wgTidyInternal ) { - $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal ); - } else { - $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal ); - } - - if ( $retVal < 0 ) { - wfDebug( "Possible tidy configuration error!\n" ); - return $text . "\n\n"; - } elseif ( is_null( $correctedtext ) ) { - wfDebug( "Tidy error detected!\n" ); - return $text . "\n\n"; - } - - $correctedtext = $wrapper->postprocess( $correctedtext ); // restore any hidden tokens - - return $correctedtext; - } - - /** - * Check HTML for errors, used if $wgValidateAllHtml = true. - * - * @param string $text - * @param string &$errorStr Return the error string - * @return bool Whether the HTML is valid - */ - public static function checkErrors( $text, &$errorStr = null ) { - global $wgTidyInternal; - - $retval = 0; - if ( $wgTidyInternal ) { - $errorStr = self::execInternalTidy( $text, true, $retval ); - } else { - $errorStr = self::execExternalTidy( $text, true, $retval ); - } - - return ( $retval < 0 && $errorStr == '' ) || $retval == 0; - } - - /** - * Spawn an external HTML tidy process and get corrected markup back from it. - * Also called in OutputHandler.php for full page validation - * - * @param string $text HTML to check - * @param bool $stderr Whether to read result from STDERR rather than STDOUT - * @param int &$retval Exit code (-1 on internal error) - * @return string|null - */ - private static function execExternalTidy( $text, $stderr = false, &$retval = null ) { - global $wgTidyConf, $wgTidyBin, $wgTidyOpts; - wfProfileIn( __METHOD__ ); - - $cleansource = ''; - $opts = ' -utf8'; - - if ( $stderr ) { - $descriptorspec = array( - 0 => array( 'pipe', 'r' ), - 1 => array( 'file', wfGetNull(), 'a' ), - 2 => array( 'pipe', 'w' ) - ); - } else { - $descriptorspec = array( - 0 => array( 'pipe', 'r' ), - 1 => array( 'pipe', 'w' ), - 2 => array( 'file', wfGetNull(), 'a' ) - ); - } - - $readpipe = $stderr ? 2 : 1; - $pipes = array(); - - $process = proc_open( - "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes ); - - //NOTE: At least on linux, the process will be created even if tidy is not installed. - // This means that missing tidy will be treated as a validation failure. - - if ( is_resource( $process ) ) { - // Theoretically, this style of communication could cause a deadlock - // here. If the stdout buffer fills up, then writes to stdin could - // block. This doesn't appear to happen with tidy, because tidy only - // writes to stdout after it's finished reading from stdin. Search - // for tidyParseStdin and tidySaveStdout in console/tidy.c - fwrite( $pipes[0], $text ); - fclose( $pipes[0] ); - while ( !feof( $pipes[$readpipe] ) ) { - $cleansource .= fgets( $pipes[$readpipe], 1024 ); - } - fclose( $pipes[$readpipe] ); - $retval = proc_close( $process ); - } else { - wfWarn( "Unable to start external tidy process" ); - $retval = -1; - } - - if ( !$stderr && $cleansource == '' && $text != '' ) { - // Some kind of error happened, so we couldn't get the corrected text. - // Just give up; we'll use the source text and append a warning. - $cleansource = null; - } - - wfProfileOut( __METHOD__ ); - return $cleansource; - } - - /** - * Use the HTML tidy extension to use the tidy library in-process, - * saving the overhead of spawning a new process. - * - * @param string $text HTML to check - * @param bool $stderr Whether to read result from error status instead of output - * @param int &$retval Exit code (-1 on internal error) - * @return string|null - */ - private static function execInternalTidy( $text, $stderr = false, &$retval = null ) { - global $wgTidyConf, $wgDebugTidy; - wfProfileIn( __METHOD__ ); - - if ( !class_exists( 'tidy' ) ) { - wfWarn( "Unable to load internal tidy class." ); - $retval = -1; - - wfProfileOut( __METHOD__ ); - return null; - } - - $tidy = new tidy; - $tidy->parseString( $text, $wgTidyConf, 'utf8' ); - - if ( $stderr ) { - $retval = $tidy->getStatus(); - - wfProfileOut( __METHOD__ ); - return $tidy->errorBuffer; - } - - $tidy->cleanRepair(); - $retval = $tidy->getStatus(); - if ( $retval == 2 ) { - // 2 is magic number for fatal error - // http://www.php.net/manual/en/function.tidy-get-status.php - $cleansource = null; - } else { - $cleansource = tidy_get_output( $tidy ); - if ( $wgDebugTidy && $retval > 0 ) { - $cleansource .= "', '-->', $tidy->errorBuffer ) . - "\n-->"; - } - } - - wfProfileOut( __METHOD__ ); - return $cleansource; - } -}