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