1<?php
2
3/**
4 * Check some page output for validity
5 *
6 * @group internet
7 */
8class general_html_test extends DokuWikiTest
9{
10    /** @var string[] we consider these hits shortcomings in the validator and not errors */
11    protected $allowedErrors = [
12        'The string “ugc” is not a registered keyword.',
13    ];
14
15    /**
16     * List of requests to check for validity
17     *
18     * @return array
19     */
20    public function requestProvider()
21    {
22        return [
23            ['/doku.php', 'GET', []],
24            ['/doku.php', 'GET', ['do' => 'recent']],
25            ['/doku.php', 'GET', ['do' => 'index']],
26            ['/doku.php', 'GET', ['do' => 'login']],
27            ['/doku.php', 'GET', ['do' => 'search', 'q' => 'wiki']],
28            ['/doku.php', 'GET', ['id' => 'wiki:syntax']],
29            ['/doku.php', 'GET', ['id' => 'wiki:syntax', 'ns' => 'wiki', 'image' => 'wiki:dokuwiki-128.png', 'do' => 'media']],
30            ['/lib/exe/detail.php', 'GET', ['id' => 'wiki:syntax', 'media' => 'wiki:dokuwiki-128.png']],
31        ];
32    }
33
34    /**
35     * Sends the given HTML to the validator and returns the result
36     *
37     * @param string $html
38     * @return array
39     * @throws Exception when communication failed
40     */
41    protected function validate($html)
42    {
43        $http = new \dokuwiki\HTTP\DokuHTTPClient();
44        $http->headers['Content-Type'] = 'text/html; charset=utf-8';
45        $result = $http->post('https://validator.w3.org/nu/?out=json&level=error', $html);
46
47        if ($result === false) {
48            throw new \Exception($http->error);
49        }
50
51        $result = json_decode($result, true);
52        if ($result === null) {
53            throw new \Exception('could not decode JSON');
54        }
55
56        return $result;
57    }
58
59    /**
60     * Reformat the errors for nicer display in output
61     *
62     * @param array $result
63     * @return string[]
64     */
65    protected function listErrors($result)
66    {
67        $errors = [];
68        foreach ($result['messages'] as $msg) {
69            if ($this->isAllowedError($msg['message'])) continue;
70            $errors[] = "☛ " . $msg['message'] . "\n" . $msg['extract'] . "\n";
71        }
72        return $errors;
73    }
74
75    /**
76     * Is the given string an allowed error that should be skipped?
77     *
78     * @param string $string
79     * @return bool
80     */
81    protected function isAllowedError($string)
82    {
83        $re = join('|', array_map('preg_quote_cb', $this->allowedErrors));
84        return (bool)preg_match("/$re/", $string);
85    }
86
87    /**
88     * @dataProvider requestProvider
89     * @param string $url
90     * @param string $method
91     * @param array $data
92     * @group internet
93     */
94    public function test_Validity($url, $method, $data)
95    {
96        $request = new TestRequest();
97        if ($method == 'GET') {
98            $response = $request->get($data, $url);
99        } elseif ($method == 'POST') {
100            $response = $request->post($data, $url);
101        } else {
102            throw new \RuntimeException("unknown method given: $method");
103        }
104
105        $html = $response->getContent();
106        try {
107            $result = $this->validate($html);
108        } catch (\Exception $e) {
109            $this->markTestSkipped($e->getMessage());
110            return;
111        }
112
113        $errors = $this->listErrors($result);
114        $info = "Invalid HTML found:\n" . join("\n", $errors);
115
116        $this->assertEquals(0, count($errors), $info);
117    }
118}
119