'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',
'SQLiteField' => 'includes/db/DatabaseSqlite.php',
# includes/debug
- 'MWDebug' => 'includes/debug/Debug.php',
+ 'MWDebug' => 'includes/debug/MWDebug.php',
# includes/deferred
'DataUpdate' => 'includes/deferred/DataUpdate.php',
'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',
--- /dev/null
+<?php
+/**
+ * Provide things related to namespaces.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * This is a utility class with only static functions
+ * for dealing with namespaces that encodes all the
+ * "magic" behaviors of them based on index. The textual
+ * names of the namespaces are handled by Language.php.
+ *
+ * These are synonyms for the names given in the language file
+ * Users and translators should not change them
+ *
+ */
+class MWNamespace {
+
+ /**
+ * These namespaces should always be first-letter capitalized, now and
+ * forevermore. Historically, they could've probably been lowercased too,
+ * but some things are just too ingrained now. :)
+ */
+ private static $alwaysCapitalizedNamespaces = array( NS_SPECIAL, NS_USER, NS_MEDIAWIKI );
+
+ /**
+ * Throw an exception when trying to get the subject or talk page
+ * for a given namespace where it does not make sense.
+ * Special namespaces are defined in includes/Defines.php and have
+ * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
+ *
+ * @param int $index
+ * @param string $method
+ *
+ * @throws MWException
+ * @return bool
+ */
+ private static function isMethodValidFor( $index, $method ) {
+ if ( $index < NS_MAIN ) {
+ throw new MWException( "$method does not make any sense for given namespace $index" );
+ }
+ return true;
+ }
+
+ /**
+ * Can pages in the given namespace be moved?
+ *
+ * @param int $index Namespace index
+ * @return bool
+ */
+ public static function isMovable( $index ) {
+ global $wgAllowImageMoving;
+
+ $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) );
+
+ /**
+ * @since 1.20
+ */
+ wfRunHooks( 'NamespaceIsMovable', array( $index, &$result ) );
+
+ return $result;
+ }
+
+ /**
+ * Is the given namespace is a subject (non-talk) namespace?
+ *
+ * @param int $index Namespace index
+ * @return bool
+ * @since 1.19
+ */
+ public static function isSubject( $index ) {
+ return !self::isTalk( $index );
+ }
+
+ /**
+ * Is the given namespace a talk namespace?
+ *
+ * @param int $index Namespace index
+ * @return bool
+ */
+ public static function isTalk( $index ) {
+ return $index > 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;
+ }
+}
+++ /dev/null
-<?php
-/**
- * Provide things related to namespaces.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * This is a utility class with only static functions
- * for dealing with namespaces that encodes all the
- * "magic" behaviors of them based on index. The textual
- * names of the namespaces are handled by Language.php.
- *
- * These are synonyms for the names given in the language file
- * Users and translators should not change them
- *
- */
-class MWNamespace {
-
- /**
- * These namespaces should always be first-letter capitalized, now and
- * forevermore. Historically, they could've probably been lowercased too,
- * but some things are just too ingrained now. :)
- */
- private static $alwaysCapitalizedNamespaces = array( NS_SPECIAL, NS_USER, NS_MEDIAWIKI );
-
- /**
- * Throw an exception when trying to get the subject or talk page
- * for a given namespace where it does not make sense.
- * Special namespaces are defined in includes/Defines.php and have
- * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
- *
- * @param int $index
- * @param string $method
- *
- * @throws MWException
- * @return bool
- */
- private static function isMethodValidFor( $index, $method ) {
- if ( $index < NS_MAIN ) {
- throw new MWException( "$method does not make any sense for given namespace $index" );
- }
- return true;
- }
-
- /**
- * Can pages in the given namespace be moved?
- *
- * @param int $index Namespace index
- * @return bool
- */
- public static function isMovable( $index ) {
- global $wgAllowImageMoving;
-
- $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) );
-
- /**
- * @since 1.20
- */
- wfRunHooks( 'NamespaceIsMovable', array( $index, &$result ) );
-
- return $result;
- }
-
- /**
- * Is the given namespace is a subject (non-talk) namespace?
- *
- * @param int $index Namespace index
- * @return bool
- * @since 1.19
- */
- public static function isSubject( $index ) {
- return !self::isTalk( $index );
- }
-
- /**
- * Is the given namespace a talk namespace?
- *
- * @param int $index Namespace index
- * @return bool
- */
- public static function isTalk( $index ) {
- return $index > 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;
- }
-}
+++ /dev/null
-<?php
-/**
- * Debug toolbar related code.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * New debugger system that outputs a toolbar on page view.
- *
- * By default, most methods do nothing ( self::$enabled = false ). You have
- * to explicitly call MWDebug::init() to enabled them.
- *
- * @todo Profiler support
- *
- * @since 1.19
- */
-class MWDebug {
- /**
- * Log lines
- *
- * @var array $log
- */
- protected static $log = array();
-
- /**
- * Debug messages from wfDebug().
- *
- * @var array $debug
- */
- protected static $debug = array();
-
- /**
- * SQL statements of the databses queries.
- *
- * @var array $query
- */
- protected static $query = array();
-
- /**
- * Is the debugger enabled?
- *
- * @var bool $enabled
- */
- protected static $enabled = false;
-
- /**
- * Array of functions that have already been warned, formatted
- * function-caller to prevent a buttload of warnings
- *
- * @var array $deprecationWarnings
- */
- protected static $deprecationWarnings = array();
-
- /**
- * Enabled the debugger and load resource module.
- * This is called by Setup.php when $wgDebugToolbar is true.
- *
- * @since 1.19
- */
- public static function init() {
- self::$enabled = true;
- }
-
- /**
- * Add ResourceLoader modules to the OutputPage object if debugging is
- * enabled.
- *
- * @since 1.19
- * @param OutputPage $out
- */
- public static function addModules( OutputPage $out ) {
- if ( self::$enabled ) {
- $out->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 .= "<!-- Debug output:\n" .
- htmlspecialchars( implode( "\n", self::$debug ) ) .
- "\n\n-->";
- }
-
- 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<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">\n<li>";
-
- foreach ( self::$debug as $line ) {
- $pre = '';
- if ( $wgDebugTimestamps ) {
- $matches = array();
- if ( preg_match( '/^(\d+\.\d+ {1,3}\d+.\dM\s{2})/', $line, $matches ) ) {
- $pre = $matches[1];
- $line = substr( $line, strlen( $pre ) );
- }
- }
- $display = ltrim( $line );
- $ident = strlen( $line ) - strlen( $display );
- $diff = $ident - $curIdent;
-
- $display = $pre . $display;
- if ( $display == '' ) {
- $display = "\xc2\xa0";
- }
-
- if ( !$ident
- && $diff < 0
- && substr( $display, 0, 9 ) != 'Entering '
- && substr( $display, 0, 8 ) != 'Exiting '
- ) {
- $ident = $curIdent;
- $diff = 0;
- $display = '<span style="background:yellow;">' .
- nl2br( htmlspecialchars( $display ) ) . '</span>';
- } else {
- $display = nl2br( htmlspecialchars( $display ) );
- }
-
- if ( $diff < 0 ) {
- $ret .= str_repeat( "</li></ul>\n", -$diff ) . "</li><li>\n";
- } elseif ( $diff == 0 ) {
- $ret .= "</li><li>\n";
- } else {
- $ret .= str_repeat( "<ul><li>\n", $diff );
- }
- $ret .= "<code>$display</code>\n";
-
- $curIdent = $ident;
- }
-
- $ret .= str_repeat( '</li></ul>', $curIdent ) . "</li>\n</ul>\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( '<br />', $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(),
- );
- }
-}
--- /dev/null
+<?php
+/**
+ * Debug toolbar related code.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * New debugger system that outputs a toolbar on page view.
+ *
+ * By default, most methods do nothing ( self::$enabled = false ). You have
+ * to explicitly call MWDebug::init() to enabled them.
+ *
+ * @todo Profiler support
+ *
+ * @since 1.19
+ */
+class MWDebug {
+ /**
+ * Log lines
+ *
+ * @var array $log
+ */
+ protected static $log = array();
+
+ /**
+ * Debug messages from wfDebug().
+ *
+ * @var array $debug
+ */
+ protected static $debug = array();
+
+ /**
+ * SQL statements of the databses queries.
+ *
+ * @var array $query
+ */
+ protected static $query = array();
+
+ /**
+ * Is the debugger enabled?
+ *
+ * @var bool $enabled
+ */
+ protected static $enabled = false;
+
+ /**
+ * Array of functions that have already been warned, formatted
+ * function-caller to prevent a buttload of warnings
+ *
+ * @var array $deprecationWarnings
+ */
+ protected static $deprecationWarnings = array();
+
+ /**
+ * Enabled the debugger and load resource module.
+ * This is called by Setup.php when $wgDebugToolbar is true.
+ *
+ * @since 1.19
+ */
+ public static function init() {
+ self::$enabled = true;
+ }
+
+ /**
+ * Add ResourceLoader modules to the OutputPage object if debugging is
+ * enabled.
+ *
+ * @since 1.19
+ * @param OutputPage $out
+ */
+ public static function addModules( OutputPage $out ) {
+ if ( self::$enabled ) {
+ $out->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 .= "<!-- Debug output:\n" .
+ htmlspecialchars( implode( "\n", self::$debug ) ) .
+ "\n\n-->";
+ }
+
+ 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<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">\n<li>";
+
+ foreach ( self::$debug as $line ) {
+ $pre = '';
+ if ( $wgDebugTimestamps ) {
+ $matches = array();
+ if ( preg_match( '/^(\d+\.\d+ {1,3}\d+.\dM\s{2})/', $line, $matches ) ) {
+ $pre = $matches[1];
+ $line = substr( $line, strlen( $pre ) );
+ }
+ }
+ $display = ltrim( $line );
+ $ident = strlen( $line ) - strlen( $display );
+ $diff = $ident - $curIdent;
+
+ $display = $pre . $display;
+ if ( $display == '' ) {
+ $display = "\xc2\xa0";
+ }
+
+ if ( !$ident
+ && $diff < 0
+ && substr( $display, 0, 9 ) != 'Entering '
+ && substr( $display, 0, 8 ) != 'Exiting '
+ ) {
+ $ident = $curIdent;
+ $diff = 0;
+ $display = '<span style="background:yellow;">' .
+ nl2br( htmlspecialchars( $display ) ) . '</span>';
+ } else {
+ $display = nl2br( htmlspecialchars( $display ) );
+ }
+
+ if ( $diff < 0 ) {
+ $ret .= str_repeat( "</li></ul>\n", -$diff ) . "</li><li>\n";
+ } elseif ( $diff == 0 ) {
+ $ret .= "</li><li>\n";
+ } else {
+ $ret .= str_repeat( "<ul><li>\n", $diff );
+ }
+ $ret .= "<code>$display</code>\n";
+
+ $curIdent = $ident;
+ }
+
+ $ret .= str_repeat( '</li></ul>', $curIdent ) . "</li>\n</ul>\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( '<br />', $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(),
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * HTML validation and correction
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * Class used to hide mw:editsection tokens from Tidy so that it doesn't break them
+ * or break on them. This is a bit of a hack for now, but hopefully in the future
+ * we may create a real postprocessor or something that will replace this.
+ * It's called wrapper because for now it basically takes over MWTidy::tidy's task
+ * of wrapping the text in a xhtml block
+ *
+ * This re-uses some of the parser's UNIQ tricks, though some of it is private so it's
+ * duplicated. Perhaps we should create an abstract marker hiding class.
+ *
+ * @ingroup Parser
+ */
+class MWTidyWrapper {
+
+ /**
+ * @var ReplacementArray
+ */
+ protected $mTokens;
+
+ protected $mUniqPrefix;
+
+ protected $mMarkerIndex;
+
+ public function __construct() {
+ $this->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 <mw:editsection> elements with placeholders
+ $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
+ array( &$this, 'replaceCallback' ), $text );
+ // ...and <mw:toc> markers
+ $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/',
+ array( &$this, 'replaceCallback' ), $wrappedtext );
+
+ // Modify inline Microdata <link> and <meta> elements so they say <html-link> and <html-meta> 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}>)!', '<html-$1$2$3', $wrappedtext );
+
+ // Wrap the whole thing in a doctype and body for Tidy.
+ $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' .
+ ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>' .
+ '<head><title>test</title></head><body>' . $wrappedtext . '</body></html>';
+
+ 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 <html-{link,meta}> back to <{link,meta}>
+ $text = preg_replace( '!<html-(link|meta)([^>]*?)(/{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<!-- Tidy was unable to run -->\n";
+ } elseif ( is_null( $correctedtext ) ) {
+ wfDebug( "Tidy error detected!\n" );
+ return $text . "\n<!-- Tidy found serious XHTML errors -->\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 .= "<!--\nTidy reports:\n" .
+ str_replace( '-->', '-->', $tidy->errorBuffer ) .
+ "\n-->";
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $cleansource;
+ }
+}
+++ /dev/null
-<?php
-/**
- * HTML validation and correction
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Parser
- */
-
-/**
- * Class used to hide mw:editsection tokens from Tidy so that it doesn't break them
- * or break on them. This is a bit of a hack for now, but hopefully in the future
- * we may create a real postprocessor or something that will replace this.
- * It's called wrapper because for now it basically takes over MWTidy::tidy's task
- * of wrapping the text in a xhtml block
- *
- * This re-uses some of the parser's UNIQ tricks, though some of it is private so it's
- * duplicated. Perhaps we should create an abstract marker hiding class.
- *
- * @ingroup Parser
- */
-class MWTidyWrapper {
-
- /**
- * @var ReplacementArray
- */
- protected $mTokens;
-
- protected $mUniqPrefix;
-
- protected $mMarkerIndex;
-
- public function __construct() {
- $this->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 <mw:editsection> elements with placeholders
- $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
- array( &$this, 'replaceCallback' ), $text );
- // ...and <mw:toc> markers
- $wrappedtext = preg_replace_callback( '/\<\\/?mw:toc\>/',
- array( &$this, 'replaceCallback' ), $wrappedtext );
-
- // Modify inline Microdata <link> and <meta> elements so they say <html-link> and <html-meta> 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}>)!', '<html-$1$2$3', $wrappedtext );
-
- // Wrap the whole thing in a doctype and body for Tidy.
- $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' .
- ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>' .
- '<head><title>test</title></head><body>' . $wrappedtext . '</body></html>';
-
- 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 <html-{link,meta}> back to <{link,meta}>
- $text = preg_replace( '!<html-(link|meta)([^>]*?)(/{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<!-- Tidy was unable to run -->\n";
- } elseif ( is_null( $correctedtext ) ) {
- wfDebug( "Tidy error detected!\n" );
- return $text . "\n<!-- Tidy found serious XHTML errors -->\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 .= "<!--\nTidy reports:\n" .
- str_replace( '-->', '-->', $tidy->errorBuffer ) .
- "\n-->";
- }
- }
-
- wfProfileOut( __METHOD__ );
- return $cleansource;
- }
-}