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