2require_once __DIR__ . 
'/../src/geshi.php';
 
   34        self::TYPE_FATAL => 
'[FATAL]  ' 
   54        $this->issues = array();
 
   89        foreach ($this->issues as $issue) {
 
   90            $string .= $this->severityNames[$issue[0]] . 
' ' . $issue[1] . 
"\n";
 
  102        if (!is_file($this->file)) {
 
  103            $this->
issue(self::TYPE_FATAL, 
'The path "' . $this->file . 
'" does not ressemble a regular file!');
 
  106        if (!is_readable($this->file)) {
 
  107            $this->
issue(self::TYPE_FATAL, 
'Cannot read file "' . $this->file . 
'"!');
 
  110        $this->content = file_get_contents($this->file);
 
  118        if (preg_match(
'/\?>(?:\r?\n|\r(?!\n)){2,}\Z/', $this->content)) {
 
  121        if (preg_match(
'/\?>(?:\r?\n|\r(?!\n))?\Z/', $this->content)) {
 
  124        if (!preg_match(
'/(?:\r?\n|\r(?!\n))\Z/', $this->content)) {
 
  127        if (preg_match(
'/(\r?\n|\r(?!\n))\\1\Z/', $this->content)) {
 
  130        if (preg_match(
'/[\x20\t]$/m', $this->content)) {
 
  133        if (preg_match(
'/\t/', $this->content)) {
 
  134            $this->
issue(
self::TYPE_NOTICE, 
'Language file contains unescaped tabulator chars (probably for indentation)!');
 
  136        if (preg_match(
'/^(?:    )*(?!    )(?! \*) /m', $this->content)) {
 
  137            $this->
issue(
self::TYPE_NOTICE, 
'Language file contains irregular indentation (other than 4 spaces per indentation level)!');
 
  139        if (preg_match(
'/\015\012/s', $this->content)) {
 
  141        } 
else if (preg_match(
'/\015/s', $this->content)) {
 
  151        if (!preg_match(
'/\/\*\*\**\s(.*?)(?:\s*\*\/)/s', $this->content, 
$m)) {
 
  157        if (!preg_match(
'/Author: +\S+/', 
$comment)) {
 
  160        if (!preg_match(
'/Copyright: +\S+/', 
$comment)) {
 
  163        if (!preg_match(
'/Release Version: +\d+\.\d+\.\d+\.\d+/', 
$comment)) {
 
  166        if (!preg_match(
'/Date Started: +\S+/s', 
$comment)) {
 
  167            $this->
issue(
self::TYPE_WARNING, 
'Language file does not contain a specification of the date it was started!');
 
  169        if (!preg_match(
'/This file is part of GeSHi\./', 
$comment)) {
 
  172        if (!preg_match(
'/\S+ language file for GeSHi\./', 
$comment)) {
 
  175        if (!preg_match(
'/GNU General Public License/', 
$comment)) {
 
  176            $this->
issue(
self::TYPE_WARNING, 
'Language file does not state that it is provided under the terms of the GNU GPL!');
 
  192            $this->
issue(self::TYPE_FATAL, 
'Language file does not contain a $language_data structure to check!');
 
  196            $this->
issue(self::TYPE_FATAL, 
'Language file contains a $language_data structure which is not an array!');
 
  202        $defined = get_defined_vars();
 
  204            $this->
issue(
self::TYPE_ERROR, 
'Language file seems to define other variables than $language_data!');
 
  220        $types = explode(
'|', 
$type);
 
  222        if (!isset($this->langdata[
$name])) {
 
  230        if (!in_array(gettype($this->langdata[
$name]), $types)) {
 
  231            $this->
issue(
self::TYPE_ERROR, 
"Language file contains a \$language_data['$name'] specification which is not a $type!");
 
  259            if (1 < strlen($this->langdata[
'ESCAPE_CHAR'])) {
 
  260                $this->
issue(
self::TYPE_ERROR, 
'Language file contains a $language_data[\'ESCAPE_CHAR\'] specification is not empty or exactly one char!');
 
  269                $this->
issue(
self::TYPE_ERROR, 
'Language file contains a $language_data[\'CASE_KEYWORDS\'] specification which is neither of GESHI_CAPS_NO_CHANGE, GESHI_CAPS_LOWER nor GESHI_CAPS_UPPER!');
 
  274            foreach ($this->langdata[
'KEYWORDS'] as $kw_key => $kw_value) {
 
  275                if (!is_integer($kw_key)) {
 
  276                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$kw_key' in \$language_data['KEYWORDS'] that is not integer!");
 
  277                } elseif (!is_array($kw_value)) {
 
  278                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a \$language_data['KEYWORDS']['$kw_value'] structure which is not an array!");
 
  284            foreach ($this->langdata[
'CASE_SENSITIVE'] as $cs_key => $cs_value) {
 
  285                if (!is_integer($cs_key)) {
 
  286                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$cs_key' in \$language_data['CASE_SENSITIVE'] that is not integer!");
 
  287                } elseif (!is_bool($cs_value)) {
 
  288                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a Case Sensitivity specification for \$language_data['CASE_SENSITIVE']['$cs_value'] which is not a boolean!");
 
  294            foreach ($this->langdata[
'URLS'] as $url_key => $url_value) {
 
  295                if (!is_integer($url_key)) {
 
  296                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$url_key' in \$language_data['URLS'] that is not integer!");
 
  297                } elseif (!is_string($url_value)) {
 
  298                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a Documentation URL specification for \$language_data['URLS']['$url_value'] which is not a string!");
 
  299                } elseif (preg_match(
'#&([^;]*(=|$))#U', $url_value)) {
 
  300                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains unescaped ampersands (&) in \$language_data['URLS']!");
 
  306            if (
false !== $this->langdata[
'OOLANG'] &&
 
  307                true !== $this->langdata[
'OOLANG'] &&
 
  308                2 !== $this->langdata[
'OOLANG']
 
  310                $this->
issue(
self::TYPE_ERROR, 
'Language file contains a $language_data[\'OOLANG\'] specification which is neither of false, true or 2!');
 
  314        if ($this->
ensureKeyType(
'STRICT_MODE_APPLIES', 
'integer')) {
 
  315            if (
GESHI_MAYBE != $this->langdata[
'STRICT_MODE_APPLIES'] &&
 
  316                GESHI_ALWAYS != $this->langdata[
'STRICT_MODE_APPLIES'] &&
 
  317                GESHI_NEVER != $this->langdata[
'STRICT_MODE_APPLIES']
 
  319                $this->
issue(
self::TYPE_ERROR, 
'Language file contains a $language_data[\'STRICT_MODE_APPLIES\'] specification which is neither of GESHI_MAYBE, GESHI_ALWAYS nor GESHI_NEVER!');
 
  324            if (1 > $this->langdata[
'TAB_WIDTH']) {
 
  325                $this->
issue(
self::TYPE_ERROR, 
'Language file contains a $language_data[\'TAB_WIDTH\'] specification which is less than 1!');
 
  330            $style_arrays = array(
'KEYWORDS', 
'COMMENTS', 
'ESCAPE_CHAR',
 
  331                'BRACKETS', 
'STRINGS', 
'NUMBERS', 
'METHODS', 
'SYMBOLS',
 
  332                'REGEXPS', 
'SCRIPT');
 
  333            foreach ($style_arrays as $style_kind) {
 
  334                if (!isset($this->langdata[
'STYLES'][$style_kind])) {
 
  335                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains no \$language_data['STYLES']['$style_kind'] structure to check!");
 
  336                } elseif (!is_array($this->langdata[
'STYLES'][$style_kind])) {
 
  337                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a \$language_data['STYLES\']['$style_kind'] structure which is not an array!");
 
  339                    foreach ($this->langdata[
'STYLES'][$style_kind] as $sk_key => $sk_value) {
 
  340                        if (!is_int($sk_key) && (
'COMMENTS' != $style_kind && 
'MULTI' != $sk_key)
 
  341                            && !((
'STRINGS' == $style_kind || 
'ESCAPE_CHAR' == $style_kind) && 
'HARD' == $sk_key)
 
  343                            $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$sk_key' in \$language_data['STYLES']['$style_kind'] that is not integer!");
 
  344                        } elseif (!is_string($sk_value)) {
 
  345                            $this->
issue(
self::TYPE_WARNING, 
"Language file contains a CSS specification for \$language_data['STYLES']['$style_kind'][$key] which is not a string!");
 
  360        foreach ($this->langdata[
'KEYWORDS'] as 
$key => $keywords) {
 
  361            if (!isset($this->langdata[
'CASE_SENSITIVE'][
$key])) {
 
  362                $this->
issue(
self::TYPE_ERROR, 
"Language file contains no \$language_data['CASE_SENSITIVE'] specification for keyword group $key!");
 
  364            if (!isset($this->langdata[
'URLS'][
$key])) {
 
  365                $this->
issue(
self::TYPE_ERROR, 
"Language file contains no \$language_data['URLS'] specification for keyword group $key!");
 
  367            if (empty($keywords)) {
 
  368                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an empty keyword list in \$language_data['KEYWORDS'] for group $key!");
 
  370            foreach ($keywords as 
$id => $kw) {
 
  371                if (!is_string($kw)) {
 
  372                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an non-string entry at \$language_data['KEYWORDS'][$key][$id]!");
 
  373                } elseif (!strlen($kw)) {
 
  374                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains an empty string entry at \$language_data['KEYWORDS'][$key][$id]!");
 
  375                } elseif (preg_match(
'/^([\(\)\{\}\[\]\^=.,:;\-+\*\/%\$\"\'\?]|&[\w#]\w*;)+$/i', $kw)) {
 
  376                    $this->
issue(
self::TYPE_NOTICE, 
"Language file contains an keyword ('$kw') at \$language_data['KEYWORDS'][$key][$id] which seems to be better suited for the symbols section!");
 
  379            if (isset($this->langdata[
'CASE_SENSITIVE'][
$key]) && !$this->langdata[
'CASE_SENSITIVE'][
$key]) {
 
  380                array_walk($keywords, array($this, 
'strtolower'));
 
  382            if (count($keywords) != count(array_unique($keywords))) {
 
  383                $kw_diffs = array_count_values($keywords);
 
  384                foreach ($kw_diffs as $kw => $kw_count) {
 
  386                        $this->
issue(
self::TYPE_WARNING, 
"Language file contains per-group duplicate keyword '$kw' in \$language_data['KEYWORDS'][$key]!");
 
  392        $disallowed_before = 
'(?<![a-zA-Z0-9\$_\|\#;>|^&';
 
  393        $disallowed_after = 
'(?![a-zA-Z0-9_\|%\\-&;';
 
  395        foreach ($this->langdata[
'KEYWORDS'] as 
$key => $keywords) {
 
  396            foreach ($this->langdata[
'KEYWORDS'] as $key2 => $keywords2) {
 
  400                $kw_diffs = array_intersect($keywords, $keywords2);
 
  401                foreach ($kw_diffs as $kw) {
 
  402                    if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'])) {
 
  404                        $g1_pre = $disallowed_before;
 
  405                        $g2_pre = $disallowed_before;
 
  406                        $g1_post = $disallowed_after;
 
  407                        $g2_post = $disallowed_after;
 
  408                        if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
'DISALLOWED_BEFORE'])) {
 
  409                            $g1_pre = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
'DISALLOWED_BEFORE'];
 
  410                            $g2_pre = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
'DISALLOWED_BEFORE'];
 
  412                        if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
'DISALLOWED_AFTER'])) {
 
  413                            $g1_post = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
'DISALLOWED_AFTER'];
 
  414                            $g2_post = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
'DISALLOWED_AFTER'];
 
  417                        if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
$key][
'DISALLOWED_BEFORE'])) {
 
  418                            $g1_pre = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
$key][
'DISALLOWED_BEFORE'];
 
  420                        if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
$key][
'DISALLOWED_AFTER'])) {
 
  421                            $g1_post = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][
$key][
'DISALLOWED_AFTER'];
 
  424                        if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][$key2][
'DISALLOWED_BEFORE'])) {
 
  425                            $g2_pre = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][$key2][
'DISALLOWED_BEFORE'];
 
  427                        if (isset($this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][$key2][
'DISALLOWED_AFTER'])) {
 
  428                            $g2_post = $this->langdata[
'PARSER_CONTROL'][
'KEYWORDS'][$key2][
'DISALLOWED_AFTER'];
 
  431                        if ($g1_pre != $g2_pre || $g1_post != $g2_post) {
 
  435                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains cross-group duplicate keyword '$kw' in \$language_data['KEYWORDS'][$key] and \$language_data['KEYWORDS'][$key2]!");
 
  439        foreach ($this->langdata[
'CASE_SENSITIVE'] as 
$key => $keywords) {
 
  441                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an superfluous \$language_data['CASE_SENSITIVE'] specification for non-existing keyword group $key!");
 
  444        foreach ($this->langdata[
'URLS'] as 
$key => $keywords) {
 
  445            if (!isset($this->langdata[
'KEYWORDS'][
$key])) {
 
  446                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an superfluous \$language_data['URLS'] specification for non-existing keyword group $key!");
 
  449        foreach ($this->langdata[
'STYLES'][
'KEYWORDS'] as 
$key => $keywords) {
 
  450            if (!isset($this->langdata[
'KEYWORDS'][
$key])) {
 
  451                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an superfluous \$language_data['STYLES']['KEYWORDS'] specification for non-existing keyword group $key!");
 
  455        foreach ($this->langdata[
'COMMENT_SINGLE'] as $ck => $cv) {
 
  457                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$ck' in \$language_data['COMMENT_SINGLE'] that is not integer!");
 
  459            if (!is_string($cv)) {
 
  460                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an non-string entry at \$language_data['COMMENT_SINGLE'][$ck]!");
 
  462            if (!isset($this->langdata[
'STYLES'][
'COMMENTS'][$ck])) {
 
  463                $this->
issue(
self::TYPE_WARNING, 
"Language file contains no \$language_data['STYLES']['COMMENTS'] specification for comment group $ck!");
 
  466        if (isset($this->langdata[
'COMMENT_REGEXP'])) {
 
  467            foreach ($this->langdata[
'COMMENT_REGEXP'] as $ck => $cv) {
 
  469                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$ck' in \$language_data['COMMENT_REGEXP'] that is not integer!");
 
  471                if (!is_string($cv)) {
 
  472                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an non-string entry at \$language_data['COMMENT_REGEXP'][$ck]!");
 
  474                if (!isset($this->langdata[
'STYLES'][
'COMMENTS'][$ck])) {
 
  475                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains no \$language_data['STYLES']['COMMENTS'] specification for comment group $ck!");
 
  479        foreach ($this->langdata[
'STYLES'][
'COMMENTS'] as $ck => $cv) {
 
  480            if ($ck != 
'MULTI' && !isset($this->langdata[
'COMMENT_SINGLE'][$ck]) &&
 
  481                !isset($this->langdata[
'COMMENT_REGEXP'][$ck])
 
  483                $this->
issue(
self::TYPE_NOTICE, 
"Language file contains an superfluous \$language_data['STYLES']['COMMENTS'] specification for Single Line or Regular-Expression Comment key $ck!");
 
  486        if (isset($this->langdata[
'STYLES'][
'STRINGS'][
'HARD'])) {
 
  487            if (empty($this->langdata[
'HARDQUOTE'])) {
 
  488                $this->
issue(
self::TYPE_NOTICE, 
"Language file contains superfluous \$language_data['STYLES']['STRINGS'] specification for key 'HARD', but no 'HARDQUOTE's are defined!");
 
  490            unset($this->langdata[
'STYLES'][
'STRINGS'][
'HARD']);
 
  492        foreach ($this->langdata[
'STYLES'][
'STRINGS'] as $sk => $sv) {
 
  493            if ($sk && !isset($this->langdata[
'QUOTEMARKS'][$sk])) {
 
  494                $this->
issue(
self::TYPE_NOTICE, 
"Language file contains an superfluous \$language_data['STYLES']['STRINGS'] specification for non-existing quotemark key $sk!");
 
  498        foreach ($this->langdata[
'REGEXPS'] as $rk => $rv) {
 
  500                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an key '$rk' in \$language_data['REGEXPS'] that is not integer!");
 
  502            if (is_string($rv)) {
 
  505                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains an empty regular expression at \$language_data['REGEXPS'][$rk]!");
 
  507                    if (preg_match(
"/(?<!\\\\)\//s", $rv)) {
 
  508                        $this->
issue(
self::TYPE_WARNING, 
"Language file contains a regular expression with an unmasked / character at \$language_data['REGEXPS'][$rk]!");
 
  509                    } elseif (preg_match(
"/(?<!<)(\\\\\\\\)*\\\\\|(?!>)/s", $rv)) {
 
  510                        $this->
issue(
self::TYPE_WARNING, 
"Language file contains a regular expression with an unescaped match for a pipe character '|' which needs escaping as '<PIPE>' instead at \$language_data['REGEXPS'][$rk]!");
 
  513            } elseif (is_array($rv)) {
 
  515                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains no GESHI_SEARCH entry in extended regular expression at \$language_data['REGEXPS'][$rk]!");
 
  517                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a GESHI_SEARCH entry in extended regular expression at \$language_data['REGEXPS'][$rk] which is not a string!");
 
  520                        $this->
issue(
self::TYPE_WARNING, 
"Language file contains a regular expression with an unmasked / character at \$language_data['REGEXPS'][$rk]!");
 
  521                    } elseif (preg_match(
"/(?<!<)(\\\\\\\\)*\\\\\|(?!>)/s", $rv[
GESHI_SEARCH])) {
 
  522                        $this->
issue(
self::TYPE_WARNING, 
"Language file contains a regular expression with an unescaped match for a pipe character '|' which needs escaping as '<PIPE>' instead at \$language_data['REGEXPS'][$rk]!");
 
  526                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains no GESHI_REPLACE entry in extended regular expression at \$language_data['REGEXPS'][$rk]!");
 
  528                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a GESHI_REPLACE entry in extended regular expression at \$language_data['REGEXPS'][$rk] which is not a string!");
 
  531                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains no GESHI_MODIFIERS entry in extended regular expression at \$language_data['REGEXPS'][$rk]!");
 
  533                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a GESHI_MODIFIERS entry in extended regular expression at \$language_data['REGEXPS'][$rk] which is not a string!");
 
  536                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains no GESHI_BEFORE entry in extended regular expression at \$language_data['REGEXPS'][$rk]!");
 
  538                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a GESHI_BEFORE entry in extended regular expression at \$language_data['REGEXPS'][$rk] which is not a string!");
 
  541                    $this->
issue(
self::TYPE_WARNING, 
"Language file contains no GESHI_AFTER entry in extended regular expression at \$language_data['REGEXPS'][$rk]!");
 
  543                    $this->
issue(
self::TYPE_ERROR, 
"Language file contains a GESHI_AFTER entry in extended regular expression at \$language_data['REGEXPS'][$rk] which is not a string!");
 
  546                $this->
issue(
self::TYPE_WARNING, 
"Language file contains an non-string and non-array entry at \$language_data['REGEXPS'][$rk]!");
 
  548            if (!isset($this->langdata[
'STYLES'][
'REGEXPS'][$rk])) {
 
  549                $this->
issue(
self::TYPE_WARNING, 
"Language file contains no \$language_data['STYLES']['REGEXPS'] specification for regexp group $rk!");
 
  552        foreach ($this->langdata[
'STYLES'][
'REGEXPS'] as $rk => $rv) {
 
  553            if (!isset($this->langdata[
'REGEXPS'][$rk])) {
 
  554                $this->
issue(
self::TYPE_NOTICE, 
"Language file contains an superfluous \$language_data['STYLES']['REGEXPS'] specification for regexp key $rk!");
 
  569        $this->issues[] = array(
$type, $msg);
 
  572        if (
$type == self::TYPE_FATAL) {
 
  573            throw new Exception($msg);
 
An exception for terminatinating execution or to throw for unit testing.
__construct($file)
LangCheck constructor.
checkMainKeys()
Check the major keys in the language array.
checkComment()
Check that the needed information is in the initial file comment.
checkGeneral()
Check some general file properties.
getIssuesAsString()
Get all found issues as formatted list.
runChecks()
Run all the checks on the file.
issue($type, $msg)
log an issue
loadFile()
Load the file content.
ensureKeyType($name, $type='array', $optional=false)
Check that the given key exists and has the correct type.
getIssues()
Get all found issues as (severity, message) tuple.
checkKeyContents()
Check the keywords are sane.
loadLanguageData()
Load the language data.
if(!array_key_exists('StateId', $_REQUEST)) $id
const GESHI_MAYBE
Strict mode might apply, and can be enabled or disabled by GeSHi->enable_strict_mode().
const GESHI_CAPS_NO_CHANGE
Lowercase keywords found.
const GESHI_BEFORE
The key of the regex array defining what bracket group in a matched search to put before the replacem...
const GESHI_SEARCH
The key of the regex array defining what to search for.
const GESHI_AFTER
The key of the regex array defining what bracket group in a matched search to put after the replaceme...
const GESHI_ALWAYS
Strict mode always applies.
const GESHI_REPLACE
The key of the regex array defining what bracket group in a matched search to use as a replacement.
const GESHI_CAPS_LOWER
Leave keywords found as the case that they are.
const GESHI_COMMENTS
Used in language files to mark comments.
const GESHI_NEVER
#+ @access private
const GESHI_MODIFIERS
The key of the regex array defining any modifiers to the regular expression.
const GESHI_CAPS_UPPER
Uppercase keywords found.