2 require_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]!");
516 } elseif (!is_string($rv[GESHI_SEARCH])) {
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!");
519 if (preg_match(
"/(?<!\\\\)\//s", $rv[GESHI_SEARCH])) {
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]!");
527 } elseif (!is_string($rv[GESHI_REPLACE])) {
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]!");
532 } elseif (!is_string($rv[GESHI_MODIFIERS])) {
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]!");
537 } elseif (!is_string($rv[GESHI_BEFORE])) {
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]!");
542 } elseif (!is_string($rv[GESHI_AFTER])) {
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) {
loadLanguageData()
Load the language data.
const GESHI_MODIFIERS
The key of the regex array defining any modifiers to the regular expression.
getIssuesAsString()
Get all found issues as formatted list.
if(!array_key_exists('StateId', $_REQUEST)) $id
const GESHI_BEFORE
The key of the regex array defining what bracket group in a matched search to put before the replacem...
__construct($file)
LangCheck constructor.
issue($type, $msg)
log an issue
const GESHI_COMMENTS
Used in language files to mark comments.
checkGeneral()
Check some general file properties.
const GESHI_REPLACE
The key of the regex array defining what bracket group in a matched search to use as a replacement...
checkMainKeys()
Check the major keys in the language array.
loadFile()
Load the file content.
const GESHI_CAPS_UPPER
Uppercase keywords found.
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.
const GESHI_SEARCH
The key of the regex array defining what to search for.
const GESHI_CAPS_LOWER
Leave keywords found as the case that they are.
const GESHI_MAYBE
Strict mode might apply, and can be enabled or disabled by GeSHi->enable_strict_mode().
const GESHI_NEVER
#+ private
const GESHI_CAPS_NO_CHANGE
Lowercase keywords found.
checkComment()
Check that the needed information is in the initial file comment.
const GESHI_ALWAYS
Strict mode always applies.
runChecks()
Run all the checks on the file.
const GESHI_AFTER
The key of the regex array defining what bracket group in a matched search to put after the replaceme...