Dotclear

source: inc/core/class.dc.trackback.php @ 1671:7c0168380f80

Revision 1671:7c0168380f80, 10.8 KB checked in by Florent Cotton <florent.cotton@…>, 12 years ago (diff)

Commit intermédiaire portant uniquement sur la découverte et le ping des URLs de pingback (see #1370)

Line 
1<?php
2# -- BEGIN LICENSE BLOCK ---------------------------------------
3#
4# This file is part of Dotclear 2.
5#
6# Copyright (c) 2003-2013 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 -----------------------------------------
12if (!defined('DC_RC_PATH')) { return; }
13
14/**
15@ingroup DC_CORE
16@brief Trackbacks sender and server
17
18Sends and receives trackbacks. Also handles trackbacks auto discovery.
19*/
20class 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          $ping_parts = explode('|',$url);
83         
84          # Let's walk by the trackback way
85          if (count($ping_parts) < 2) {
86               $data = array(
87                    'title' => $post_title,
88                    'excerpt' => $post_excerpt,
89                    'url' => $post_url,
90                    'blog_name' => trim(html::escapeHTML(html::clean($this->core->blog->name)))
91                    //,'__debug' => false
92               );
93               
94               # Ping
95               try
96               {
97                    $http = self::initHttp($url,$path);
98                    $http->post($path,$data,'UTF-8');
99                    $res = $http->getContent();
100               }
101               catch (Exception $e)
102               {
103                    throw new Exception(__('Unable to ping URL'));
104               }
105               
106               $pattern =
107               '|<response>.*<error>(.*)</error>(.*)'.
108               '(<message>(.*)</message>(.*))?'.
109               '</response>|msU';
110               
111               if (!preg_match($pattern,$res,$match))
112               {
113                    throw new Exception(sprintf(__('%s is not a ping URL'),$url));
114               }
115               
116               $ping_error = trim($match[1]);
117               $ping_msg = (!empty($match[4])) ? $match[4] : '';
118          }
119          # Damnit ! Let's play pingback
120          else {
121               try {
122                    $xmlrpc = new xmlrpcClient($ping_parts[0]);
123                    $res = $xmlrpc->query('pingback.ping', $post_url, $ping_parts[1]);
124                    $ping_error = '0';
125               }
126               catch (xmlrpcException $e) {
127                    $ping_error = $e->getCode();
128                    $ping_msg = $e->getMessage(); 
129               }
130               catch (Exception $e) {
131                    throw new Exception(__('Unable to ping URL'));
132               }
133          }
134         
135          if ($ping_error != '0') {
136               throw new Exception(sprintf(__('%s, ping error:'),$url).' '.$ping_msg);
137          } else {
138               # Notify ping result in database
139               $cur = $this->con->openCursor($this->table);
140               $cur->post_id = $post_id;
141               $cur->ping_url = $url;
142               $cur->ping_dt = date('Y-m-d H:i:s');
143               
144               $cur->insert();
145          }
146     }
147     //@}
148     
149     /// @name Receive trackbacks
150     //@{
151     /**
152     Receives a trackback and insert it as a comment of given post.
153     
154     @param    post_id        <b>integer</b>      Post ID
155     */
156     public function receive($post_id)
157     {
158          header('Content-Type: text/xml; charset=UTF-8');
159          if (empty($_POST)) {
160               http::head(405,'Method Not Allowed');
161               echo
162               '<?xml version="1.0" encoding="utf-8"?>'."\n".
163               "<response>\n".
164               "  <error>1</error>\n".
165               "  <message>POST request needed</message>\n".
166               "</response>";
167               return;
168          }
169         
170          $post_id = (integer) $post_id;
171         
172          $title = !empty($_POST['title']) ? $_POST['title'] : '';
173          $excerpt = !empty($_POST['excerpt']) ? $_POST['excerpt'] : '';
174          $url = !empty($_POST['url']) ? $_POST['url'] : '';
175          $blog_name = !empty($_POST['blog_name']) ? $_POST['blog_name'] : '';
176          $charset = '';
177          $comment = '';
178         
179          $err = false;
180          $msg = '';
181         
182          if ($this->core->blog === null)
183          {
184               $err = true;
185               $msg = 'No blog.';
186          }
187          elseif ($url == '')
188          {
189               $err = true;
190               $msg = 'URL parameter is required.';
191          }
192          elseif ($blog_name == '') {
193               $err = true;
194               $msg = 'Blog name is required.';
195          }
196         
197          if (!$err)
198          {
199               $post = $this->core->blog->getPosts(array('post_id'=>$post_id,'post_type'=>''));
200               
201               if ($post->isEmpty())
202               {
203                    $err = true;
204                    $msg = 'No such post.';
205               }
206               elseif (!$post->trackbacksActive())
207               {
208                    $err = true;
209                    $msg = 'Trackbacks are not allowed for this post or weblog.';
210               }
211          }
212         
213          if (!$err)
214          {
215               $charset = self::getCharsetFromRequest();
216               
217               if (!$charset) {
218                    $charset = mb_detect_encoding($title.' '.$excerpt.' '.$blog_name,
219                    'UTF-8,ISO-8859-1,ISO-8859-2,ISO-8859-3,'.
220                    'ISO-8859-4,ISO-8859-5,ISO-8859-6,ISO-8859-7,ISO-8859-8,'.
221                    'ISO-8859-9,ISO-8859-10,ISO-8859-13,ISO-8859-14,ISO-8859-15');
222               }
223               
224               if (strtolower($charset) != 'utf-8') {
225                    $title = iconv($charset,'UTF-8',$title);
226                    $excerpt = iconv($charset,'UTF-8',$excerpt);
227                    $blog_name = iconv($charset,'UTF-8',$blog_name);
228               }
229               
230               $title = trim(html::clean($title));
231               $title = html::decodeEntities($title);
232               $title = html::escapeHTML($title);
233               $title = text::cutString($title,60);
234               
235               $excerpt = trim(html::clean($excerpt));
236               $excerpt = html::decodeEntities($excerpt);
237               $excerpt = preg_replace('/\s+/ms',' ',$excerpt);
238               $excerpt = text::cutString($excerpt,252); 
239               $excerpt = html::escapeHTML($excerpt).'...';
240               
241               $blog_name = trim(html::clean($blog_name));
242               $blog_name = html::decodeEntities($blog_name);
243               $blog_name = html::escapeHTML($blog_name);
244               $blog_name = text::cutString($blog_name,60);
245               
246               $url = trim(html::clean($url));
247               
248               if (!$blog_name) {
249                    $blog_name = 'Anonymous blog';
250               }
251               
252               $comment =
253               "<!-- TB -->\n".
254               '<p><strong>'.($title ? $title : $blog_name)."</strong></p>\n".
255               '<p>'.$excerpt.'</p>';
256               
257               $cur = $this->core->con->openCursor($this->core->prefix.'comment');
258               $cur->comment_author = (string) $blog_name;
259               $cur->comment_site = (string) $url;
260               $cur->comment_content = (string) $comment;
261               $cur->post_id = $post_id;
262               $cur->comment_trackback = 1;
263               $cur->comment_status = $this->core->blog->settings->system->trackbacks_pub ? 1 : -1;
264               $cur->comment_ip = http::realIP();
265               
266               try
267               {
268                    # --BEHAVIOR-- publicBeforeTrackbackCreate
269                    $this->core->callBehavior('publicBeforeTrackbackCreate',$cur);
270                    if ($cur->post_id) {
271                         $comment_id = $this->core->blog->addComment($cur);
272                         
273                         # --BEHAVIOR-- publicAfterTrackbackCreate
274                         $this->core->callBehavior('publicAfterTrackbackCreate',$cur,$comment_id);
275                    }
276               }
277               catch (Exception $e)
278               {
279                    $err = 1;
280                    $msg = 'Something went wrong : '.$e->getMessage();
281               }
282          }
283         
284         
285          $debug_trace =
286          "  <debug>\n".
287          '    <title>'.$title."</title>\n".
288          '    <excerpt>'.$excerpt."</excerpt>\n".
289          '    <url>'.$url."</url>\n".
290          '    <blog_name>'.$blog_name."</blog_name>\n".
291          '    <charset>'.$charset."</charset>\n".
292          '    <comment>'.$comment."</comment>\n".
293          "  </debug>\n";
294         
295          $resp =
296          '<?xml version="1.0" encoding="utf-8"?>'."\n".
297          "<response>\n".
298          '  <error>'.(integer) $err."</error>\n";
299         
300          if ($msg) {
301               $resp .= '  <message>'.$msg."</message>\n";
302          }
303         
304          if (!empty($_POST['__debug'])) {
305               $resp .= $debug_trace;
306          }
307         
308          echo $resp."</response>";
309     }
310     //@}
311     
312     private static function initHttp($url,&$path)
313     {
314          $client = netHttp::initClient($url,$path);
315          $client->setTimeout(5);
316          $client->setUserAgent('Dotclear - http://www.dotclear.org/');
317          $client->useGzip(false);
318          $client->setPersistReferers(false);
319         
320          return $client;
321     }
322     
323     private static function getCharsetFromRequest()
324     {
325          if (isset($_SERVER['CONTENT_TYPE']))
326          {
327               if (preg_match('|charset=([a-zA-Z0-9-]+)|',$_SERVER['CONTENT_TYPE'],$m)) {
328                    return $m[1];
329               }
330          }
331         
332          return null;
333     }
334     
335     /// @name Trackbacks auto discovery
336     //@{
337     /**
338     Returns an array containing all discovered trackbacks URLs in
339     <var>$text</var>.
340     
341     @param    text      <b>string</b>       Input text
342     @return   <b>array</b>
343     */
344     public function discover($text)
345     {
346          $res = array();
347         
348          foreach ($this->getTextLinks($text) as $link)
349          {
350               if (($url = $this->getPingURL($link)) !== null) {
351                    $res[] = $url;
352               }
353          }
354         
355          return $res;
356     }
357     //@}
358     
359     private function getTextLinks($text)
360     {
361          $res = array();
362         
363          # href attribute on "a" tags
364          if (preg_match_all('/<a ([^>]+)>/ms', $text, $match, PREG_SET_ORDER))
365          {
366               for ($i = 0; $i<count($match); $i++)
367               {
368                    if (preg_match('/href="(https?:\/\/[^"]+)"/ms', $match[$i][1], $matches)) {
369                         $res[$matches[1]] = 1;
370                    }
371               }
372          }
373          unset($match);
374         
375          # cite attributes on "blockquote" and "q" tags
376          if (preg_match_all('/<(blockquote|q) ([^>]+)>/ms', $text, $match, PREG_SET_ORDER))
377          {
378               for ($i = 0; $i<count($match); $i++)
379               {
380                    if (preg_match('/cite="(https?:\/\/[^"]+)"/ms', $match[$i][2], $matches)) {
381                         $res[$matches[1]] = 1;
382                    }
383               }
384          }
385         
386          return array_keys($res);
387     }
388     
389     private function getPingURL($url)
390     {
391          try
392          {
393               $http = self::initHttp($url,$path);
394               $http->get($path);
395               $page_content = $http->getContent();
396               $pb_url = $http->getHeader('x-pingback');
397          }
398          catch (Exception $e)
399          {
400               return false;
401          }
402         
403          # If we've got a X-Pingback header and it's a valid URL, it will be enough
404          if ($pb_url && filter_var($pb_url,FILTER_VALIDATE_URL) && preg_match('!^https?:!',$pb_url)) {
405               return $pb_url.'|'.$url;
406          }
407         
408          # No X-Pingback header ? OK, let's check for a trackback data chunk...
409          $pattern_rdf =
410          '/<rdf:RDF.*?>.*?'.
411          '<rdf:Description\s+(.*?)\/>'.
412          '.*?<\/rdf:RDF>'.
413          '/msi';
414         
415          preg_match_all($pattern_rdf,$page_content,$rdf_all,PREG_SET_ORDER);
416         
417          $url_path = parse_url($url, PHP_URL_PATH);
418          $sanitized_url = str_replace($url_path, html::sanitizeURL($url_path), $url);
419         
420          for ($i=0; $i<count($rdf_all); $i++)
421          {
422               $rdf = $rdf_all[$i][1];
423               if (preg_match('/dc:identifier="'.preg_quote($url,'/').'"/msi',$rdf) ||
424                    preg_match('/dc:identifier="'.preg_quote($sanitized_url,'/').'"/msi',$rdf)) {
425                    if (preg_match('/trackback:ping="(.*?)"/msi',$rdf,$tb_link)) {
426                         return $tb_link[1];
427                    }
428               }
429          }
430         
431          # Last call before the point of no return : a link rel=pingback, maybe ?
432          $pattern_pingback = '!<link rel="pingback" href="(.*?)"( /)?>!msi';
433         
434          if (preg_match($pattern_pingback,$page_content,$m)) {
435               $pb_url = $m[1];
436               if (filter_var($pb_url,FILTER_VALIDATE_URL) && preg_match('!^https?:!',$pb_url)) {
437                    return $pb_url.'|'.$url;
438               }
439          }
440         
441          return null;
442     }
443}
444?>
Note: See TracBrowser for help on using the repository browser.

Sites map