Jacek Kowalski
2016-02-12 4aa6278f7fd40fdcbbde2be5c7e0e68c3117b44e
commit | author | age
8bd4d9 1 <?php
JK 2 class BotMsgException extends Exception {}
0868e0 3
JK 4 /**
5  * Interfejs dla klas przetwarzających wiadomości wychodzące
6  * do formatu właściwego dla danej sieci.
7  */
8bd4d9 8 interface BotMsgInterface {
0868e0 9     /**
JK 10      * Konstruktor
11      * @param BotMsg $msg Wiadomość do przetworzenia
12      */
8bd4d9 13     function __construct(BotMsg $msg);
0868e0 14     /**
JK 15      * Zwraca przetworzoną wiadomość
16      * @return string Wiadomość po przetworzeniu
17      */
8bd4d9 18     function __toString();
0868e0 19     
JK 20     /**
21      * Podaje na wyjście (np. za pomocą echo) wiadomość w formacie
22      * odpowiednim dla danego API, uwzględniając nagłówki HTTP
23      * i inne konieczne elementy.
24      */
8bd4d9 25     function sendPullResponse();
JK 26 }
27
0868e0 28 /**
JK 29  * Klasa reprezentująca wiadomość wychodzącą.
30  */
8bd4d9 31 class BotMsg {
JK 32     private $beautiful = TRUE;
33     private $parser = NULL;
34     private $html = NULL;
35     private $text = NULL;
36     private $raw = '';
37     
38     /**
0868e0 39      * Włącza lub wyłącza "upiększanie" konwertowanej
JK 40      * do czystego tekstu ({@link BotMsg::getText()}) wiadomości, np.:
41      *
42      * &lt;b&gt;abc&lt;/b&gt; zamieniane jest na \*abc\*
43      *
44      * &lt;h1&gt;efg&lt;h1&gt; przechodzi w = efg =
45      *
8bd4d9 46      * Domyślnie włączone
JK 47      * @param bool $set Ustawienie "upiększania"
48      */
0868e0 49     function setBeautiful($set = FALSE) {
JK 50         if($this->beautiful != $set) {
51             $this->text = $this->html = $this->parser = NULL;
52             $this->beautiful = (bool)$set;
53         }
54     }
55     
56     /**
57      * @deprecated Zastąpiono funkcją {@link BotMsg::setBeautiful()}
58      */
8bd4d9 59     function beautifulText($set = FALSE) {
0868e0 60         $this->setBeautiful($set);
8bd4d9 61     }
JK 62     
63     /**
64      * Konstruktor. De facto alias dla {@link BotMsg::append()}
65      */
66     function __construct($str = NULL) {
67         if($str !== NULL) {
68             $this->append($str);
69         }
70     }
71     
72     /**
73      * Serializacja klasy wymaga zapisania tylko niektórych elementów
74      */
75     function __sleep() {
76         return array('beautiful', 'raw');
77     }
78     
79     /**
80      * Alias dla {@link BotMsg::append()}
81      */
82     function a($str) {
83         $this->append($str);
84     }
85     
86     /**
87      * Dodaje kod HTML na koniec wiadomości
88      * @param string $str Treść do dodania
89      */
90     function append($str) {
91         $this->text = $this->html = $this->parser = NULL;
92         $this->raw .= (string)$str;
93     }
94     
95     /**
96      * Zwraca wiadomość jako czysty tekst
97      * @return string Wiadomość
98      */
99     function getText() {
100         if($this->text === NULL) {
101             $this->text = trim($this->parseTextDOM($this->getParser()->getElementsByTagName('body')->item(0)));
102         }
103         
104         return $this->text;
105     }
106     
107     /**
108      * Zwraca wiadomość jako kod HTML
109      * @return string Wiadomość
110      */
111     function getHTML() {
112         if($this->html === NULL) {
113             $doc = $this->getParser();
114             $this->parseHTMLDOM( $doc->getElementsByTagName('body')->item(0) );
115             $this->html = $doc->saveXML( $doc->getElementsByTagName('body')->item(0) );
116         }
117         
118         return (string)substr($this->html, 6, -7);
119     }
120     
121     /**
122      * Zwraca treść wiadomości zapisaną przy użyciu {@link BotMsg::append()} bez żadnych modyfikacji
123      * @return string Oryginalna wiadomość
124      */
125     function getRaw() {
126         return $this->raw;
127     }
128     
129     /**
130      * Zwraca wiadomość jako kod HTML
131      * @return string Wiadomość
132      */
133     function __toString() {
134         return $this->getHTML();
135     }
136     
137     /**
138      * Zwraca kopię drzewa DOM wiadomości
139      * @return DOMDocument Wiadomość
140      */
141     function getParser() {
142         if($this->parser === NULL) {
143             $this->parser = new DOMDocument('1.0', 'utf-8');
144             try {
145                 $this->parser->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$this->raw.'</body></html>');
146             }
147             catch(ErrorException $e) {
148                 if($e->getSeverity() != E_WARNING) {
149                     throw $e;
150                 }
151             }
152             
153             foreach($this->parser->getElementsByTagName('a') as $node) {
154                 if(!$node->hasAttribute('href')) {
155                     $node->setAttribute('href', $node->textContent);
156                 }
157             }
158         }
159         
160         return $this->parser->cloneNode(TRUE);
161     }
162     
163     private function parseTextDOM($dom) {
164         if(!($dom instanceof DOMElement)) {
165             throw new BotMsgException('Nieznany element DOM: '.get_class($dom));
166         }
167         
168         $return = '';
169         foreach($dom->childNodes as $node) {
170             if($node instanceof DOMText || $node instanceof DOMEntity) {
171                 $return .= strtr($node->nodeValue, array("\n" => '', "\r" => ''));
172             }
173             elseif($node instanceof DOMElement) {
174                 switch(strtolower($node->tagName)) {
175                     case 'b':
176                     case 'strong':
177                         $return .= ($this->beautiful ? '*' : '').$this->parseTextDOM($node).($this->beautiful ? '*' : '');
178                     break;
179                     case 'u':
180                         $return .= ($this->beautiful ? '_' : '').$this->parseTextDOM($node).($this->beautiful ? '_' : '');
181                     break;
182                     case 'i':
183                         $return .= ($this->beautiful ? '/' : '').$this->parseTextDOM($node).($this->beautiful ? '/' : '');
184                     break;
185                     case 'br':
186                         $return .= "\n";
187                     break;
188                     case 'p':
189                         if(substr($return, -1) != "\n") {
190                             $return .= "\n\n";
191                         }
192                         $return .= $this->parseTextDOM($node)."\n\n";
193                     break;
194                     case 'h1':
195                         if(substr($return, -1) != "\n") {
196                             $return .= "\n\n";
197                         }
198                         $return .= ($this->beautiful ? '= ' : '').$this->parseTextDOM($node).($this->beautiful ? ' =' : '')."\n";
199                     break;
200                     case 'h2':
201                         if(substr($return, -1) != "\n") {
202                             $return .= "\n\n";
203                         }
204                         $return .= ($this->beautiful ? '== ' : '').$this->parseTextDOM($node).($this->beautiful ? ' ==' : '')."\n";
205                     break;
206                     case 'h3':
207                         if(substr($return, -1) != "\n") {
208                             $return .= "\n\n";
209                         }
210                         $return .= ($this->beautiful ? '=== ' : '').$this->parseTextDOM($node).($this->beautiful ? ' ===' : '')."\n";
211                     break;
212                     case 'td':
213                         $return .= $this->parseTextDOM($node)."\t";
214                     break;
215                     case 'th':
216                         $return .= ($this->beautiful ? '*' : '').$this->parseTextDOM($node).($this->beautiful ? '*' : '')."\t";
217                     break;
218                     case 'tr':
219                         $return .= $this->parseTextDOM($node)."\n";
220                     break;
221                     case 'a':
222                         $return .= $this->parseTextDOM($node);
223                         
224                         if($node->getAttribute('href') != $node->textContent) {
225                             $return .= ' ('.$node->getAttribute('href').')';
226                         }
227                     break;
228                     case 'script':
229                     case 'style':
230                     case 'img':
231                     break;
232                     default:
233                         $return .= $this->parseTextDOM($node);
234                     break;
235                 }
236             }
237             else
238             {
239                 throw new BotMsgException('Nieznany element DOM: '.get_class($node));
240             }
241         }
242         
0868e0 243         return trim($return);
8bd4d9 244     }
JK 245     
246     private function parseHTMLDOM($dom) {
247         if(!($dom instanceof DOMNode)) {
248             throw new BotMsgException('Nieznany element DOM: '.get_class($dom));
249         }
250         
251         foreach($dom->childNodes as $node) {
252             if($node instanceof DOMElement) {
253                 if($node->hasAttribute('color')) {
254                     $color = trim($node->getAttribute('color'));
255                     $node->removeAttribute('color');
256                     if(substr($color, 0, 1)=='#' AND (strlen($color)==4 OR strlen($color)==7) AND ctype_xdigit(substr($color, 1))) {
257                         $node->setAttribute('style', 'color:'.$color.';'.$node->getAttribute('style'));
258                     }
259                 }
260                 
261                 $this->parseHTMLDOM($node);
262             }
263         }
264     }
265 }
266 ?>