or
) for the code.
* For example, use style="border: 0px none white;" to
* disable the frame around the code. Corresponds to
* GeSHI's set_overall_style().
*
* EXAMPLES
*
* Include a file from the local file system:
*
* Include a remote file:
*
* Include a local fragment of HTML:
*
* Include a local file with syntax highlighting:
*
*
* DEPENDENCIES
*
* For highlight support you will need to enable SyntaxHighlight_GeSHi
* which is included in MW since v1.21. see
* https://www.mediawiki.org/wiki/Extension:SyntaxHighlight#Installation
*
* AUTHOR
*
* Noah Spurrier
* http://www.noah.org/wiki/MediaWiki_Include
* Matthieu Moy
* https://gitlab.com/MediawikiInclude/include/tree/master
* Edgar Soldin
* https://github.com/edeso/SecureInclude/
*
* @package extensions
* @version 8
* @copyright Copyright 2008 @author Noah Spurrier
* @copyright Copyright 2013 @author Matthieu Moy
* @copyright Copyright 2019 @author Edgar Soldin
* @license GPLv3 or later
*
*/
if (! defined('MEDIAWIKI')) {
die('This file is a MediaWiki extension, it is not a valid entry point');
}
# needed since v1.39
use MediaWiki\SyntaxHighlight\SyntaxHighlight;
/* Prevent register_global attacks */
$wg_include_allowed_features = Null;
$wg_include_allowed_parent_paths = Null;
$wg_include_allowed_url_regexp = Null;
$wg_include_disallowed_regex = Null;
$wg_include_disallowed_url_regexp = Null;
function ef_include_onParserFirstCallInit(Parser $parser){
// Create a function hook associating the magic word with the function
$parser->setHook('include', "ef_include_render");
$parser->setHook('shell', "ef_include_shell");
$parser->setHook('php', "ef_include_php");
global $wgGroupPermissions;
$wgGroupPermissions['secureinclude']['secureinclude-scripting'] = true;
}
/**
* ef_include_path_in_regex_list
*
* This returns true if the needle_path matches any regular expression in haystack_list.
* This returns false if the needle_path does not match any regular expression in haystack_list.
* This returns false if the haystack_list is not set or contains no elements.
*
* @param mixed $haystack_list
* @param mixed $needle_path
*
* @access public
* @return boolean
*/
function ef_include_path_in_regex_list($haystack_list, $needle_path)
{
// polymorphism. Allow either a string or an Array of strings to be passed.
if (is_string($haystack_list)) {
$haystack_list = Array(
$haystack_list
);
}
// no list, nothing allowed
if ( !is_array($haystack_list) || count($haystack_list) < 1) {
return false;
}
foreach ($haystack_list as $p) {
if (preg_match($p, $needle_path)) {
return true;
}
}
return false;
}
/**
* ef_include_path_in_allowed_list
*
* This returns true if the given needle_path is a subdirectory of any
* directory listed in haystack_list. Similar to
* ef_include_path_in_regex_list, but does not not allow regular
* expression, in $haystack_list.
*
* @param mixed $haystack_list
* @param mixed $needle_path
* @access public
* @return boolean
*/
function ef_include_path_in_allowed_list($haystack_list, $needle_path)
{
// polymorphism. Allow either a string or an Array of strings to be passed.
if (is_string($haystack_list)) {
$haystack_list = Array(
$haystack_list
);
}
// no list, nothing allowed
if ( !is_array($haystack_list) || count($haystack_list) < 1) {
return false;
}
foreach ($haystack_list as $path) {
$path = realpath($path);
// path does not exist
if ( !$path ) continue;
// succeed only if requested path starts with an allowed absolute path
if ( strpos( $needle_path, $path ) === 0 ) return true;
}
return false;
}
/* helper function for ef_include_is_regexp */
function ef_include_trap_error()
{
global $wg_include_error_trapped;
$wg_include_error_trapped = true;
}
/**
* ef_include_is_regexp
*
* Check whether $reg_exp is a valid regular expression (including
* delimiters, like /foo/).
*
* @param string $reg_exp
* The expression to check
*
* @access public
* @return boolean
*/
function ef_include_is_regexp($reg_exp)
{
global $wg_include_error_trapped;
$wg_include_error_trapped = false;
$sPREVIOUSHANDLER = set_error_handler('ef_include_trap_error');
preg_match($reg_exp, '');
restore_error_handler($sPREVIOUSHANDLER);
return ! $wg_include_error_trapped;
}
/**
* ef_include_match
*
* Check whether a string matches with a regular expression (either a
* string or a /regexp/)
*
* @param string $regexp_or_string
* The expression to match with
* @param string $to_match
* String to match
*
* @access public
* @return boolean
*/
function ef_include_match($regexp_or_string, $to_match)
{
if (ef_include_is_regexp($regexp_or_string))
return preg_match($regexp_or_string, $to_match);
else
return $to_match === $regexp_or_string;
}
/**
* ef_include_extract_line_range_maybe
*
* Extract a line range from a multi-line string.
*
* @param string $output
* Multi-line string from which to do the extraction
* @param string $lines
* Line range to extract
* @param integer $startline
* If not set before calling the function,
* this variable is set to the first line extracted.
*
* @access public
* @return boolean
*/
function ef_include_extract_line_range_maybe($output, $argv, &$startline)
{
if ((! isset($argv['lines'])) && (! isset($argv['after'])) && (! isset($argv['before'])) && (! isset($argv['from'])) && (! isset($argv['to'])))
return $output;
$output_a = explode("\n", $output);
if (isset($argv['lines'])) {
$array = ef_include_parse_range($argv['lines'], count($output_a));
} else {
$array = range(1, count($output_a));
}
$computed_startline = - 1;
$i = 0;
$in_regexp = ! isset($argv['after']) && ! isset($argv['from']);
foreach ($array as $line) {
// $array is indexed from 1, but $output_X are indexed
// from 0, hence the -1.
$index = $line - 1;
if (isset($argv['from']) && ef_include_match($argv['from'], $output_a[$index]))
$in_regexp = true;
if (isset($argv['before']) && ef_include_match($argv['before'], $output_a[$index]))
break;
if ($in_regexp) {
$output_b[$i] = $output_a[$index];
$i ++;
if ($computed_startline == - 1)
$computed_startline = $line;
}
if (isset($argv['after']) && ef_include_match($argv['after'], $output_a[$index]))
$in_regexp = true;
if (isset($argv['to']) && ef_include_match($argv['to'], $output_a[$index]))
break;
}
if ($i == 0)
return "";
// When extracting lines X-Y, start counting at X unless asked
// otherwise.
if (! isset($startline)) {
$startline = $computed_startline;
}
$output = join("\n", $output_b);
return $output;
}
/**
* ef_include_parse_range
*
* Parse a line-range string, and return a list of line numbers. For
* example:
*
* "42" => (42)
* "1,4,12" => (1 4 12)
* "1,4-12" => (1 4 5 6 7 8 9 10 11 12)
* "-3" => (1 2 3)
* "3-" => (3 4 5 ... untill end of file)
*
* @param string $range
* The range string to parse.
* @param integer $last_lineno
* Number of the last line in file.
*
* @access public
* @return boolean
*/
function ef_include_parse_range($range, $last_lineno)
{
$res = array();
$array = explode(",", $range);
foreach ($array as $elem) {
if (preg_match('/^ *([0-9]+) *$/', $elem, $matches)) {
$res[] = intval($matches[1]);
} else if (preg_match('/^ *([0-9]*) *- *([0-9]*) *$/', $elem, $matches)) {
if ($matches[1] == "") {
// lines="-12" mean start from first line.
$start = 1;
} else {
$start = intval($matches[1]);
}
if ($matches[2] == "") {
// lines="42-" mean finish at last line.
$end = $last_lineno;
} else {
$end = intval($matches[2]);
}
if ($start < 1)
$start = 1;
if ($end > $last_lineno)
$end = $last_lineno;
for ($i = $start; $i <= $end; $i ++) {
$res[] = $i;
}
}
}
return $res;
}
/**
* ef_include_geshi_syntax_highlight
*
* Apply syntax-highlighting using GeSHI.
*
* @param string $output
* Text to syntaxe-highlight.
* @param array $argv
* Parameters given to the tag.
*
* @access public
* @return boolean
*/
// function ef_include_geshi_syntax_highlight($output, $argv)
// {
// if (preg_match('/([a-zA-Z0-9+]+)/', $argv['highlight'], $matches)) {
// // If the language string contains garbage but still matches a
// // language name somewhere, take just the language name.
// $lang = $matches[1];
// } else {
// $lang = "c";
// }
// $geshi = new GeSHi($output, $lang);
// if (isset($argv['nopre'])) {
// $geshi->set_header_type(GESHI_HEADER_NONE);
// } else {
// $geshi->set_header_type(GESHI_HEADER_PRE);
// }
// if (isset($argv['style'])) {
// $geshi->set_overall_style(htmlspecialchars($argv['style']));
// }
// if (isset($argv['select'])) {
// $array = ef_include_parse_range($argv['select'], substr_count($output, "\n") + 1);
// $geshi->highlight_lines_extra($array);
// }
// if (isset($argv['linenums'])) {
// $geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);
// if (isset($argv['linestart'])) {
// // intval to make sure we don't pass arbitrary
// // string to geshi for security reasons.
// $geshi->start_line_numbers_at(intval($argv['linestart']));
// }
// }
// $output = $geshi->parse_code();
// return $output;
// }
/**
* ef_include_render_iframe
*
* Generate an iframe including the remote code.
*
* @param array $argv
* Parameters given to the tag.
*
* @access public
* @return boolean
*/
function ef_include_render_iframe($argv)
{
if (isset($argv['frameborder']))
$frameborder = htmlspecialchars($argv['frameborder']);
else
$frameborder = '1';
if (isset($argv['scrolling']))
$scrolling = htmlspecialchars($argv['scrolling']);
else
$scrolling = 'yes';
if (isset($argv['width']))
$width = htmlspecialchars($argv['width']);
else
$width = '100%';
if (isset($argv['height']))
$height = htmlspecialchars($argv['height']);
else
$height = '100%';
return '';
}
/**
* ef_include_check_remote_url
*
* Checks whether a remote URL is allowed.
*
* @param string $src_path
* URL to check.
*
* @access public
* @return mixed (True if the URL is allowed, string error message
* otherwise)
*/
function ef_include_check_remote_url($src_path)
{
global $wg_include_allowed_features;
global $wg_include_disallowed_url_regexp;
global $wg_include_allowed_url_regexp;
if ( @$wg_include_allowed_features['remote'] !== true )
return "Not allowed to include remote URLs!";
// Errors in parse_url generating a warning also return
// false. Since we check for false right after, we don't
// need/want to see the warning.
$old_report_level = error_reporting(E_ERROR);
$parsed = parse_url($src_path);
error_reporting($old_report_level);
if ($parsed === false or ! isset($parsed['scheme']) or $parsed['scheme'] == "")
return htmlspecialchars($src_path) . " does not look like a URL, and doesn't exist as a file.";
// file:// URLs would be _dangerous_, since they bypass
// the $wg_include_allowed_parent_paths test, and
// therefore allow things like file:///etc/passwd.
// Be safe: fuzzy match for anything containing 'file'.
if (preg_match('/file/', $parsed['scheme']))
return "file:// URLs not allowed.";
if (ef_include_path_in_regex_list($wg_include_disallowed_url_regexp, $src_path))
return "URL " . htmlspecialchars($src_path) . " in disallowed list.";
if (! ef_include_path_in_regex_list($wg_include_allowed_url_regexp, $src_path))
return "URL " . htmlspecialchars($src_path) . " not in allowed list.";
// URL is allowed.
return True;
}
/**
* ef_include_check_local_file
*
* Checks whether a local file can be included.
*
* @param string $src_path
* path name to check.
*
* @access public
* @return mixed (True if the path is allowed, string error message
* otherwise)
*/
function ef_include_check_local_file($src_path)
{
global $wg_include_allowed_features;
global $wg_include_allowed_parent_paths;
global $wg_include_disallowed_regex;
// general permission
if (! $wg_include_allowed_features['local'])
return "Not allowed to include local files.";
// openable?
if ((! is_readable($src_path)) || is_dir($src_path)) {
// purposely the same message for unreadable files and
// directories, to avoid leaking information.
return "Cannot open file '" . htmlspecialchars($src_path) . "'.";
}
$src_path = realpath($src_path);
// in path list?
if (! ef_include_path_in_allowed_list($wg_include_allowed_parent_paths, $src_path)) {
return "'" . htmlspecialchars($src_path) . "' is not a child of any path in \$wg_include_allowed_parent_paths. '" . htmlspecialchars(implode('; ', array_map(function ($val) {
$path = realpath($val);
return $path ? $path : $val . ' [not found]';
}, $wg_include_allowed_parent_paths))) . "'";
}
// in regex list?
if (ef_include_path_in_regex_list($wg_include_disallowed_regex, $src_path)) {
return htmlspecialchars($src_path) . " matches a pattern in \$wg_include_disallowed_regex.";
}
// Local file is allowed.
return True;
}
/**
* ef_include_render
*
* This is called automatically by the MediaWiki parser extension system.
* This does the work of loading a file and returning the text content.
* $argv is an associative array of arguments passed in the tag as
* attributes.
*
* @param mixed $input
* string
* @param mixed $argv
* associative array
* @param mixed $parser
* Parser
* @param mixed $frame
* PPFrame
*
* @access public
* @return string
*/
function ef_include_render($input, $argv, $parser, $frame)
{
global $wg_include_highlighter_package;
global $wg_include_allowed_features;
global $wg_include_allowed_parent_paths;
global $wg_include_disallowed_regex;
global $wg_include_allowed_url_regexp;
global $wg_include_disallowed_url_regexp;
// $argv['nocache'] = true;
// http://www.mediawiki.org/wiki/Extensions_FAQ#How_do_I_disable_caching_for_pages_using_my_extension.3F
if (array_key_exists('nocache', $argv)) {
$parser->getOutput()->updateCacheExpiry(0);
}
$error_msg_prefix = "ERROR in " . htmlspecialchars(basename(__FILE__)) . ": ";
foreach ($argv as &$a) {
if (isset($a)) {
$a = $parser->recursivePreprocess($a, $frame);
}
}
if (! isset($argv['src'])) {
return ef_include_get_errors(" tag is missing 'src' attribute.");
}
// iframe option...
// Note that this does not check that the iframe src actually exists.
// I also don't need to check against $wg_include_allowed_parent_paths or $wg_include_disallowed_regex
// because the iframe content is loaded by the web browser and so security
// is handled by whatever server is hosting the src file.
if (isset($argv['iframe'])) {
if (! $wg_include_allowed_features['iframe'])
return ef_include_get_errors("'iframe' feature not activated for include.");
return ef_include_render_iframe($argv);
}
// if (isset($argv['shell'])) {
// if (! $wg_include_allowed_features['shell'])
// return ef_include_get_errors("'shell' feature not activated for include.");
// // $result = Shell::command( $argv['src'] )
// // // ->environment( [ 'MW_CPU_LIMIT' => '0' ] )
// // // ->limits( [ 'time' => 300 ] )
// // ->execute();
// // $exitCode = $result->getExitCode();
// // $output = $result->getStdout();
// // $error = $result->getStderr();
// $cmd = "sh -c " . escapeshellarg($argv['src']) . "";
// exec($cmd, $output, $return_var);
// return $output;
// }
// cat file from SVN repository...
if (isset($argv['svncat'])) {
if (! $wg_include_allowed_features['svncat'])
return ef_include_get_errors("'svncat' feature not activated for include.");
$cmd = "svn cat " . escapeshellarg($argv['src']);
exec($cmd, $output, $return_var);
// If plain 'svn cat' fails then try again using 'svn cat
// --config-dir=/tmp'. Plain 'svn cat' worked fine for months
// then just stopped.
// Adding --config-dir=/tmp is a hack that fixed it, but
// I only want to use it if necessary. I wish I knew what
// the root cause was.
if ($return_var != 0) {
$cmd = "svn cat --config-dir=/tmp " . escapeshellarg($argv['src']);
exec($cmd, $output, $return_var);
}
if ($return_var != 0)
return ef_include_get_errors("could not read the given src URL using 'svn cat'.\ncmd: $cmd\nreturn code: $return_var\noutput: " . join("\n", $output));
$output = join("\n", $output);
} else // load file from URL (may be a local or remote URL)...
{
if ( filter_var($argv['src'], FILTER_VALIDATE_URL) ) {
$msg = ef_include_check_remote_url($argv['src']);
if (! ($msg === True))
return ef_include_get_errors($msg);
} else {
$msg = ef_include_check_local_file($argv['src']);
if (! ($msg === True))
return ef_include_get_errors($msg);
}
// We will generate a clean error message in case fetching a
// remote URL fails. Don't generate extra warnings.
$old_report_level = error_reporting(E_ERROR);
$output = file_get_contents($argv['src']);
error_reporting($old_report_level);
if ($output === False)
return ef_include_get_errors("could not read the given src URL " . htmlspecialchars($argv['src']));
}
$output = ef_include_extract_line_range_maybe($output, $argv, $argv['linestart']);
if (isset($argv['lang'])) {
if (! $wg_include_allowed_features['highlight'])
return ef_include_get_errors("'highlight' feature not activated for include.");
$error = '';
if (! class_exists('SyntaxHighlight') && ! class_exists('\MediaWiki\SyntaxHighlight\SyntaxHighlight')) {
$error = ef_include_add_error('Missing SyntaxHighlight_GeSHi extension.');
} else {
$status = SyntaxHighlight::highlight($output, $argv['lang'], $argv);
# generate warnings if we hit the size limits
$config = MediaWiki\MediaWikiServices::getInstance()->getMainConfig();
$maxLines = $config->get( 'SyntaxHighlightMaxLines' );
$maxBytes = $config->get( 'SyntaxHighlightMaxBytes' );
// check size
$outBytes = strlen( $output );
if ( $outBytes > $maxBytes )
ef_include_add_error( "Output size '$outBytes' exceeds Syntaxhighlight size limit '\$wgSyntaxHighlightMaxBytes=$maxBytes'" );
# check line limit
$outLines = substr_count( $output, "\n" ) + 1;
if ( $outLines > $maxLines )
ef_include_add_error( "Output line count '$outLines' exceeds size limit '\$wgSyntaxHighlightMaxLines=$maxLines'" );
if ($status->isOK()) {
$output = $status->getValue();
if (count($status->getMessages()) > 0)
ef_include_add_error( Status::wrap( $status )->getHTML() );
//enqueue css so styles are rendered
$parser->getOutput()->addModuleStyles( [ 'ext.pygments' ] );
} else {
ef_include_add_error( var_export($status, true) );
$output = htmlspecialchars($output);
}
}
} elseif (isset($argv['wikitext'])) {
if (! $wg_include_allowed_features['wikitext'])
return ef_include_get_errors("'wikitext' feature not activated for include.");
$parsedText = $parser->parse($output, $parser->mTitle, $parser->mOptions, false, false);
$output = $parsedText->getText();
} else if (isset($argv['noesc'])) {
if (! $wg_include_allowed_features['noesc'])
return ef_include_get_errors("'noesc' feature not activated for include.");
// nothing
} else {
$output = htmlspecialchars($output);
}
if ( ! ef_include_argv_value_is($argv, 'nopre', true) ) {
$output = "" . $output . "
";
}
// prepend formatted errors, if any
$output = [
ef_include_get_errors() . $output
];
// dont touch output further, if nowiki is set
if (! ef_include_argv_value_is($argv, 'nowiki', false))
$output['markerType'] = 'nowiki';
return $output;
}
function ef_include_shell($input, $argv, $parser, $frame)
{
$checksum = sha1($input);
$res = ef_include_isEvalAllowed('shell', $checksum);
if (! $res[0])
return ef_include_get_errors($res[1]);
// $output = var_export($input, true);
$cmd = "sh -c " . escapeshellarg($input) . "2>&1";
exec($cmd, $output, $return_var);
$error = '';
if (isset($res[1])) {
$error = ef_include_get_errors($res[1]);
$parsedText = $parser->parse($error, $parser->getTitle(), $parser->getOptions(), false, false);
$error = $parsedText->getText();
}
return $error . implode("\n", $output);
}
function ef_include_php($input, $argv, $parser, $frame)
{
$input = trim($input);
$checksum = sha1($input);
$res = ef_include_isEvalAllowed('php', $checksum);
if (! $res[0])
return ef_include_get_errors($res[1]);
if (isset($res[1]))
ef_include_add_error($res[1]);
$output = '';
ob_start();
try {
eval( $input );
}catch (Error $e) {
ef_include_add_error("'{$e->getMessage()}' in line {$e->getLine()}");
}
$output = ob_get_clean();
$output = [
ef_include_get_errors() . $output
];
return $output;
}
use MediaWiki\MediaWikiServices;
function ef_include_isEvalAllowed( $mode, $checksum = null ) {
// are enabled globally?
global $wg_include_allowed_features;
if ( !$wg_include_allowed_features[$mode] )
return [ false, "'{$mode}' feature not activated for include." ];
// enabled via checksum?
global $wg_include_allowed_checksums;
$checksum_ok = $checksum && isset($wg_include_allowed_checksums[$mode]) && is_array($wg_include_allowed_checksums[$mode]) && in_array($checksum, $wg_include_allowed_checksums[$mode]);
$group = 'secureinclude';
$right = 'secureinclude-scripting';
// enabled via user?
$wgContext = RequestContext::getMain();
$wgUser = $wgContext->getUser();
$logged_in = $wgUser && $wgUser->getId() != 0;
$edit_ok = $logged_in && $wgUser->isAllowed( 'edit' );
$rights = MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $wgUser );
$script_ok = $edit_ok && in_array($right,$rights);
// test if latest revision is from same user
$revUserId='';
if ( $wgContext && $wgContext->canUseWikiPage() &&
$wgContext->getWikiPage() &&
$wgContext->getWikiPage()->getRevisionRecord() &&
$wgContext->getWikiPage()->getRevisionRecord()->getUser() )
$revUserId = $wgContext->getWikiPage()->getRevisionRecord()->getUser()->getId();
$lastEdit_ok = $wgUser->getId() === $revUserId;
if (! $checksum_ok) {
$prohibited = "Executing this '{$mode}' code is currently prohibited";
$user = "the currently logged in user '{$wgUser->getName()}'";
if ( !$logged_in )
return [
false,
"$prohibited because \$wg_include_allowed_checksums[$mode] does not contain a matching checksum!"
];
elseif (! $script_ok)
return [
false,
"$prohibited because '$user' is no member of group '$group' and \$wg_include_allowed_checksums[$mode] does not contain a matching checksum!"
];
elseif (! $lastEdit_ok)
return [
false,
"$prohibited because someone else than '$user' has edited the page inbetween and \$wg_include_allowed_checksums[$mode] does not contain a matching checksum!
Doublecheck the changes and make sure they don't pose a security risk. Afterwards do some minor edit and save the page so you are the latest editor!"
];
else
return [
true,
"Executing this '{$mode}' code with checksum '{$checksum}' is only temporarily allowed during editing. To make it permanent add the checksum to '''\$wg_include_allowed_checksums['$mode']''' !"
];
} else {
return [ true ];
}
// should never reach here
return [ false, "Executing this '{$mode}' code is currently prohibited. Dunno why." ];
}
function ef_include_add_error(string $message)
{
global $ef_include_errors;
$fileinfo = 'no_file_info';
$backtrace = debug_backtrace();
if (! empty($backtrace[0]) && is_array($backtrace[0]) && is_array($backtrace[1])) {
$fileinfo = basename($backtrace[0]['file']) . "::" . $backtrace[1]['function'] . ' (line ' . $backtrace[0]['line'] . ')';
}
$error = ''.
'
'.
'
'.
'
ERROR in '.htmlspecialchars($fileinfo).'
' . $message . '
'.
'
';
$ef_include_errors[] = $error;
}
function ef_include_get_errors(string $message = null)
{
global $ef_include_errors;
if ($message)
ef_include_add_error($message);
$error = ($ef_include_errors ? join($ef_include_errors) : '');
$ef_include_errors = [];
return $error;
}
/**
* compare a (list of) value(s) against an argv entry.
* comparison is as follows
* .'true' and 'false' keep their meaning (case is ignored)
* .defined key but empty value equals true
* .undefined key equals null (to allow a default setting)
*
* eg. ef_include_argv_value_is( $argv, 'nowiki', [ true, null, 'Bernd' ] );
*
* @param
* array of args $argv
* @param string $key
* @param mixed $value
* (array of values or plain boolean or string value)
* @return boolean
*/
function ef_include_argv_value_is(array $argv, string $key, $value)
{
if (is_array($value)) {
$in_array = false;
foreach ($value as $value_entry) {
if (ef_include_argv_value_is($argv, $key, $value_entry))
$in_array = true;
}
return $in_array;
}
if (! array_key_exists($key, $argv))
if ($value === null)
return true;
else
return false;
if (is_bool($value))
$value = ($value) ? 'true' : 'false';
$argv_value = empty($argv[$key]) ? 'true' : $argv[$key] . "";
// false == 'false', true == 'true' or ''
return (strtolower($value) === strtolower($argv_value));
}
?>