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