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