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