Jacek Kowalski
2012-08-12 4cc259fef15c9a4212e635d2c62e4c9ddf9b4513
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() {
271560 118         if($this->format == '') {
JK 119             return '';
120         }
121         else
122         {
123             return pack('Cv', 0x02, strlen($this->format)).$this->format;
124         }
8bd4d9 125     }
JK 126     
127     /**
128      * Wyślij wiadomość na standardowe wyjście w sposób właściwy dla BotAPI
129      */
130     function sendPullResponse() {
131         header('Content-Type: application/x-gadu-gadu; charset=utf-8');
127d1a 132         echo $this->getGG();
8bd4d9 133     }
JK 134     
135     private function rewriteOne($node, $saveto) {
136         
137     }
138     
139     private function rewrite($dom, $saveto, $top = TRUE) {
140         if(!($dom instanceof DOMElement)) {
141             throw new BotMsgException('Nieznany element DOM: '.get_class($dom));
142         }
143         
144         foreach($dom->childNodes as $node) {
145             if($node instanceof DOMElement) {
146                 switch(strtolower($node->tagName)) {
147                     case 'b':
148                     case 'i':
149                     case 'u':
150                     case 'sup':
151                     case 'sub':
152                     case 'span':
153                         $tag = DOMHelper::cloneNode($node, $saveto);
154                         $this->rewrite($node, $tag, FALSE);
155                         $saveto->appendChild($tag);
156                     break;
157                     
158                     case 'strong':
159                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
160                         $this->rewrite($node, $tag, FALSE);
161                         $saveto->appendChild($tag);
162                     break;
163                     
164                     case 'script':
165                     case 'style':
166                     break;
167                     
168                     case 'p':
169                         DOMHelper::rtrim($saveto);
170                         DOMHelper::insertElement('br', $saveto);
171                         DOMHelper::insertElement('br', $saveto);
172                         
173                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
174                         $this->rewrite($node, $tag, FALSE);
175                         $saveto->appendChild($tag);
176                         
177                         
178                         DOMHelper::insertElement('br', $saveto);
179                         DOMHelper::insertElement('br', $saveto);
180                     break;
181                     case 'a':
182                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
183                         
184                         $this->rewrite($node, $tag, FALSE);
185                         if($node->getAttribute('href') != $node->textContent) {
186                             $tag->appendChild($tag->ownerDocument->createTextNode(' ('.$node->getAttribute('href').')'));
187                         }
188                         
189                         $saveto->appendChild($tag);
190                     break;
191                     
192                     case 'h1':
193                         DOMHelper::rtrim($saveto);
194                         DOMHelper::insertElement('br', $saveto);
195                         DOMHelper::insertElement('br', $saveto);
196                         
197                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
198                         $tag2 = $tag->ownerDocument->createElement('u');
199                         $tag->appendChild($tag2);
200                         
201                         $this->rewrite($node, $tag2, FALSE);
202                         
203                         $saveto->appendChild($tag);
204                         
205                         DOMHelper::insertElement('br', $saveto);
206                     break;
207                     case 'h2':
208                         DOMHelper::rtrim($saveto);
209                         DOMHelper::insertElement('br', $saveto);
210                         DOMHelper::insertElement('br', $saveto);
211                         
212                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
213                         $this->rewrite($node, $tag, FALSE);
214                         $saveto->appendChild($tag);
215                         
216                         
217                         DOMHelper::insertElement('br', $saveto);
218                     break;
219                     case 'h3':
220                         DOMHelper::rtrim($saveto);
221                         DOMHelper::insertElement('br', $saveto);
222                         DOMHelper::insertElement('br', $saveto);
223                         
224                         $tag = DOMHelper::cloneNode($node, $saveto, 'b');
225                         $this->rewrite($node, $tag, FALSE);
226                         $saveto->appendChild($tag);
227                         
228                         
229                         DOMHelper::insertElement('br', $saveto);
230                     break;
231                     
232                     case 'ul':
233                         DOMHelper::rtrim($saveto);
234                         DOMHelper::insertElement('br', $saveto);
235                         
a430ee 236                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
8bd4d9 237                         $this->rewrite($node, $tag, FALSE);
JK 238                         $saveto->appendChild($tag);
239                     break;
240                     case 'ol':
241                         DOMHelper::rtrim($saveto);
242                         DOMHelper::insertElement('br', $saveto);
243                         
244                         if(!$node->hasAttribute('start') || !ctype_digit($node->getAttribute('start'))) {
245                             $node->setAttribute('start', 1);
246                         }
247                         
a430ee 248                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
8bd4d9 249                         $this->rewrite($node, $tag, FALSE);
JK 250                         $saveto->appendChild($tag);
251                     break;
252                     case 'li':
253                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
254                         
255                         if(strtolower($dom->tagName) == 'ul') {
256                             $tag->appendChild($tag->ownerDocument->createTextNode('- '));
257                         }
258                         elseif(strtolower($dom->tagName) == 'ol') {
259                             $tag->appendChild($tag->ownerDocument->createTextNode($dom->getAttribute('start').'. '));
260                             
261                             $dom->setAttribute('start', $dom->getAttribute('start')+1);
262                         }
263                         
264                         $this->rewrite($node, $tag, FALSE);
265                         $saveto->appendChild($tag);
266                         
267                         $saveto->appendChild($saveto->ownerDocument->createElement('br'));
268                     break;
269                     
270                     case 'br':
271                     case 'img':
272                         $tag = DOMHelper::cloneNode($node, $saveto);
273                         $saveto->appendChild($tag);
274                     break;
275                     
276                     default:
277                         $tag = DOMHelper::cloneNode($node, $saveto, 'span');
278                         $this->rewrite($node, $tag, FALSE);
279                         $saveto->appendChild($tag);
280                     break;
281                 }
282             }
283             elseif($node instanceof DOMText) {
284                 $val = strtr($node->nodeValue, array("\n" => '', "\r" => ''));
285                 if($val) {
286                     $saveto->appendChild($saveto->ownerDocument->createTextNode($val));
287                 }
288             }
289             else
290             {
291                 $saveto->appendChild($saveto->ownerDocument->importNode($node, TRUE));
292             }
293         }
294         
295         if($top) {
296             DOMHelper::trim($saveto);
297         }
298         
299         foreach($saveto->childNodes as $node) {
300             if(($node instanceof DOMElement) && $node->tagName == 'span' && $node->attributes->length == 0) {
301                 while($node->hasChildNodes()) {
302                     $node->parentNode->insertBefore($node->firstChild, $node);
303                 }
304                 $node->parentNode->removeChild($node);
305             }
306         }
307         
308         foreach($saveto->childNodes as $node) {
309             if(($node instanceof DOMElement) && $node->hasAttribute('auto')) {
310                 $node->removeAttribute('auto');
311             }
312         }
313     }
314     
315     private function image($node) {
316         if($node->hasAttribute('src')) {
317             $src = $node->getAttribute('src');
318             $node->removeAttribute('src');
319             
320             if(!is_file($src)) {
321                 return;
322             }
323             
324             if(isset($this->images[$src])) {
325                 list($crc, $size, $name) = $this->images[$src];
326             }
327             else
328             {
329                 $size = filesize($src);
330                 if($size<0 || $size>262144) {
331                     return;
332                 }
333                 
334                 $crc = hash_file('crc32b', $src);
335                 $name = sprintf('%08s%08x', $crc, $size);
336                 
fb87e0 337                 $this->images[$src] = array($crc, $size, $name, $src);
8bd4d9 338             }
JK 339             
340             $node->setAttribute('name', $name);
341             
12f022 342             $this->format .= pack('vC', mb_strlen($this->old), self::FORMAT_IMAGE)
8bd4d9 343                     .pack('CCVV', 0x09, 0x01, $size, hexdec($crc));
JK 344         }
345     }
346     
347     private function format(&$node) {
348         $node->setAttribute('beforeFormatType', ord($this->f_type));
349         $node->setAttribute('beforeFormatColor', base64_encode($this->f_color));
350         
351         if($node->hasAttribute('color')) {
352             $color = trim($node->getAttribute('color'));
353             if(substr($color, 0, 1)=='#' AND (strlen($color)==4 OR strlen($color)==7) AND ctype_xdigit(substr($color, 1))) {
354                 $node->setAttribute('style', 'color:'.$color.';'.$node->getAttribute('style'));
355                 
356                 $R = $G = $B = 0;
357                 if(strlen($color)==4) {
358                     $R = hexdec(str_repeat(substr($color, 1, 1), 2));
359                     $G = hexdec(str_repeat(substr($color, 2, 1), 2));
360                     $B = hexdec(str_repeat(substr($color, 3, 1), 2));
361                 }
362                 else
363                 {
364                     $R = hexdec(substr($color, 1, 2));
365                     $G = hexdec(substr($color, 3, 2));
366                     $B = hexdec(substr($color, 5, 2));
367                 }
368                 
369                 $this->f_color = chr($R).chr($G).chr($B);
370                 $this->f_type |= self::FORMAT_COLOR;
371             }
372             $node->removeAttribute('color');
373         }
374         
375         switch(strtolower($node->tagName)) {
376             case 'b':
377                 $this->f_type |= self::FORMAT_BOLD;
378             break;
379             case 'i':
380                 $this->f_type |= self::FORMAT_ITALIC;
381             break;
382             case 'u':
383                 $this->f_type |= self::FORMAT_UNDERLINE;
384             break;
385         }
386     }
387     
388     private function unformat($node) {
389         $this->f_type = chr($node->getAttribute('beforeFormatType'));
390         $node->removeAttribute('beforeFormatType');
391         
392         $this->f_color = base64_decode($node->getAttribute('beforeFormatColor'));
393         $node->removeAttribute('beforeFormatColor');
394         
395         return TRUE;
396     }
397     
398     private function cf() {
399         $format = pack('C', $this->f_type).$this->f_color;
400         
401         if($this->f_old != $format) {
402             $this->format .= pack('v', mb_strlen($this->old)).$format;
403             $this->f_old = $format;
404         }
405     }
406     
407     private function parse($dom) {
408         if(!($dom instanceof DOMElement)) {
409             throw new BotMsgException('Nieznany element DOM: '.$dom);
410         }
411         
412         foreach($dom->childNodes as $node) {
413             if($node instanceof DOMText || $node instanceof DOMEntity) {
414                 $this->cf();
415                 $this->old .= $node->nodeValue;
416             }
417             elseif($node instanceof DOMElement) {
418                 if($node->tagName == 'br') {
419                     $this->old .= "\r\n";
420                     continue;
421                 }
422                 elseif($node->tagName == 'img') {
423                     $this->image($node);
424                     continue;
425                 }
426                 
427                 $this->format($node);
428                 $this->parse($node);
429                 $this->unformat($node);
430             }
431             else
432             {
433                 throw new BotMsgException('Nieznany element DOM: '.$node);
434             }
435         }
436     }
437 }
438 ?>