| 1 | <?php | 
|---|
| 2 | # -- BEGIN LICENSE BLOCK --------------------------------------- | 
|---|
| 3 | # | 
|---|
| 4 | # This file is part of Dotclear 2. | 
|---|
| 5 | # | 
|---|
| 6 | # Copyright (c) 2003-2010 Olivier Meunier & Association Dotclear | 
|---|
| 7 | # Licensed under the GPL version 2.0 license. | 
|---|
| 8 | # See LICENSE file or | 
|---|
| 9 | # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html | 
|---|
| 10 | # | 
|---|
| 11 | # -- END LICENSE BLOCK ----------------------------------------- | 
|---|
| 12 | if (!defined('DC_RC_PATH')) { return; } | 
|---|
| 13 |  | 
|---|
| 14 | /** | 
|---|
| 15 | @ingroup DC_CORE | 
|---|
| 16 | @brief Trackbacks sender and server | 
|---|
| 17 |  | 
|---|
| 18 | Sends and receives trackbacks. Also handles trackbacks auto discovery. | 
|---|
| 19 | */ | 
|---|
| 20 | class dcTrackback | 
|---|
| 21 | { | 
|---|
| 22 | public $core;       ///< <b>dcCore</b> dcCore instance | 
|---|
| 23 | public $table;      ///< <b>string</b> done pings table name | 
|---|
| 24 |  | 
|---|
| 25 | /** | 
|---|
| 26 | Object constructor | 
|---|
| 27 |  | 
|---|
| 28 | @param    core      <b>dcCore</b>       dcCore instance | 
|---|
| 29 | */ | 
|---|
| 30 | public function __construct($core) | 
|---|
| 31 | { | 
|---|
| 32 | $this->core =& $core; | 
|---|
| 33 | $this->con =& $this->core->con; | 
|---|
| 34 | $this->table = $this->core->prefix.'ping'; | 
|---|
| 35 | } | 
|---|
| 36 |  | 
|---|
| 37 | /// @name Send trackbacks | 
|---|
| 38 | //@{ | 
|---|
| 39 | /** | 
|---|
| 40 | Get all pings sent for a given post. | 
|---|
| 41 |  | 
|---|
| 42 | @param    post_id   <b>integer</b>      Post ID | 
|---|
| 43 | @return   <b>record</b> | 
|---|
| 44 | */ | 
|---|
| 45 | public function getPostPings($post_id) | 
|---|
| 46 | { | 
|---|
| 47 | $strReq = 'SELECT ping_url, ping_dt '. | 
|---|
| 48 | 'FROM '.$this->table.' '. | 
|---|
| 49 | 'WHERE post_id = '.(integer) $post_id; | 
|---|
| 50 |  | 
|---|
| 51 | return $this->con->select($strReq); | 
|---|
| 52 | } | 
|---|
| 53 |  | 
|---|
| 54 | /** | 
|---|
| 55 | Sends a ping to given <var>$url</var>. | 
|---|
| 56 |  | 
|---|
| 57 | @param    url            <b>string</b>       URL to ping | 
|---|
| 58 | @param    post_id        <b>integer</b>      Post ID | 
|---|
| 59 | @param    post_title     <b>string</b>       Post title | 
|---|
| 60 | @param    post_excerpt   <b>string</b>       Post excerpt | 
|---|
| 61 | @param    post_url       <b>string</b>       Post URL | 
|---|
| 62 | */ | 
|---|
| 63 | public function ping($url,$post_id,$post_title,$post_excerpt,$post_url) | 
|---|
| 64 | { | 
|---|
| 65 | if ($this->core->blog === null) { | 
|---|
| 66 | return false; | 
|---|
| 67 | } | 
|---|
| 68 |  | 
|---|
| 69 | $post_id = (integer) $post_id; | 
|---|
| 70 |  | 
|---|
| 71 | # Check for previously done trackback | 
|---|
| 72 | $strReq = 'SELECT post_id, ping_url FROM '.$this->table.' '. | 
|---|
| 73 | 'WHERE post_id = '.$post_id.' '. | 
|---|
| 74 | "AND ping_url = '".$this->con->escape($url)."' "; | 
|---|
| 75 |  | 
|---|
| 76 | $rs = $this->con->select($strReq); | 
|---|
| 77 |  | 
|---|
| 78 | if (!$rs->isEmpty()) { | 
|---|
| 79 | throw new Exception(sprintf(__('%s has still been pinged'),$url)); | 
|---|
| 80 | } | 
|---|
| 81 |  | 
|---|
| 82 | $data = array( | 
|---|
| 83 | 'title' => $post_title, | 
|---|
| 84 | 'excerpt' => $post_excerpt, | 
|---|
| 85 | 'url' => $post_url, | 
|---|
| 86 | 'blog_name' => trim(html::escapeHTML(html::clean($this->core->blog->name))) | 
|---|
| 87 | //,'__debug' => false | 
|---|
| 88 | ); | 
|---|
| 89 |  | 
|---|
| 90 | # Ping | 
|---|
| 91 | try | 
|---|
| 92 | { | 
|---|
| 93 | $http = self::initHttp($url,$path); | 
|---|
| 94 | $http->post($path,$data,'UTF-8'); | 
|---|
| 95 | $res = $http->getContent(); | 
|---|
| 96 | } | 
|---|
| 97 | catch (Exception $e) | 
|---|
| 98 | { | 
|---|
| 99 | throw new Exception(__('Unable to ping URL')); | 
|---|
| 100 | } | 
|---|
| 101 |  | 
|---|
| 102 | $pattern = | 
|---|
| 103 | '|<response>.*<error>(.*)</error>(.*)'. | 
|---|
| 104 | '(<message>(.*)</message>(.*))?'. | 
|---|
| 105 | '</response>|msU'; | 
|---|
| 106 |  | 
|---|
| 107 | if (!preg_match($pattern,$res,$match)) | 
|---|
| 108 | { | 
|---|
| 109 | throw new Exception(sprintf(__('%s is not a ping URL'),$url)); | 
|---|
| 110 | } | 
|---|
| 111 |  | 
|---|
| 112 | $ping_error = trim($match[1]); | 
|---|
| 113 | $ping_msg = (!empty($match[4])) ? $match[4] : ''; | 
|---|
| 114 |  | 
|---|
| 115 | if ($ping_error != '0') { | 
|---|
| 116 | throw new Exception(sprintf(__('%s, ping error:'),$url).' '.$ping_msg); | 
|---|
| 117 | } else { | 
|---|
| 118 | # Notify ping result in database | 
|---|
| 119 | $cur = $this->con->openCursor($this->table); | 
|---|
| 120 | $cur->post_id = $post_id; | 
|---|
| 121 | $cur->ping_url = $url; | 
|---|
| 122 | $cur->ping_dt = date('Y-m-d H:i:s'); | 
|---|
| 123 |  | 
|---|
| 124 | $cur->insert(); | 
|---|
| 125 | } | 
|---|
| 126 | } | 
|---|
| 127 | //@} | 
|---|
| 128 |  | 
|---|
| 129 | /// @name Receive trackbacks | 
|---|
| 130 | //@{ | 
|---|
| 131 | /** | 
|---|
| 132 | Receives a trackback and insert it as a comment of given post. | 
|---|
| 133 |  | 
|---|
| 134 | @param    post_id        <b>integer</b>      Post ID | 
|---|
| 135 | */ | 
|---|
| 136 | public function receive($post_id) | 
|---|
| 137 | { | 
|---|
| 138 | header('Content-Type: text/xml; charset=UTF-8'); | 
|---|
| 139 | if (empty($_POST)) { | 
|---|
| 140 | http::head(405,'Method Not Allowed'); | 
|---|
| 141 | echo | 
|---|
| 142 | '<?xml version="1.0" encoding="utf-8"?>'."\n". | 
|---|
| 143 | "<response>\n". | 
|---|
| 144 | "  <error>1</error>\n". | 
|---|
| 145 | "  <message>POST request needed</message>\n". | 
|---|
| 146 | "</response>"; | 
|---|
| 147 | return; | 
|---|
| 148 | } | 
|---|
| 149 |  | 
|---|
| 150 | $post_id = (integer) $post_id; | 
|---|
| 151 |  | 
|---|
| 152 | $title = !empty($_POST['title']) ? $_POST['title'] : ''; | 
|---|
| 153 | $excerpt = !empty($_POST['excerpt']) ? $_POST['excerpt'] : ''; | 
|---|
| 154 | $url = !empty($_POST['url']) ? $_POST['url'] : ''; | 
|---|
| 155 | $blog_name = !empty($_POST['blog_name']) ? $_POST['blog_name'] : ''; | 
|---|
| 156 | $charset = ''; | 
|---|
| 157 | $comment = ''; | 
|---|
| 158 |  | 
|---|
| 159 | $err = false; | 
|---|
| 160 | $msg = ''; | 
|---|
| 161 |  | 
|---|
| 162 | if ($this->core->blog === null) | 
|---|
| 163 | { | 
|---|
| 164 | $err = true; | 
|---|
| 165 | $msg = 'No blog.'; | 
|---|
| 166 | } | 
|---|
| 167 | elseif ($url == '') | 
|---|
| 168 | { | 
|---|
| 169 | $err = true; | 
|---|
| 170 | $msg = 'URL parameter is required.'; | 
|---|
| 171 | } | 
|---|
| 172 | elseif ($blog_name == '') { | 
|---|
| 173 | $err = true; | 
|---|
| 174 | $msg = 'Blog name is required.'; | 
|---|
| 175 | } | 
|---|
| 176 |  | 
|---|
| 177 | if (!$err) | 
|---|
| 178 | { | 
|---|
| 179 | $post = $this->core->blog->getPosts(array('post_id'=>$post_id,'post_type'=>'')); | 
|---|
| 180 |  | 
|---|
| 181 | if ($post->isEmpty()) | 
|---|
| 182 | { | 
|---|
| 183 | $err = true; | 
|---|
| 184 | $msg = 'No such post.'; | 
|---|
| 185 | } | 
|---|
| 186 | elseif (!$post->trackbacksActive()) | 
|---|
| 187 | { | 
|---|
| 188 | $err = true; | 
|---|
| 189 | $msg = 'Trackbacks are not allowed for this post or weblog.'; | 
|---|
| 190 | } | 
|---|
| 191 | } | 
|---|
| 192 |  | 
|---|
| 193 | if (!$err) | 
|---|
| 194 | { | 
|---|
| 195 | $charset = self::getCharsetFromRequest(); | 
|---|
| 196 |  | 
|---|
| 197 | if (!$charset) { | 
|---|
| 198 | $charset = mb_detect_encoding($title.' '.$excerpt.' '.$blog_name, | 
|---|
| 199 | 'UTF-8,ISO-8859-1,ISO-8859-2,ISO-8859-3,'. | 
|---|
| 200 | 'ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,'. | 
|---|
| 201 | 'ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15'); | 
|---|
| 202 | } | 
|---|
| 203 |  | 
|---|
| 204 | if (strtolower($charset) != 'utf-8') { | 
|---|
| 205 | $title = iconv($charset,'UTF-8',$title); | 
|---|
| 206 | $excerpt = iconv($charset,'UTF-8',$excerpt); | 
|---|
| 207 | $blog_name = iconv($charset,'UTF-8',$blog_name); | 
|---|
| 208 | } | 
|---|
| 209 |  | 
|---|
| 210 | $title = trim(html::clean($title)); | 
|---|
| 211 | $title = html::decodeEntities($title); | 
|---|
| 212 | $title = html::escapeHTML($title); | 
|---|
| 213 | $title = text::cutString($title,60); | 
|---|
| 214 |  | 
|---|
| 215 | $excerpt = trim(html::clean($excerpt)); | 
|---|
| 216 | $excerpt = html::decodeEntities($excerpt); | 
|---|
| 217 | $excerpt = preg_replace('/\s+/ms',' ',$excerpt); | 
|---|
| 218 | $excerpt = text::cutString($excerpt,252); | 
|---|
| 219 | $excerpt = html::escapeHTML($excerpt).'...'; | 
|---|
| 220 |  | 
|---|
| 221 | $blog_name = trim(html::clean($blog_name)); | 
|---|
| 222 | $blog_name = html::decodeEntities($blog_name); | 
|---|
| 223 | $blog_name = html::escapeHTML($blog_name); | 
|---|
| 224 | $blog_name = text::cutString($blog_name,60); | 
|---|
| 225 |  | 
|---|
| 226 | $url = trim(html::clean($url)); | 
|---|
| 227 |  | 
|---|
| 228 | if (!$blog_name) { | 
|---|
| 229 | $blog_name = 'Anonymous blog'; | 
|---|
| 230 | } | 
|---|
| 231 |  | 
|---|
| 232 | $comment = | 
|---|
| 233 | "<!-- TB -->\n". | 
|---|
| 234 | '<p><strong>'.($title ? $title : $blog_name)."</strong></p>\n". | 
|---|
| 235 | '<p>'.$excerpt.'</p>'; | 
|---|
| 236 |  | 
|---|
| 237 | $cur = $this->core->con->openCursor($this->core->prefix.'comment'); | 
|---|
| 238 | $cur->comment_author = (string) $blog_name; | 
|---|
| 239 | $cur->comment_site = (string) $url; | 
|---|
| 240 | $cur->comment_content = (string) $comment; | 
|---|
| 241 | $cur->post_id = $post_id; | 
|---|
| 242 | $cur->comment_trackback = 1; | 
|---|
| 243 | $cur->comment_status = $this->core->blog->settings->system->trackbacks_pub ? 1 : -1; | 
|---|
| 244 | $cur->comment_ip = http::realIP(); | 
|---|
| 245 |  | 
|---|
| 246 | try | 
|---|
| 247 | { | 
|---|
| 248 | # --BEHAVIOR-- publicBeforeTrackbackCreate | 
|---|
| 249 | $this->core->callBehavior('publicBeforeTrackbackCreate',$cur); | 
|---|
| 250 | if ($cur->post_id) { | 
|---|
| 251 | $comment_id = $this->core->blog->addComment($cur); | 
|---|
| 252 |  | 
|---|
| 253 | # --BEHAVIOR-- publicAfterTrackbackCreate | 
|---|
| 254 | $this->core->callBehavior('publicAfterTrackbackCreate',$cur,$comment_id); | 
|---|
| 255 | } | 
|---|
| 256 | } | 
|---|
| 257 | catch (Exception $e) | 
|---|
| 258 | { | 
|---|
| 259 | $err = 1; | 
|---|
| 260 | $msg = 'Something went wrong : '.$e->getMessage(); | 
|---|
| 261 | } | 
|---|
| 262 | } | 
|---|
| 263 |  | 
|---|
| 264 |  | 
|---|
| 265 | $debug_trace = | 
|---|
| 266 | "  <debug>\n". | 
|---|
| 267 | '    <title>'.$title."</title>\n". | 
|---|
| 268 | '    <excerpt>'.$excerpt."</excerpt>\n". | 
|---|
| 269 | '    <url>'.$url."</url>\n". | 
|---|
| 270 | '    <blog_name>'.$blog_name."</blog_name>\n". | 
|---|
| 271 | '    <charset>'.$charset."</charset>\n". | 
|---|
| 272 | '    <comment>'.$comment."</comment>\n". | 
|---|
| 273 | "  </debug>\n"; | 
|---|
| 274 |  | 
|---|
| 275 | $resp = | 
|---|
| 276 | '<?xml version="1.0" encoding="utf-8"?>'."\n". | 
|---|
| 277 | "<response>\n". | 
|---|
| 278 | '  <error>'.(integer) $err."</error>\n"; | 
|---|
| 279 |  | 
|---|
| 280 | if ($msg) { | 
|---|
| 281 | $resp .= '  <message>'.$msg."</message>\n"; | 
|---|
| 282 | } | 
|---|
| 283 |  | 
|---|
| 284 | if (!empty($_POST['__debug'])) { | 
|---|
| 285 | $resp .= $debug_trace; | 
|---|
| 286 | } | 
|---|
| 287 |  | 
|---|
| 288 | echo $resp."</response>"; | 
|---|
| 289 | } | 
|---|
| 290 | //@} | 
|---|
| 291 |  | 
|---|
| 292 | private static function initHttp($url,&$path) | 
|---|
| 293 | { | 
|---|
| 294 | $client = netHttp::initClient($url,$path); | 
|---|
| 295 | $client->setTimeout(5); | 
|---|
| 296 | $client->setUserAgent('Dotclear - http://www.dotclear.org/'); | 
|---|
| 297 | $client->useGzip(false); | 
|---|
| 298 | $client->setPersistReferers(false); | 
|---|
| 299 |  | 
|---|
| 300 | return $client; | 
|---|
| 301 | } | 
|---|
| 302 |  | 
|---|
| 303 | private static function getCharsetFromRequest() | 
|---|
| 304 | { | 
|---|
| 305 | if (isset($_SERVER['CONTENT_TYPE'])) | 
|---|
| 306 | { | 
|---|
| 307 | if (preg_match('|charset=([a-zA-Z0-9-]+)|',$_SERVER['CONTENT_TYPE'],$m)) { | 
|---|
| 308 | return $m[1]; | 
|---|
| 309 | } | 
|---|
| 310 | } | 
|---|
| 311 |  | 
|---|
| 312 | return null; | 
|---|
| 313 | } | 
|---|
| 314 |  | 
|---|
| 315 | /// @name Trackbacks auto discovery | 
|---|
| 316 | //@{ | 
|---|
| 317 | /** | 
|---|
| 318 | Returns an array containing all discovered trackbacks URLs in | 
|---|
| 319 | <var>$text</var>. | 
|---|
| 320 |  | 
|---|
| 321 | @param    text      <b>string</b>       Input text | 
|---|
| 322 | @return   <b>array</b> | 
|---|
| 323 | */ | 
|---|
| 324 | public function discover($text) | 
|---|
| 325 | { | 
|---|
| 326 | $res = array(); | 
|---|
| 327 |  | 
|---|
| 328 | foreach ($this->getTextLinks($text) as $link) | 
|---|
| 329 | { | 
|---|
| 330 | if (($url = $this->getPingURL($link)) !== null) { | 
|---|
| 331 | $res[] = $url; | 
|---|
| 332 | } | 
|---|
| 333 | } | 
|---|
| 334 |  | 
|---|
| 335 | return $res; | 
|---|
| 336 | } | 
|---|
| 337 | //@} | 
|---|
| 338 |  | 
|---|
| 339 | private function getTextLinks($text) | 
|---|
| 340 | { | 
|---|
| 341 | $res = array(); | 
|---|
| 342 |  | 
|---|
| 343 | # href attribute on "a" tags | 
|---|
| 344 | if (preg_match_all('/<a ([^>]+)>/ms', $text, $match, PREG_SET_ORDER)) | 
|---|
| 345 | { | 
|---|
| 346 | for ($i = 0; $i<count($match); $i++) | 
|---|
| 347 | { | 
|---|
| 348 | if (preg_match('/href="(http:\/\/[^"]+)"/ms', $match[$i][1], $matches)) { | 
|---|
| 349 | $res[$matches[1]] = 1; | 
|---|
| 350 | } | 
|---|
| 351 | } | 
|---|
| 352 | } | 
|---|
| 353 | unset($match); | 
|---|
| 354 |  | 
|---|
| 355 | # cite attributes on "blockquote" and "q" tags | 
|---|
| 356 | if (preg_match_all('/<(blockquote|q) ([^>]+)>/ms', $text, $match, PREG_SET_ORDER)) | 
|---|
| 357 | { | 
|---|
| 358 | for ($i = 0; $i<count($match); $i++) | 
|---|
| 359 | { | 
|---|
| 360 | if (preg_match('/cite="(http:\/\/[^"]+)"/ms', $match[$i][2], $matches)) { | 
|---|
| 361 | $res[$matches[1]] = 1; | 
|---|
| 362 | } | 
|---|
| 363 | } | 
|---|
| 364 | } | 
|---|
| 365 |  | 
|---|
| 366 | return array_keys($res); | 
|---|
| 367 | } | 
|---|
| 368 |  | 
|---|
| 369 | private function getPingURL($url) | 
|---|
| 370 | { | 
|---|
| 371 | try | 
|---|
| 372 | { | 
|---|
| 373 | $http = self::initHttp($url,$path); | 
|---|
| 374 | $http->get($path); | 
|---|
| 375 | $page_content = $http->getContent(); | 
|---|
| 376 | } | 
|---|
| 377 | catch (Exception $e) | 
|---|
| 378 | { | 
|---|
| 379 | return false; | 
|---|
| 380 | } | 
|---|
| 381 |  | 
|---|
| 382 | $pattern_rdf = | 
|---|
| 383 | '/<rdf:RDF.*?>.*?'. | 
|---|
| 384 | '<rdf:Description\s+(.*?)\/>'. | 
|---|
| 385 | '.*?<\/rdf:RDF>'. | 
|---|
| 386 | '/msi'; | 
|---|
| 387 |  | 
|---|
| 388 | preg_match_all($pattern_rdf,$page_content,$rdf_all,PREG_SET_ORDER); | 
|---|
| 389 |  | 
|---|
| 390 | for ($i=0; $i<count($rdf_all); $i++) | 
|---|
| 391 | { | 
|---|
| 392 | $rdf = $rdf_all[$i][1]; | 
|---|
| 393 |  | 
|---|
| 394 | if (preg_match('/dc:identifier="'.preg_quote($url,'/').'"/msi',$rdf)) { | 
|---|
| 395 | if (preg_match('/trackback:ping="(.*?)"/msi',$rdf,$tb_link)) { | 
|---|
| 396 | return $tb_link[1]; | 
|---|
| 397 | } | 
|---|
| 398 | } | 
|---|
| 399 | } | 
|---|
| 400 |  | 
|---|
| 401 | return null; | 
|---|
| 402 | } | 
|---|
| 403 | } | 
|---|
| 404 | ?> | 
|---|