Jacek Kowalski
2012-08-27 80ddb438c8098a34ecdfadd954353d676acab281
commit | author | age
8bd4d9 1 <?php
JK 2 /**
3  * Klasa konwertuje wiadomość ({@link BotMsg}) do formatu specyficznego dla Gadu-Gadu
4  */
5 class BotMsgGG implements BotMsgInterface {
6     private $parser;
7     private $html = '';
8     private $old = '';
9     private $format = '';
10     
11     private $images = array();
12     
13     private $f_handl = FALSE;
14     private $f_old = '';
15     private $f_type = 0x00;
16     private $f_color = '';
17     
18     const FORMAT_BOLD =    0x01;
19     const FORMAT_ITALIC =    0x02;
20     const FORMAT_UNDERLINE =0x04;
21     const FORMAT_COLOR =    0x08;
22     const FORMAT_IMAGE =    0x80;
23     
24     /**
25      * @param BotMsg $msg Wiadomość do przekonwertowania
26      */
27     function __construct(BotMsg $msg) {
28         $parser = $msg->getParser();
29         unset($msg);
30         
31         $this->parser = new DOMDocument('1.0', 'utf-8');
32         $this->parser->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body></body></html>');
33         
34         $this->rewrite( $parser->getElementsByTagName('body')->item(0), $this->parser->getElementsByTagName('body')->item(0) );
35         unset($parser);
36         
37         $this->parse( $this->parser->getElementsByTagName('body')->item(0) );
38         
39         $this->html = strtr(
40             (string)substr($this->parser->saveXML( $this->parser->getElementsByTagName('body')->item(0) ), 6, -7),
41             array('/>' => '>') // Tak! GG nie lubi XML!
42         );
43     }
44     
45     /**
46      * Zwraca wiadomość zgodną z BotAPI Gadu-Gadu, którą można przekazać bezpośrednio do BotMastera
fb87e0 47      * @param NULL|bool $img Czy dołączać obrazki?
8bd4d9 48      * @return string
JK 49      */
fb87e0 50     function getGG($image = NULL) {
JK 51         if($image === FALSE) {
52             $image = '';
53         }
54         elseif($image === TRUE) {
55             $last = array_pop($this->images);
56             if(count($this->images) > 0) {
57                 $push = new BotAPIGG();
58                 foreach($this->images as $data) {
59                     $push->putImage($image[3]);
60                 }
61             }
62             
63             $image = $last[2].file_get_contents($last[3]);
8bd4d9 64         }
JK 65         else
66         {
159094 67             if(count($this->images) > 0) {
JK 68                 $push = new BotAPIGG();
c84030 69                 foreach($this->images as $image) {
159094 70                     if(!$push->existsImage($image[2])) {
JK 71                         $push->putImage($image[3]);
72                     }
73                 }
74             }
75             
8bd4d9 76             $image = '';
JK 77         }
78         
79         $format = $this->getFormat();
80         
81         return pack('VVVV', strlen($this->html)+1, strlen($this->old)+1, strlen($image), strlen($format)).$this->html."\0".$this->old."\0".$image.$format;
82     }
83     
84     /**
85      * Zwraca wiadomość zgodną z BotAPI Gadu-Gadu, którą można przekazać bezpośrednio do BotMastera
86      * @return string
87      */
88     function __toString() {
89         return $this->getGG();
90     }
91     
92     /**
93      * Zwraca wiadomość w formacie HTML przekonwertowaną tak, by zawierała jedynie dozwolone tagi.
94      * @return string
95      */
96     function getHTML() {
97         return $this->html;
98     }
99     
100     /**
101      * Zwraca wiadomość jako tekst
102      * @return string
103      */
104     function getText() {
105         return $this->old;
106     }
107     
108     /**
109      * Zwraca formatowanie wiadomości tekstowej zgodne z BotAPI Gadu-Gadu
110      * @see BotMsgGG::getText()
111      * @return string
112      */
113     function getFormat() {
271560 114         if($this->format == '') {
JK 115             return '';
116         }
117         else
118         {
119             return pack('Cv', 0x02, strlen($this->format)).$this->format;
120         }
8bd4d9 121     }
JK 122     
123     /**
124      * Wyślij wiadomość na standardowe wyjście w sposób właściwy dla BotAPI
125      */
126     function sendPullResponse() {
127         header('Content-Type: application/x-gadu-gadu; charset=utf-8');
127d1a 128         echo $this->getGG();
8bd4d9 129     }
JK 130     
131     private function rewrite($dom, $saveto, $top = TRUE) {
132         if(!($dom instanceof DOMElement)) {
133             throw new BotMsgException('Nieznany element DOM: '.get_class($dom));
134         }
135         
136         foreach($dom->childNodes as $node) {
137             if($node instanceof DOMElement) {
138                 switch(strtolower($node->tagName)) {
139                     case 'b':
140                     case 'i':
141                     case 'u':
142                     case 'sup':
143                     case 'sub':
144                     case 'span':
145                         $tag = DOMHelper::cloneNode($node, $saveto);
146                         $this->rewrite($node, $tag, FALSE);
147                         $saveto->appendChild($tag);
148                     break;
149                     
150                     case 'strong':
151                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
152                         $this->rewrite($node, $tag, FALSE);
153                         $saveto->appendChild($tag);
154                     break;
155                     
156                     case 'script':
157                     case 'style':
158                     break;
159                     
160                     case 'p':
161                         DOMHelper::rtrim($saveto);
162                         DOMHelper::insertElement('br', $saveto);
163                         DOMHelper::insertElement('br', $saveto);
164                         
165                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
166                         $this->rewrite($node, $tag, FALSE);
167                         $saveto->appendChild($tag);
168                         
169                         
170                         DOMHelper::insertElement('br', $saveto);
171                         DOMHelper::insertElement('br', $saveto);
172                     break;
173                     case 'a':
174                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
175                         
176                         $this->rewrite($node, $tag, FALSE);
177                         if($node->getAttribute('href') != $node->textContent) {
178                             $tag->appendChild($tag->ownerDocument->createTextNode(' ('.$node->getAttribute('href').')'));
179                         }
180                         
181                         $saveto->appendChild($tag);
182                     break;
183                     
184                     case 'h1':
185                         DOMHelper::rtrim($saveto);
186                         DOMHelper::insertElement('br', $saveto);
187                         DOMHelper::insertElement('br', $saveto);
188                         
189                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
190                         $tag2 = $tag->ownerDocument->createElement('u');
191                         $tag->appendChild($tag2);
192                         
193                         $this->rewrite($node, $tag2, FALSE);
194                         
195                         $saveto->appendChild($tag);
196                         
197                         DOMHelper::insertElement('br', $saveto);
198                     break;
199                     case 'h2':
200                         DOMHelper::rtrim($saveto);
201                         DOMHelper::insertElement('br', $saveto);
202                         DOMHelper::insertElement('br', $saveto);
203                         
204                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
205                         $this->rewrite($node, $tag, FALSE);
206                         $saveto->appendChild($tag);
207                         
208                         
209                         DOMHelper::insertElement('br', $saveto);
210                     break;
211                     case 'h3':
212                         DOMHelper::rtrim($saveto);
213                         DOMHelper::insertElement('br', $saveto);
214                         DOMHelper::insertElement('br', $saveto);
215                         
216                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
217                         $this->rewrite($node, $tag, FALSE);
218                         $saveto->appendChild($tag);
219                         
220                         
221                         DOMHelper::insertElement('br', $saveto);
222                     break;
223                     
224                     case 'ul':
225                         DOMHelper::rtrim($saveto);
226                         DOMHelper::insertElement('br', $saveto);
227                         
a430ee 228                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
8bd4d9 229                         $this->rewrite($node, $tag, FALSE);
JK 230                         $saveto->appendChild($tag);
231                     break;
232                     case 'ol':
233                         DOMHelper::rtrim($saveto);
234                         DOMHelper::insertElement('br', $saveto);
235                         
236                         if(!$node->hasAttribute('start') || !ctype_digit($node->getAttribute('start'))) {
237                             $node->setAttribute('start', 1);
238                         }
239                         
a430ee 240                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
8bd4d9 241                         $this->rewrite($node, $tag, FALSE);
JK 242                         $saveto->appendChild($tag);
243                     break;
244                     case 'li':
245                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
246                         
247                         if(strtolower($dom->tagName) == 'ul') {
248                             $tag->appendChild($tag->ownerDocument->createTextNode('- '));
249                         }
250                         elseif(strtolower($dom->tagName) == 'ol') {
251                             $tag->appendChild($tag->ownerDocument->createTextNode($dom->getAttribute('start').'. '));
252                             
253                             $dom->setAttribute('start', $dom->getAttribute('start')+1);
254                         }
255                         
256                         $this->rewrite($node, $tag, FALSE);
257                         $saveto->appendChild($tag);
258                         
259                         $saveto->appendChild($saveto->ownerDocument->createElement('br'));
260                     break;
261                     
262                     case 'br':
263                     case 'img':
264                         $tag = DOMHelper::cloneNode($node, $saveto);
265                         $saveto->appendChild($tag);
266                     break;
267                     
268                     default:
269                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
270                         $this->rewrite($node, $tag, FALSE);
271                         $saveto->appendChild($tag);
272                     break;
273                 }
274             }
275             elseif($node instanceof DOMText) {
276                 $val = strtr($node->nodeValue, array("\n" => '', "\r" => ''));
277                 if($val) {
278                     $saveto->appendChild($saveto->ownerDocument->createTextNode($val));
279                 }
280             }
281             else
282             {
283                 $saveto->appendChild($saveto->ownerDocument->importNode($node, TRUE));
284             }
285         }
286         
287         if($top) {
288             DOMHelper::trim($saveto);
289         }
290         
291         foreach($saveto->childNodes as $node) {
292             if(($node instanceof DOMElement) && $node->tagName == 'span' && $node->attributes->length == 0) {
293                 while($node->hasChildNodes()) {
294                     $node->parentNode->insertBefore($node->firstChild, $node);
295                 }
296                 $node->parentNode->removeChild($node);
297             }
298         }
299         
300         foreach($saveto->childNodes as $node) {
301             if(($node instanceof DOMElement) && $node->hasAttribute('auto')) {
302                 $node->removeAttribute('auto');
303             }
304         }
305     }
306     
307     private function image($node) {
308         if($node->hasAttribute('src')) {
309             $src = $node->getAttribute('src');
310             $node->removeAttribute('src');
311             
312             if(!is_file($src)) {
313                 return;
314             }
315             
316             if(isset($this->images[$src])) {
317                 list($crc, $size, $name) = $this->images[$src];
318             }
319             else
320             {
321                 $size = filesize($src);
322                 if($size<0 || $size>262144) {
323                     return;
324                 }
325                 
326                 $crc = hash_file('crc32b', $src);
327                 $name = sprintf('%08s%08x', $crc, $size);
328                 
fb87e0 329                 $this->images[$src] = array($crc, $size, $name, $src);
8bd4d9 330             }
JK 331             
332             $node->setAttribute('name', $name);
333             
12f022 334             $this->format .= pack('vC', mb_strlen($this->old), self::FORMAT_IMAGE)
8bd4d9 335                     .pack('CCVV', 0x09, 0x01, $size, hexdec($crc));
4cd94b 336             $this->f_old = '';
8bd4d9 337         }
JK 338     }
339     
340     private function format(&$node) {
341         $node->setAttribute('beforeFormatType', ord($this->f_type));
342         $node->setAttribute('beforeFormatColor', base64_encode($this->f_color));
343         
344         if($node->hasAttribute('color')) {
345             $color = trim($node->getAttribute('color'));
346             if(substr($color, 0, 1)=='#' AND (strlen($color)==4 OR strlen($color)==7) AND ctype_xdigit(substr($color, 1))) {
347                 $node->setAttribute('style', 'color:'.$color.';'.$node->getAttribute('style'));
348                 
349                 $R = $G = $B = 0;
350                 if(strlen($color)==4) {
351                     $R = hexdec(str_repeat(substr($color, 1, 1), 2));
352                     $G = hexdec(str_repeat(substr($color, 2, 1), 2));
353                     $B = hexdec(str_repeat(substr($color, 3, 1), 2));
354                 }
355                 else
356                 {
357                     $R = hexdec(substr($color, 1, 2));
358                     $G = hexdec(substr($color, 3, 2));
359                     $B = hexdec(substr($color, 5, 2));
360                 }
361                 
362                 $this->f_color = chr($R).chr($G).chr($B);
363                 $this->f_type |= self::FORMAT_COLOR;
364             }
365             $node->removeAttribute('color');
366         }
367         
368         switch(strtolower($node->tagName)) {
369             case 'b':
370                 $this->f_type |= self::FORMAT_BOLD;
371             break;
372             case 'i':
373                 $this->f_type |= self::FORMAT_ITALIC;
374             break;
375             case 'u':
376                 $this->f_type |= self::FORMAT_UNDERLINE;
377             break;
378         }
379     }
380     
381     private function unformat($node) {
382         $this->f_type = chr($node->getAttribute('beforeFormatType'));
383         $node->removeAttribute('beforeFormatType');
384         
385         $this->f_color = base64_decode($node->getAttribute('beforeFormatColor'));
386         $node->removeAttribute('beforeFormatColor');
387         
388         return TRUE;
389     }
390     
391     private function cf() {
392         $format = pack('C', $this->f_type).$this->f_color;
393         
394         if($this->f_old != $format) {
395             $this->format .= pack('v', mb_strlen($this->old)).$format;
396             $this->f_old = $format;
397         }
398     }
399     
400     private function parse($dom) {
401         if(!($dom instanceof DOMElement)) {
402             throw new BotMsgException('Nieznany element DOM: '.$dom);
403         }
404         
405         foreach($dom->childNodes as $node) {
406             if($node instanceof DOMText || $node instanceof DOMEntity) {
407                 $this->cf();
408                 $this->old .= $node->nodeValue;
409             }
410             elseif($node instanceof DOMElement) {
411                 if($node->tagName == 'br') {
412                     $this->old .= "\r\n";
413                     continue;
414                 }
415                 elseif($node->tagName == 'img') {
416                     $this->image($node);
417                     continue;
418                 }
419                 
420                 $this->format($node);
421                 $this->parse($node);
422                 $this->unformat($node);
423             }
424             else
425             {
426                 throw new BotMsgException('Nieznany element DOM: '.$node);
427             }
428         }
429     }
430 }
431 ?>