WikiDebug

From TestWiki
Revision as of 02:57, 28 June 2006 by HappyDog (Talk | contribs) (Added descriptions of current features)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

This extension contains a small set of tools that I find useful when debugging my extensions.

Currently the extension contains two tools: 'describe_table' and 'show_source'.

describe_table

This extension adds a new tag that allows you to get a live description of a DB table.

Syntax

<describe_table table="{TableName}" showindexes="{bool}"></describe_table>
  • {TableName} is the name of a table in the wiki's database. Do not include the prefix specified by $wgPrefix.
  • showindexes is an optional argument, which defaults to true if omitted. Set to false to disable index display.

As a security measure this extension will only allow display of tables added to the $wgWikiDebug_ViewableTables array. Any other tables are blocked.

For example, to view the table 'page', you would need to add the following to LocalSettings.php:

$wgWikiDebug_ViewableTables[] = "page";


Example 1: Description with indexes

Providing the table 'page' is in the viewable tables array, then you can show it's description by using the following syntax:

<describe_table table="page"></describe_table>

Live Table Definition: page

CREATE TABLE IF NOT EXISTS `page` (
  `page_id` int(8) unsigned NOT NULL AUTO_INCREMENT,
  `page_namespace` int(11) NOT NULL DEFAULT '0',
  `page_title` varchar(255) NOT NULL DEFAULT '',
  `page_restrictions` tinyblob NOT NULL,
  `page_counter` bigint(20) unsigned NOT NULL DEFAULT '0',
  `page_is_redirect` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `page_is_new` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `page_random` double unsigned NOT NULL DEFAULT '0',
  `page_touched` varchar(14) NOT NULL DEFAULT '',
  `page_latest` int(8) unsigned NOT NULL DEFAULT '0',
  `page_len` int(8) unsigned NOT NULL DEFAULT '0',
  `page_content_model` varbinary(32) DEFAULT NULL,
  `page_links_updated` varbinary(14) DEFAULT NULL,
  `page_lang` varbinary(35) DEFAULT NULL,
  PRIMARY KEY (`page_id`),
  UNIQUE KEY `name_title` (`page_namespace`,`page_title`),
  KEY `page_random` (`page_random`),
  KEY `page_len` (`page_len`),
  KEY `page_redirect_namespace_len` (`page_is_redirect`,`page_namespace`,`page_len`)
) ENGINE=MyISAM;


Example 2: Description without indexes

<describe_table table="page" showindexes="false"></describe_table>

Live Table Definition: page

CREATE TABLE IF NOT EXISTS `page` (
  `page_id` int(8) unsigned NOT NULL AUTO_INCREMENT,
  `page_namespace` int(11) NOT NULL DEFAULT '0',
  `page_title` varchar(255) NOT NULL DEFAULT '',
  `page_restrictions` tinyblob NOT NULL,
  `page_counter` bigint(20) unsigned NOT NULL DEFAULT '0',
  `page_is_redirect` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `page_is_new` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `page_random` double unsigned NOT NULL DEFAULT '0',
  `page_touched` varchar(14) NOT NULL DEFAULT '',
  `page_latest` int(8) unsigned NOT NULL DEFAULT '0',
  `page_len` int(8) unsigned NOT NULL DEFAULT '0',
  `page_content_model` varbinary(32) DEFAULT NULL,
  `page_links_updated` varbinary(14) DEFAULT NULL,
  `page_lang` varbinary(35) DEFAULT NULL,
  PRIMARY KEY (`page_id`),
  UNIQUE KEY `name_title` (`page_namespace`,`page_title`),
  KEY `page_random` (`page_random`),
  KEY `page_len` (`page_len`),
  KEY `page_redirect_namespace_len` (`page_is_redirect`,`page_namespace`,`page_len`)
) ENGINE=MyISAM;


Example 3: Non-viewable table

If we try and look at a table that is not listed in the $wgWikiDebug_ViewableTables array we get the following:

Live Table Definition: user

Table cannot be viewed. It either does not exist, or is protected against viewing.


show_source

This extension adds a new tag that allows you to display the contents of a php file within the page. For security reasons it only allows the display of files that are added to an array of allowed files. If an extension that adds a hook for the 'php' tag has been installed (e.g. the GeSHi Syntax Highlighter) then this will be used to wrap the output, otherwise 'pre' tags will be used.

Note: The use of 'php' tags is currently disabled, as it trips up if the code contains a closing tag, as in the example below.

Syntax

<show_source file="{FileName}"></show_source>
  • {FileName} is the name of a file residing on the server.

As a security measure this extension will only allow display of tables added to the $wgWikiDebug_ViewableFiles array. Any other files are blocked.

For example, to view the file containing this extension, you would need to add the following to LocalSettings.php:

$wgWikiDebug_ViewableFiles[] = "WikiDebug.php";

Note that paths are relative to the current include path, which includes the extensions directory on this wiki, but which may not on yours.

Example 1: WikiDebug.php

Here is the current (live) contents of WikiDebug.php - the file that contains this extension.

<show_source file="WikiDebug.php"></show_source>

Live sourcecode viewer: WikiDebug.php
Last modified: 2024-01-27 12:32:18

<?php
if (!defined('MEDIAWIKI'))
	die("MediaWiki extensions cannot be run directly.");
/**
 * An extension to implement various useful debugging tools.
 *
 * @author Mark Clements <mclements at kennel17 dot co dot uk>
 * @copyright Copyright © 2006-2024, Mark Clements
 * @license http://creativecommons.org/licenses/by-sa/2.5/ cc-by-sa 2.5 or later
 * @version $Rev: 2439 $
 */
 
// The $wgVersion global variable was deprecated in MW 1.35, in favour of a new
// constant.  All code should use the constant rather than the variable, but for
// backwards compatibility we create the constant if it is not already defined.
// Note that the constant was back-ported to other release branches, but was not
// present in the *.0 release, so they do not affect the minimum version requirements
// for removing this shim.
// @back-compat MW < 1.35
	if (!defined("MW_VERSION"))
		define("MW_VERSION", $wgVersion);
 
// Setup version number
	$pMCExt_Version = '$Rev: 2439 $';
	$pMCExt_Version = substr($pMCExt_Version, 6, -2);
// Setup extension credits
	$wgExtensionCredits['other'][] = array(
		'name' => 'WikiDebug',
		'version' => "r" . $pMCExt_Version,
		'author' => 'Mark Clements',
		'description' => "A set of debugging tools for extension developers",
		'url' => 'http://www.kennel17.co.uk/testwiki/Debugging%20tools',
	);
// Tidy up
	unset($pMCExt_Version);
 
	if (!isset($wgWikiDebug_ViewableTables))
		$wgWikiDebug_ViewableTables = array();
	if (!isset($wgWikiDebug_ViewableFiles))
		$wgWikiDebug_ViewableFiles = array();
 
	$pWikiDebug_Messages = array(
	// Messages for wfWikiDebug_DescribeTable()
		'debug_table_header' => "Live Table Definition: $1",
		'debug_no_table' => "No table specified.",
		'debug_table_not_found' => "Table cannot be viewed.  It either does not "
								   . "exist, or is protected against viewing.",
		'debug_no_fields' => "No fields found.",
		'debug_index_header' => "Indexes",
		'debug_no_indexes' => "No indexes defined.",
		'debug_error_reading_tabledef' => "There was an error reading the table "
										  . "definition!",
 
	// Messages for wfWikiDebug_ShowSource() and wfWikiDebug_ShowRevision()
		'debug_file_header' => "Live source code viewer: $1",
		'debug_last_modified' => "Last modified: $1",
		'debug_no_file' => "No file specified.",
		'debug_file_blocked' => "File cannot be viewed.  It either does not exist, "
								. "or is protected against viewing.",
		'debug_file_blocked_short' => "File not found",
		'debug_file_unreadable' => "File cannot be read.",
		'debug_no_version' => "version unknown",
		'debug_version' => "rev \$1",
	);
 
// DB_PRIMARY was introduced in MW 1.36, at which point DB_MASTER was made into an
// alias.
// To maintain support for older versions of MediaWiki, if DB_PRIMARY is not defined,
// then we define it here.
// @back-compat MW < 1.36
	if (!defined("DB_PRIMARY")) {
		define("DB_PRIMARY", DB_MASTER);
	}
 
// Parser hooks.
// The ParserFirstCallInit hook was added in MediaWiki 1.12.
// For older versions, of MediaWiki, we manually call the hook function as part of
// the main extension registration function, as this hook is ignored.
	$wgHooks['ParserFirstCallInit'][] = "wfWikiDebug_RegisterParserHooks";
 
	function wfWikiDebug_RegisterParserHooks(&$Parser) {
	// Specific tags to be parsed
		$Parser->setHook( "describe_table", "wfWikiDebug_DescribeTable" );
		$Parser->setHook( "show_source", "wfWikiDebug_ShowSource" );
		$Parser->setHook( "show_revision", "wfWikiDebug_ShowRevision" );
 
	// We also accept <show_version> as an alias for <show_revision>, for
	// backwards-compatibility reasons.
		$Parser->setHook( "show_version", "wfWikiDebug_ShowRevision" );
 
		return true;
	}
 
	$wgExtensionFunctions[] = "wfWikiDebug";
	function wfWikiDebug() {
		global $wgMessageCache, $wgParser, $wgHooks;
		global $pWikiDebug_Messages, $wgWikiDebug_ViewableFiles;
 
	// Define the extension's messages.
	// If the old-style $wgMessageCache object is available then use this, as it
	// gives a better result (the messages are defined, and are editable via
	// Special:AllMessages).
	// If the $wgMessageCache object is not defined, we instead use the
	// MessagesPreLoad hook, to populate the message keys with our strings.
	// This is less good as (a) the messages won't show up for editing on
	// Special:AllPages; and (b) I suspect it is detrimental to performance.
	// However, it does mean that our messages will appear correctly in the
	// interface, and if you manually create the appropriate page in the
	// MediaWiki namespace, then the local version will be correctly used.
	// TODO: Is there a better way of handling it that resolves the above issues?
	//		 The LocalisationCacheRecache hook looked promising, but doesn't seem
	//		 to work well enough for us (it seems not to run, presumably because
	//		 a recache needs to be triggered somehow).
		if (is_object($wgMessageCache)) {
			$wgMessageCache->addMessages($pWikiDebug_Messages);
		}
		else {
			$wgHooks['MessagesPreLoad'][] = "wfWikiDebug_PreloadMessage";
		}
 
	// If this is MediaWiki < 1.12, then the ParserFirstCallInit hook does not exist,
	// therefore we manually call our hook registration function with the global
	// Parser object.
	// @back-compat MW < 1.12
		if (version_compare(MW_VERSION, '1.12', '<')) {
			wfWikiDebug_RegisterParserHooks($wgParser);
			wfWikiDebug_SetupParserFunctions($wgParser);
		}
 
	// Tidy all paths in the list of viewable files, to ensure that directory
	// separators are treated in a system-agnostic fashion.
		foreach ($wgWikiDebug_ViewableFiles as $Key => $Path)
			$wgWikiDebug_ViewableFiles[$Key] = pwfWikiDebug_TidyPath($Path);
	}
 
	function wfWikiDebug_DescribeTable($Input, $Args, $Parser = null) {
		global $wgParser;
		global $wgWikiDebug_ViewableTables;
 
	// The $Parser argument was added in MediaWiki 1.5.8.  Therefore, to keep
	// compatibility with older versions, we need to default the function argument
	// to null and default to the global $wgParser argument if it is not set.
	// @back-compat MW < 1.5.8
		if (!isset($Parser))
			$Parser = $wgParser;
 
		WikiDebug_DisableCache($Parser);
 
		if (isset($Args['table']))
			$TableName = $Args['table'];
		else
			$TableName = "";
		$Output = "<h3>"
				. WikiDebug_Msg('debug_table_header', $TableName)
				. "</h3>\n";
 
		if ($TableName == "")
			$Output .= WikiDebug_Msg('debug_no_table');
 
		elseif (!in_array($TableName, $wgWikiDebug_ViewableTables))
			$Output .= WikiDebug_Msg('debug_table_not_found');
 
		else {
 
			$DB = wfGetDB(DB_PRIMARY);
 
			if ($DB->tableExists($TableName)) {
 
			// Get the requested display mode.
				$Display = "sql";
				if (isset($Args['display']))
					$Display = strtolower(trim($Args['display']));
 
			// Output the data according to the display mode.
				switch ($Display) {
					case "tables":
					case "table":
					// Get field data from table
						$objResult = $DB->query("DESCRIBE "
												. $DB->tableName($TableName) . ";");
						$Fields = array();
						while ( $row = $objResult->fetchObject() )
							$Fields[] = $row;
 
						$objResult->free();
 
					// If required, get index data from table
						$ShowIndexes = true;
						if (isset($Args['showindexes'])) {
							switch (strtolower(trim($Args['showindexes']))) {
								case "0":
								case "false":
									$ShowIndexes = false;
							}
						}
						$Indexes = array();
						if ($ShowIndexes) {
							$objResult = $DB->query("SHOW INDEX FROM "
													. $DB->tableName($Args['table'])
													. ";");
							while ( $row = $objResult->fetchObject() )
								$Indexes[] = $row;
 
							$objResult->free();
						}
 
						if (count($Fields) == 0)
							$Output .= WikiDebug_Msg('debug_no_fields');
 
						else {
							$Output .= '<table border="1" cellpadding="5" '
									 . 'cellspacing="2"><tr>';
							foreach ($Fields[0] as $RowHeader => $Row)
								$Output .= '<th>' . $RowHeader . '</th>';
							foreach ($Fields as $Row) {
								$Output .= '<tr>';
								foreach ($Row as $RowValue) {
									if ($RowValue === null || $RowValue == "")
										$RowValue = "&nbsp;";
									$Output .= '<td>' . $RowValue . '</td>';
								}
								$Output .= '</tr>';
							}
							$Output .= '</table>';
 
							if ($ShowIndexes) {
								$Output .= "<h3>"
										 . WikiDebug_Msg('debug_index_header')
										 . "</h3>\n";
 
								if (count($Indexes) > 0) {
									$Output .= '<table border="1" cellpadding="5" '
											 . 'cellspacing="2"><tr>';
									foreach ($Indexes[0] as $RowHeader => $Row) {
										if ($RowHeader != "Table")
											$Output .= '<th>' . $RowHeader . '</th>';
									}
									foreach ($Indexes as $Row) {
										$Output .= '<tr>';
										foreach ($Row as $RowHeader => $RowValue) {
											if ($RowHeader != "Table") {
												if ($RowValue === null
													|| $RowValue == "")
												{
													$RowValue = "&nbsp;";
												}
												$Output .= '<td>' . $RowValue
														 . '</td>';
											}
										}
										$Output .= '</tr>';
									}
									$Output .= '</table>';
								}
								else {
									$Output .= "&nbsp;&nbsp;&nbsp;"
											 . WikiDebug_Msg('debug_no_indexes');
								}
							}
						}
 
						break;
 
					case "sql":
					default:
					// Get field data from table
						$objResult = $DB->query("SHOW CREATE TABLE "
												. $DB->tableName($TableName) . ";");
						if ($objResult !== false && $row = $objResult->fetchObject())
						{
							$SQL = "";
							foreach ($row as $key => $val) {
								if ($key = "Create Table") {
									$SQL = htmlspecialchars($val, ENT_COMPAT,
															"UTF-8");
								}
							}
 
							$Removables = array(
								"/character set .* /Ui",
								"/collate .* /Ui",
								"/ ?DEFAULT CHARSET=.*( |$)/Ui",
								"/ ?COLLATE=.*( |$)/Ui",
								"/ ?AUTO_INCREMENT\s*=\s*([0-9])+/",
							);
							$SQL = preg_replace($Removables, "", $SQL);
 
							$Search = array(
								"/^CREATE TABLE " . $DB->tableName($TableName)
									. "/i",
								"/TYPE=(.*)( |$)/Ui",
								"/^/m",		// Space at the start of the line, to
											// ensure <pre> block works properly.
								"/([^;])$/",
							);
							$Replace = array(
								"CREATE TABLE IF NOT EXISTS `" . $TableName . "`",
								"ENGINE=$1$2",
								" ",
								"$1;",
							);
							$SQL = preg_replace($Search, $Replace, $SQL);
							$Output .= $SQL . "\n";
						}
 
						else
							$Output .= WikiDebug_Msg('debug_error_reading_tabledef');
 
						if ($objResult !== false)
							$objResult->free();
				}
			}
 
			else
				$Output .= WikiDebug_Msg('debug_table_not_found');
		}
 
		$Output = '<div style="border: 1px solid #AAAAAA; '
				. 'background-color: #EEEEEE; padding: 1em;">' . $Output . '</div>';
		return $Output;
	}
 
	function wfWikiDebug_ShowSource($Input, $Args, $Parser = null) {
		global $wgOut, $wgParser;
 
	// The $Parser argument was added in MediaWiki 1.5.8.  Therefore, to keep
	// compatibility with older versions, we need to default the function argument
	// to null and default to the global $wgParser argument if it is not set.
	// @back-compat MW < 1.5.8
		if (!isset($Parser))
			$Parser = $wgParser;
 
		WikiDebug_DisableCache($Parser);
		$AllowPHPOutput = false;
		$OutputIsAlreadyEscaped = false;
 
		if (isset($Args['file']))
			$FileName = $Args['file'];
		else
			$FileName = "";
		$Header = WikiDebug_Msg('debug_file_header', $FileName);
 
		if ($FileName == "") {
			$Output = WikiDebug_Msg('debug_no_file');
			$OutputIsAlreadyEscaped = true;
		}
 
		else {
			$FilePath = pwfWikiDebug_GetRealPath($FileName);
			if (!$FilePath) {
				$Output = WikiDebug_Msg('debug_file_blocked');
				$OutputIsAlreadyEscaped = true;
			}
 
			elseif (($File = @fopen($FilePath, "r", true)) === false) {
				$Output = WikiDebug_Msg('debug_file_unreadable');
				$OutputIsAlreadyEscaped = true;
			}
 
			else {
			// Get last modified date
				$ModDate = date("Y-m-d h:i:s", filemtime($FilePath));
				$Header .= "<br><span style=\"font-size: 80%; "
						 . "font-weight: normal;\">"
						 . WikiDebug_Msg('debug_last_modified', $ModDate)
						 . "</span>";
 
			// Get length of file.
				fseek($File, 0, SEEK_END);
				$Length = ftell($File);
				fseek($File, 0, SEEK_SET);
 
			// Get contents of file.
				$Output = fread($File, $Length);
 
			// We don't care about read errors and we don't report them.  Therefore,
			// convert any error results to an empty string.
				if (!is_string($Output))
					$Output = "";
 
			// If it was a PHP file and was successfully read, then enable the
			// PHP syntax-highlighter (if installed).
			// We detect this by seeing if the file contains the opening PHP tag.
				if (strpos($Output, "<?") !== false)
					$AllowPHPOutput = true;
 
			// Convert file to UTF-8, if we detect that it is in a different
			// encoding.
			// This can only be done if the multi-byte extension is enabled.  If not,
			// we currently just output the file in its raw format, which may result
			// in encoding errors.
			// TODO: Is there a better way of handling situations where we can't
			//		 detect the encoding (e.g. error message)?  Are there alternative
			//		 detection methods (e.g. built into MediaWiki)?
				if (function_exists("mb_detect_encoding")) {
				// The supported encodings are UTF-8 and CP1252.
				// The ordering is important - if these are the other way round then
				// CP1252 will always be detected as it covers all code points.
				// Note that CP1252 is a superset of ISO-8859-1, so this will also
				// correctly detect ISO-8859-1 encoding.
				// TODO: Are there other encodings that should be included in this
				//		 list?
					$SupportedEncodings = 'UTF-8, CP1252';
 
				// Detect the encoding.  Note that, because CP1252 is a
				// single-byte encoding with no invalid values, we won't ever fail
				// to detect something.
					$Encoding = mb_detect_encoding($Output, $SupportedEncodings,
												   true);
 
				// Perform the conversion.
				// TODO: Is it safe to assume our output is always UTF-8?
					$Output = iconv($Encoding, "UTF-8", $Output);
				}
			}
		}
 
		$Header = "<h3>" . $Header . "</h3>\n";
 
		if ($AllowPHPOutput && isset($Parser->mTagHooks['php'])) {
		// This can easily use more than the standard available memory, so remove
		// limit.
			$MemLimit = ini_get('memory_limit');
			ini_set('memory_limit', -1);
			$Output = $Header . call_user_func($Parser->mTagHooks['php'],
											   $Output, array(), $Parser);
			ini_set('memory_limit', $MemLimit);
		}
		else {
			if (!$OutputIsAlreadyEscaped)
				$Output = htmlspecialchars($Output, ENT_COMPAT, "UTF-8");
 
			$Output = "<pre>" . $Header . $Output . "</pre>";
		}
 
		return $Output;
	}
 
	function wfWikiDebug_ShowRevision($Input, $Args, $Parser = null) {
		global $wgOut, $wgParser;
 
	// The $Parser argument was added in MediaWiki 1.5.8.  Therefore, to keep
	// compatibility with older versions, we need to default the function argument
	// to null and default to the global $wgParser argument if it is not set.
	// @back-compat MW < 1.5.8
		if (!isset($Parser))
			$Parser = $wgParser;
 
		WikiDebug_DisableCache($Parser);
 
		if (isset($Args['file']))
			$FileName = $Args['file'];
		else
			$FileName = "";
 
		if ($FileName == "")
			$Output = WikiDebug_Msg('debug_no_file');
 
		else {
			$FilePath = pwfWikiDebug_GetRealPath($FileName);
			if (!$FilePath)
				$Output = WikiDebug_Msg('debug_file_blocked');
 
			elseif (($File = @fopen($FilePath, "r", true)) === false)
				$Output = WikiDebug_Msg('debug_file_unreadable');
 
			else {
			// Get length of file.
				fseek($File, 0, SEEK_END);
				$Length = ftell($File);
			    fseek($File, 0, SEEK_SET);
 
			// Get contents of file.
				$Contents = fread($File, $Length);
 
			// We don't care about read errors and we don't report them.  Therefore,
			// convert any error results to an empty string.
				if (!is_string($Contents))
					$Contents = "";
 
				$VersionRegex = '/(?:\$Rev:|\$LastChangedRevision:|\$Revision:|) '
							  . '?([0-9]+) ?\$/Ui';
				$Result = preg_match($VersionRegex, $Contents, $matches);
				if (count($matches) > 0)
					$Output = WikiDebug_Msg('debug_version', $matches[1]);
				else
					$Output = WikiDebug_Msg('debug_no_version');
			}
		}
 
		return "<i>(" . $Output . ")</i>";
	}
 
/////////////////////////// PARSER FUNCTIONS ///////////////////////////
// MW 1.7 and above, only.
 
// SETUP FUNCTIONS
 
// The ParserFirstCallInit hook was added in MediaWiki 1.12.
// For older versions, of MediaWiki, we manually call the hook function as part of
// the main extension registration function, as this hook is ignored.
	$wgHooks['ParserFirstCallInit'][] = 'wfWikiDebug_SetupParserFunctions';
	$wgHooks['LanguageGetMagic'][]	  = 'wfWikiDebug_SetupLanguageMagic';
 
	function wfWikiDebug_SetupParserFunctions(&$Parser) {
		$Parser->setFunctionHook('IfExtensionPresent',
								 'wfWikiDebug_IfExtensionPresent');
        return true;
	}
 
	function wfWikiDebug_SetupLanguageMagic(&$MagicWords) {
	// Add the magic word.
	// Set first array element to 0 to indicate non-case-sensitivity.
	// Additional elements are synonyms.
		$MagicWords['IfExtensionPresent'] = array(1, 'IfExtensionPresent');
 
	// Return true, so other parser hooks execute properly.
		return true;
	}
 
// INDIVIDUAL PARSER FUNCTIONS
 
	function wfWikiDebug_IfExtensionPresent($Parser, $Param1 = "", $Param2 = "",
											$Param3 = "")
	{
		global $wgExtensionCredits;
 
		$Matched = false;
		foreach ($wgExtensionCredits as $Type => $Extensions) {
			foreach ($Extensions as $Extension) {
				if (isset($Extension['name']) && $Extension['name'] == $Param1) {
					$Matched = true;
					break 2;
				}
			}
		}
 
		if ($Matched)
			return $Param2;
		else
			return $Param3;
	}
 
/////////////////////////// SUPPORT FUNCTIONS ///////////////////////////
 
// pwfWikiDebug_GetRealPath()
// Takes a $FileName argument, and checks that (a) the file exists in the
// include paths and (b) it is in the list of viewable files.
// If it fails either of these tests, the function returns false, indicating that
// the file cannot be viewed (there is deliberately no distinction between the
// different reasons why it may not be viewable).
// If it succeeds, and the file exists and is on the white list, then the real
// path to the file is returned.
// The function does not check whether the file is readable by the web user.
	function pwfWikiDebug_GetRealPath($FileName) {
		$FileName = pwfWikiDebug_TidyPath($FileName);
 
	// First, check that the file exists in the include path.  We do this first
	// as it will allow us to use realpath() in the CanViewFile() function if we
	// need to (currently it isn't used, but at one point it was thought to be
	// required and I'm not sure that I won't change my mind again in the future).
		$FilePath = pwfWikiDebug_file_exists_incpath($FileName);
		if (!$FilePath)
			return false;
 
	// If file exists, check it is viewable.
		if (!pwfWikiDebug_CanViewFile($FileName))
			return false;
 
		return $FilePath;
	}
 
// pwfWikiDebug_file_exists_incpath()
// For relative paths, checks if the file exists in the include path and returns
// the full path of where it was found if it does, or false if not.
// For absolute paths, it doesn't check the include path - it just returns the
// supplied $File path if it exists, or false if not.
// Function was modified from file_exists_incpath() - credits follow:
/**
 * Check if a file exists in the include path
 *
 * @version     1.2.1
 * @author      Aidan Lister <aidan@php.net>
 * @link        http://aidanlister.com/repos/v/function.file_exists_incpath.php
 * @param       string     $File       Name of the file to look for
 * @return      mixed      The full path if file exists, FALSE if it does not
 */
	function pwfWikiDebug_file_exists_incpath($File) {
 
	// If this is an absolute path, then we don't need to resolve it in terms
	// of the include path - just check whether the file exists.
		if (in_array(substr($File, 0, 1), array("\\", "/"))) {
			if (file_exists($File))
				return $File;
		}
 
	// Otherwise it is a relative path, so look for it in each of the include
	// directories, selecting the first directory in which the file is found.
		else {
			$arrPaths = explode(PATH_SEPARATOR, ini_get('include_path'));
			foreach ($arrPaths as $Path) {
			// Skip over any blank paths.
				if ($Path != "") {
				// Formulate the absolute path, and if the file exists there,
				// return it.
					$FullPath = $Path . "/" . $File;
					if (file_exists($FullPath))
						return $FullPath;
				}
			}
		}
 
	// If the file was not found in the path, return false.
		return false;
	}
 
// pwfWikiDebug_CanViewFile()
// Returns true if the named file is viewable according to the settings in
// $wgWikiDebug_ViewableFiles.  This is an array of files/directories.  If the
// entry is a directory, all of it's contents, including sub-directories, are
// viewable.
	function pwfWikiDebug_CanViewFile($FileName) {
		global $wgWikiDebug_ViewableFiles;
 
	// If the supplied filename is in the list of viewable files, then return
	// true.
		if (in_array($FileName, $wgWikiDebug_ViewableFiles))
			return true;
 
	// Otherwise, if there are any directories in the list of viewable files,
	// return true if this file is within one of those directories.
		foreach ($wgWikiDebug_ViewableFiles as $ViewableFile) {
		// Ensure the path ends in a directory separator.  This doesn't cause
		// any problems if the file is not a directory, as we separately test
		// that the full path exists.
			if (substr($ViewableFile, -1) != "/")
				$ViewableFile .= "/";
 
		// If the path of the $ViewableFile (including trailing backslash) matches
		// the start of the file we are checking permission for, then the file is
		// inside the viewable directory, so we return true.
		// TODO: We could be a bit smarter about matching files which are
		//		 directories (i.e. allowing them to be specified with or without
		//		 a trailing slash) but this isn't required at the moment.  It needs
		//		 fixing at the point where we have any tags that can operate on
		//		 directories (e.g. <file_list> to provide a directory listing, or
		//		 <zip> tag to zip up the contents of a directory).
		//		 (Actually, as far as I can tell this is only a problem if the
		//		 ViewableFiles array contains entries with trailing slashes, as the
		//		 other way round will still result in a match - and this can be
		//		 fixed in wfWikiDebug if necessary.  Some more thought required,
		//		 though).
			if (strpos($FileName, $ViewableFile) === 0)
				return true;
		}
 
		return false;
	}
 
// pwfWikiDebug_TidyPath()
// Converts Windows-like back-slash characters to Unix-like forward-slashes.
// Forward slashes work on both systems, so are always safe to use, and this
// conversion ensures that any file paths are treated in a system-agnostic way.
	function pwfWikiDebug_TidyPath($Path) {
		return str_replace("\\", "/", $Path);
	}
 
	function wfWikiDebug_PreloadMessage($Title, &$Message) {
		global $pWikiDebug_Messages;
 
	// As message names are page titles, MW normalises them to have an upper-case
	// first character.
	// We therefore need to force them back to lower-case in order to check against
	// our array.
		$Title = strtolower($Title);
 
		if (isset($pWikiDebug_Messages[$Title])) {
			$Message = $pWikiDebug_Messages[$Title];
		}
 
		return true;
	}
 
// WikiDebug_Msg()
// Returns the text of the specified system message, using any additional arguments
// for variable replacement.
// Functionally equivalent to wfMsg(), which is the original way of handling this
// in MediaWiki.  However, this was deprecated in favour of wfMessage() and the new
// Message class in MW 1.17 and removed altogether in MW 1.27.
// This wrapper function calls wfMessage() if available, otherwise wfMsg(), and
// in both cases returns the expanded string (not a Message object).  As well as
// ensuring compatibility across MediaWiki versions, using this function simplifies
// our code as it doesn't need to explicitly call text() to get the output string.
// Note that the only difference between this and wfMsg() is that it benefits from
// some bug fixes that were applied to the wfMessage() implementation, namely that
// variable substitution now happens before template expansion, so constructs such
// as {{PLURAL:$1|egg|eggs}} now work as expected.
	function WikiDebug_Msg($Key) {
		$Args = func_get_args();
 
		if (function_exists("wfMessage")) {
			$objMessage = call_user_func_array("wfMessage", $Args);
			return $objMessage->text();
		}
		else {
			return call_user_func_array("wfMsg", $Args);
		}
	}
 
// WikiDebug_DisableCache()
// Prevents the results of the current parse from being cached.  This makes most
// sense in the context of parsing a full page, but may make sense in other parsing
// contexts, too.
// Primarily provided for backwards-compatibility reasons, but may still be useful
// when older MW versions no longer need to be supported, for clarity and brevity.
	function WikiDebug_DisableCache($objParser) {
	// Prior to MW 1.28, you simply called disableCache() on the Parser instance in
	// order to disable the cache.  In MW 1.28 this was deprecated and it was
	// ultimately removed in MW 1.35.
	// Note that we need a version check here as this can't be feature-detected.  All
	// relevant functions have existed since at least MW 1.17 and we don't want to
	// call disableCache() after deprecation, as it will emit notices.
	// TODO: Although the deprecation was made in MW 1.28, it is possible that the
	//		 new technique worked correctly in older MW versions.  It may therefore
	//		 be possible to turn this into a feature-detection check after all.
	// @back-compat MW < 1.28
		if (version_compare(MW_VERSION, "<", "1.28")) {
			$objParser->disableCache();
		}
	// In more recent MediaWiki versions, you need to first retrieve the ParserOutput
	// object from the Parser and then set the cache expiry to zero seconds.
	// TODO: Although the deprecation was made in MW 1.28, it is possible that the
	//		 new technique worked correctly in older MW versions.  It may therefore
	//		 be possible to turn this into a feature-detection check after all.
		else {
			$objParserOutput = $objParser->getOutput();
			$objParserOutput->updateCacheExpiry(0);
		}
	}


Example 2: Non-viewable file

Here is an attempt to view LocalSettings.php. Because this file is not in the array of allowed files, you are unable to see it's contents:

<show_source file="LocalSettings.php"></show_source>

Live sourcecode viewer: LocalSettings.php

File cannot be viewed. It either does not exist, or is protected against viewing.