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