Merge pull request #14326 from civicrm/5.14
[civicrm-core.git] / tools / scripts / tpl-lint
1 #!/usr/bin/env php
2 <?php
3
4 // FIXME: Make this a proper app with unit-tests
5
6 $civi_pkgs_dir = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'packages';
7 require_once $civi_pkgs_dir . DIRECTORY_SEPARATOR . 'simple_html_dom.php';
8 exit(main($argv));
9
10 /**
11 * Scan a series of files for suspicious code
12 *
13 * example: find templates -name '*.tpl' | xargs php tools/scripts/tpl-lint
14 *
15 * @param array $argv
16 * @return int
17 */
18 function main($argv) {
19 $files = $argv;
20 array_shift($files); // skip program name
21 foreach ($files as $file) {
22 check_tpl($file, function ($code, $message) use ($file) {
23 printf("[%s] %s\n", $file, $message);
24 });
25 }
26 return 0;
27 }
28
29 /**
30 * Scan a file for suspicious code
31 *
32 * @param string $file
33 * @param callable $reporter
34 * @return void
35 */
36 function check_tpl($file, $reporter) {
37 $html = file_get_html($file);
38 foreach ($html->find('a') as $a) {
39 check_a($a, $reporter);
40 }
41 }
42
43 /**
44 * Scan an <A> tag
45 *
46 * @param object $a
47 * @param callable $reporter
48 * @return void
49 */
50 function check_a($a, $reporter) {
51 if (!$a->hasAttribute('href')) {
52 // anchor, don't care
53 return;
54 }
55
56 $href = trim($a->getAttribute('href'));
57 if (preg_match('/javascript:/', $href)) {
58 $reporter('javascript-url', "<a> has javascript url: $href");
59 return;
60 }
61 if ($href == '#' && $a->hasAttribute('onclick')) {
62 $onclick = $a->getAttribute('onclick');
63 if (!js_returns_false($onclick) && !js_returns_func($onclick)) {
64 $reporter('a-no-return', "<a> has href=# but handler fails to return false: $onclick");
65 return;
66 }
67 }
68 if ($href != '#' && $a->hasAttribute('onclick')) {
69 $onclick = $a->getAttribute('onclick');
70 $reporter('a-double-action', "<a> has both URL ($href) and onclick ($onclick)");
71 return;
72 }
73 }
74
75 /**
76 * Determine if snippet of JS returns strictly false
77 * @param $js
78 * @return bool
79 */
80 function js_returns_false($js) {
81 return
82 // last in a series of statements
83 preg_match('/; *return +false *; *$/', $js)
84 ||
85 // only statement
86 preg_match('/^ *return +false *;? *$/', $js);
87 }
88
89 /**
90 * Determine if snippet of JS returns a function call
91 * @param $js
92 * @return int
93 */
94 function js_returns_func($js) {
95 return preg_match('/^ *return +[a-zA-Z0-9\._$]+\(/', $js);
96 }