Fix accept header in crawler
[phinde.git] / src / phinde / Indexer.php
1 <?php
2 namespace phinde;
3
4 class Indexer
5 {
6     static $supportedTypes = array(
7         'application/xhtml+xml' => true,
8         'text/html'             => true,
9     );
10
11     public function run(Retrieved $retrieved)
12     {
13         $res   = $retrieved->httpRes;
14         $esDoc = $retrieved->esDoc;
15         $url   = $retrieved->url;
16
17         $mimetype = explode(';', $res->getHeader('content-type'))[0];
18         if (!in_array($mimetype, static::$supportedTypes)) {
19             Log::info("MIME type not supported for indexing: $mimetype");
20             return false;
21         }
22
23         if ($esDoc === null) {
24             $esDoc = Helper::baseDoc($url);
25             $retrieved->esDoc = $esDoc;
26         }
27
28         //FIXME: update index only if changed since last index time
29         //FIXME: extract base url from html
30         //FIXME: check if effective url needs updating
31
32         $base = new \Net_URL2($url);
33
34         //FIXME: MIME type switch
35         $doc = new \DOMDocument();
36         //@ to hide parse warning messages in invalid html
37         @$doc->loadHTML($res->getBody());
38         $dx = new \DOMXPath($doc);
39
40         $xbase = $dx->evaluate('/html/head/base[@href]')->item(0);
41         if ($xbase) {
42             $base = $base->resolve(
43                 $xbase->attributes->getNamedItem('href')->textContent
44             );
45         }
46
47         $meta = $dx->evaluate('/html/head/meta[@name="robots" and @content]')
48             ->item(0);
49         if ($meta) {
50             $robots = $meta->attributes->getNamedItem('content')->textContent;
51             foreach (explode(',', $robots) as $value) {
52                 if (trim($value) == 'noindex') {
53                     $esDoc->status->findable = false;
54                     return true;
55                 }
56             }
57         }
58
59         //remove script tags
60         $this->removeTags($doc, 'script');
61         $this->removeTags($doc, 'style');
62         $this->removeTags($doc, 'nav');
63
64         //default content: <body>
65         $xpContext = $doc->getElementsByTagName('body')->item(0);
66         //FIXME: follow meta refresh, no body
67         // example: https://www.gnu.org/software/coreutils/
68
69         //use microformats content if it exists
70         $xpElems = $dx->query(
71             "//*[contains(concat(' ', normalize-space(@class), ' '), ' e-content ')]"
72         );
73         if ($xpElems->length) {
74             $xpContext = $xpElems->item(0);
75         } else if ($doc->getElementById('content')) {
76             //if there is an element with ID "content", we'll use this
77             $xpContext = $doc->getElementById('content');
78         }
79
80         $esDoc->url = $url;
81         $esDoc->schemalessUrl = Helper::noSchema($url);
82         $esDoc->type = 'html';
83         $esDoc->subtype = '';
84         $esDoc->mimetype = $mimetype;
85         $esDoc->domain   = parse_url($url, PHP_URL_HOST);
86
87         //$esDoc->source = 'FIXME';
88         //$esDoc->sourcetitle = 'FIXME';
89
90         $esDoc->author = new \stdClass();
91
92         $arXpElems = $dx->query('/html/head/meta[@name="author" and @content]');
93         if ($arXpElems->length) {
94             $esDoc->author->name = trim(
95                 $arXpElems->item(0)->attributes->getNamedItem('content')->textContent
96             );
97         }
98         $arXpElems = $dx->query('/html/head/link[@rel="author" and @href]');
99         if ($arXpElems->length) {
100             $esDoc->author->url = trim(
101                 $base->resolve(
102                     $arXpElems->item(0)->attributes->getNamedItem('href')->textContent
103                 )
104             );
105         }
106
107
108         $arXpElems = $dx->query('/html/head/title');
109         if ($arXpElems->length) {
110             $esDoc->title = trim(
111                 $arXpElems->item(0)->textContent
112             );
113         }
114
115         foreach (array('h1', 'h2', 'h3', 'h4', 'h5', 'h6') as $headlinetype) {
116             $esDoc->$headlinetype = array();
117             foreach ($xpContext->getElementsByTagName($headlinetype) as $xheadline) {
118                 array_push(
119                     $esDoc->$headlinetype,
120                     trim($xheadline->textContent)
121                 );
122             }
123         }
124
125         //FIXME: split paragraphs
126         //FIXME: insert space after br
127         $esDoc->text = array();
128         $esDoc->text[] = trim(
129             str_replace(
130                 array("\r\n", "\n", "\r", '  '),
131                 ' ',
132                 $xpContext->textContent
133             )
134         );
135
136         //tags
137         $tags = array();
138         foreach ($dx->query('/html/head/meta[@name="keywords" and @content]') as $xkeywords) {
139             $keywords = $xkeywords->attributes->getNamedItem('content')->textContent;
140             foreach (explode(',', $keywords) as $keyword) {
141                 $tags[trim($keyword)] = true;
142             }
143         }
144         $esDoc->tags = array_keys($tags);
145
146         //dates
147         $arXpdates = $dx->query('/html/head/meta[@name="DC.date.created" and @content]');
148         if ($arXpdates->length) {
149             $esDoc->status->crdate = gmdate(
150                 'c',
151                 strtotime(
152                     $arXpdates->item(0)->attributes->getNamedItem('content')->textContent
153                 )
154             );
155         }
156         //FIXME: keep creation date from database, or use modified date if we
157         // do not have it there
158
159         $arXpdates = $dx->query('/html/head/meta[@name="DC.date.modified" and @content]');
160         if ($arXpdates->length) {
161             $esDoc->status->modate = gmdate(
162                 'c',
163                 strtotime(
164                     $arXpdates->item(0)->attributes->getNamedItem('content')->textContent
165                 )
166             );
167         } else {
168             $lm = $res->getHeader('last-modified');
169             if ($lm !== null) {
170                 $esDoc->status->modate = gmdate('c', strtotime($lm));
171             } else {
172                 //use current time since we don't have any other data
173                 $esDoc->status->modate = gmdate('c');
174             }
175         }
176         $esDoc->status->findable = true;
177
178         //language
179         //there may be "en-US" and "de-DE"
180         $xlang = $doc->documentElement->attributes->getNamedItem('lang');
181         if ($xlang) {
182             $esDoc->language = strtolower(substr($xlang->textContent, 0, 2));
183         }
184         //FIXME: fallback, autodetection
185         //FIXME: check noindex
186
187         //var_dump($esDoc);die();
188
189         return true;
190     }
191
192     function removeTags($doc, $tag) {
193         $elems = array();
194         foreach ($doc->getElementsbyTagName($tag) as $elem) {
195             $elems[] = $elem;
196         }
197         foreach ($elems as $elem) {
198             $elem->parentNode->removeChild($elem);
199         }
200     }
201 }
202 ?>