/[drupal]/contributions/modules/pingback/pingback.module
ViewVC logotype

Contents of /contributions/modules/pingback/pingback.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.8 - (show annotations) (download) (as text)
Sun Mar 22 18:07:27 2009 UTC (8 months ago) by haugstrup
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +1 -1 lines
File MIME type: text/x-php
Feature #241682 by jonathan1055: List past pingbacks as active links
1 <?php
2 // $Id: pingback.module,v 1.0.0.1 2007/07/26 23:17:13 dww Exp $
3
4 /**
5 * @file
6 * Main file for the Pingback module, which enables pingbacks for content.
7 */
8
9 /**
10 * Implementation of hook_perm().
11 */
12 function pingback_perm() {
13 return array('administer pingbacks');
14 }
15
16 /**
17 * Implementation of hook_menu().
18 */
19 function pingback_menu() {
20 $items['admin/settings/pingback'] = array(
21 'title' => 'Pingback',
22 'description' => 'Configure pingbacks',
23 'page callback' => 'drupal_get_form',
24 'page arguments' => array('pingback_settings_form'),
25 'access arguments' => array('administer pingbacks'),
26 'file' => 'pingback.admin.inc',
27 );
28 return $items;
29 }
30
31 /**
32 * Implementation of hook_form_alter().
33 */
34 function pingback_form_alter(&$form, &$form_state, $form_id) {
35 global $user;
36 if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
37 $type = $form['#node_type']->type;
38 $form['workflow']['pingback'] = array(
39 '#type' => 'radios',
40 '#title' => t('Pingbacks'),
41 '#options' => array(1 => t('Enabled'), 0 => t('Disabled')),
42 '#default_value' => _pingback_valid_for_node_type($type),
43 '#description' => t('Enable pingbacks for this node type.'),
44 );
45 }
46 else if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
47 $node = $form['#node'];
48 if (_pingback_valid_for_node_type($node->type)) {
49 // If there are any past successful pingbacks from this posting, add them
50 // to the node editing page.
51 $past_successes_listing = array();
52 $q = db_query('SELECT url FROM {pingback_sent} WHERE nid = %d', $node->nid);
53 while ($pb = db_fetch_object($q)) {
54 $past_successes_listing[] = l($pb->url,$pb->url);
55 }
56 // Add listing of successfully pingbacked URLs.
57 if (count($past_successes_listing)) {
58 $form['pingback'] = array(
59 '#type' => 'fieldset',
60 '#title' => t('Pingbacks'),
61 '#collapsible' => TRUE
62 );
63 $form['pingback'][] = array(
64 '#type' => 'markup',
65 '#value' => theme('item_list', $past_successes_listing, t('Successfully pingbacked URLs')),
66 );
67 }
68 }
69 }
70 // Hide pingback input format for anonymous users if desired.
71 elseif ($form_id == 'comment_form' && (!$user->uid) && variable_get('pingback_hide_format_for_anon', 0)) {
72 $alternate_formats = array();
73 foreach ($form['comment_filter']['format'] as $k => $v) {
74 if (!element_property($k) && isset($v['#return_value'])) {
75 if ($v['#return_value'] == variable_get('pingback_input_format', FILTER_FORMAT_DEFAULT)) {
76 unset($form['comment_filter']['format'][$k]);
77 }
78 else {
79 // Make a list of alternate formats for the comment form.
80 $alternate_formats[] = $k;
81 }
82 }
83 }
84 if (count($alternate_formats) == 1) {
85 // There is only one available format. Remove fieldset and go back to
86 // hidden form field.
87 $new_form[$alternate_formats[0]] = array(
88 '#type' => 'value',
89 '#value' => $alternate_formats[0],
90 '#parents' => $form['comment_filter']['format'][$alternate_formats[0]]['#parents'],
91 );
92 $new_form['format']['guidelines'] = array(
93 '#title' => t('Formatting guidelines'),
94 '#value' => $form['comment_filter']['format'][$alternate_formats[0]]['#description'],
95 );
96 $form['comment_filter']['format'] = $new_form;
97 }
98 }
99 }
100
101 /**
102 * Implementation of hook_theme().
103 */
104 function pingback_theme() {
105 return array(
106 'pingback' => array(
107 'file' => 'pingback.module',
108 'function' => 'theme_pingback',
109 ),
110 );
111 }
112
113 /**
114 * Menu callback: lists pingbacks. (TODO)
115 *
116 * @todo Actually write the function.
117 * @todo Ability to delete them!
118 * @todo Is pingback_list_pingbacks() a better name?
119 */
120 function pingback_list_page() {
121 return '';
122 }
123
124 /**
125 * Implementation of hook_nodeapi().
126 */
127 function pingback_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
128 // Check that the node is published (status is true), otherwise the attempted pingback will
129 // fail because this node will not be viewable by the server we are pinging.
130 if (_pingback_valid_for_node_type($node->type) && $node->status) {
131 switch ($op) {
132 case 'insert':
133 case 'update':
134 if (variable_get('pingback_mode', 'off') == 'submit') {
135 global $_pingback_nid;
136 $_pingback_nid = $node->nid;
137 }
138 else { //mode == 'cron'
139 //queue this nid in variable pingback_nid_queue, but take care for not queuing existing nids
140 $q = variable_get('pingback_nid_queue', array());
141 if (!in_array($node->nid, $q)) {
142 $q[] = $node->nid;
143 variable_set('pingback_nid_queue', $q);
144 }
145 }
146 break;
147 case 'view':
148 // Insert pingback header when a node is being viewed.
149 if (arg(0) == 'node' && is_numeric(arg(1)) && arg(3) == NULL) {
150 drupal_set_header('X-Pingback: '. $GLOBALS['base_url'] .'/xmlrpc.php');
151 }
152 break;
153 }
154 }
155 }
156
157 /**
158 * Implementation of hook_cron().
159 */
160 function pingback_cron() {
161 $q = variable_get('pingback_nid_queue', array());
162 $limit = variable_get('pingback_check_per_cron', 30);
163 $count = 0;
164 while (($nid = array_shift($q)) && ($count++ < $limit)) {
165 pingback_send_by_nid($nid, FALSE);
166 }
167 variable_set('pingback_nid_queue', $q);
168 }
169
170 /**
171 * Implementation of hook_exit().
172 */
173 function pingback_exit() {
174 global $_pingback_nid;
175 if (isset($_pingback_nid)) {
176 // Reset the node_load() cache.
177 node_load($_pingback_nid, NULL, TRUE);
178 pingback_send_by_nid($_pingback_nid, variable_get('pingback_notify_successful_pings', 1));
179 }
180 }
181
182 /**
183 * Implementation of hook_xmlrpc().
184 */
185 function pingback_xmlrpc() {
186 return array(array(
187 'pingback.ping',
188 'pingback_receive',
189 array('string', 'string', 'string'),
190 t('Handles pingback pings.'),
191 ));
192 }
193
194 /**
195 * XML-RPC callback: process pingback.ping() call.
196 */
197 function pingback_receive($pagelinkedfrom, $pagelinkedto) {
198 //return xmlrpc_server_error(0, 'abcdefgh');
199 //big thanks to WordPress codebase, specifically file xmlrpc.php, method pingback_ping() for becoming the reference implementation and theft victim ;)
200 //note: $pagelinkedto is a URL from our own site, $pagelinkedfrom is a foreign URL
201
202 if (!variable_get('pingback_receive', 1)) return xmlrpc_server_error(33, t("The specified target URL cannot be used as a target. It either doesn't exist, or it is not a pingback-enabled resource."));
203
204 //don't really understand this part, supposed to unescape ampersand entities?
205 $pagelinkedfrom = str_replace('&amp;', '&', $pagelinkedfrom);
206 $pagelinkedto = preg_replace('#&([^amp\;])#is', '&amp;$1', $pagelinkedto);
207 $error_code = -1;
208
209 // Check if the page linked to is in our site
210 $pos1 = strpos($pagelinkedto, str_replace(array('http://www.', 'http://', 'https://www.', 'https://'), '', $GLOBALS['base_url']));
211 if (!$pos1) {
212 return new xmlrpc_server_error(0, t('Is there no link to us?'));
213 }
214
215 // let's find which post is linked to
216 $nid = _pingback_url_to_nid($pagelinkedto);
217
218 $node = $nid ? node_load($nid) : FALSE;
219
220 if (!$node || !_pingback_valid_for_node($node)) { // node not found
221 return xmlrpc_server_error(33, t("The specified target URL cannot be used as a target. It either doesn't exist, or it is not a pingback-enabled resource."));
222 }
223
224 if ($nid == _pingback_url_to_nid($pagelinkedfrom)) {
225 return xmlrpc_server_error(0, t('The source URL and the target URL cannot both point to the same resource.'));
226 }
227
228 if (!$node->status) {
229 return xmlrpc_server_error(33, t("The specified target URL cannot be used as a target. It either doesn't exist, or it is not a pingback-enabled resource."));
230 }
231
232 // Let's check that the remote site didn't already pingback this entry
233 $result = db_result(db_query("SELECT COUNT(*) FROM {comments} WHERE nid = %d AND homepage = '%s' AND format = %d", $nid, $pagelinkedfrom, variable_get('pingback_input_format', FILTER_FORMAT_DEFAULT)));
234
235 if ($result > 0) { // We already have a Pingback from this URL
236 return xmlrpc_server_error(48, 'The pingback has already been registered.');
237 }
238
239 // very stupid, but gives time to the 'from' server to publish !
240 sleep(1);
241
242 // Let's check the remote site
243 $r = drupal_http_request($pagelinkedfrom);
244 if ($r->error)
245 return xmlrpc_server_error(16, 'The source URL does not exist.');
246 $linea = $r->data;
247
248 // Work around bug in strip_tags():
249 $linea = str_replace('<!DOC', '<DOC', $linea);
250 $linea = preg_replace('/[\s\r\n\t]+/', ' ', $linea); // normalize spaces
251 $linea = preg_replace('/ <(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body|br)[^>]*>/', "\n\n", $linea);
252
253 preg_match('|<title>([^<]*?)</title>|is', $linea, $matchtitle);
254 $title = check_plain($matchtitle[1]);
255 if (empty($title)) {
256 return xmlrpc_server_error(32, 'We cannot find a title on that page.');
257 }
258
259 $linea = strip_tags($linea, '<a>'); // just keep the tag we need
260
261 $p = explode("\n\n", $linea);
262
263 $preg_target = preg_quote($pagelinkedto);
264
265 foreach ($p as $para) {
266 if (strpos($para, $pagelinkedto) !== FALSE) { // it exists, but is it a link?
267 preg_match('|<a[^>]+?'. $preg_target .'[^>]*>([^>]+?)</a>|', $para, $context);
268
269 // If the URL isn't in a link context, keep looking
270 if (empty($context)) {
271 continue;
272 }
273
274 // We're going to use this fake tag to mark the context in a bit
275 // the marker is needed in case the link text appears more than once in the paragraph
276 //I edited <wpcontext></wpcontext> to <dpcontext></dpcontext> so it becomes more Drupal-ish!
277 $excerpt = preg_replace('|\</?dpcontext\>|', '', $para);
278
279 // prevent really long link text
280 if (drupal_strlen($context[1]) > 100) {
281 $context[1] = drupal_substr($context[1], 0, 100) .'...';
282 }
283
284 $marker = '<dpcontext>'. $context[1] .'</dpcontext>'; // set up our marker
285 $excerpt = str_replace($context[0], $marker, $excerpt); // swap out the link for our marker
286 $excerpt = strip_tags($excerpt, '<dpcontext>'); // strip all tags but our context marker
287 $excerpt = trim($excerpt);
288 $preg_marker = preg_quote($marker);
289 $excerpt = preg_replace("|.*?\s(.{0,100}$preg_marker.{0,100})\s.*|s", '$1', $excerpt);
290 $excerpt = strip_tags($excerpt); // YES, again, to remove the marker wrapper
291
292 break;
293 }
294 }
295
296 if (empty($context)) { // Link to target not found
297 return xmlrpc_server_error(17, t('The source URL does not contain a link to the target URL, and so cannot be used as a source.'));
298 }
299
300 //??? can someone explain about this?
301 $pagelinkedfrom = preg_replace('#&([^amp\;])#is', '&amp;$1', $pagelinkedfrom);
302
303 //TODO: a custom filter for $excerpt
304 $edit = array(
305 'nid' => $nid,
306 'subject' => t('Pingback'),
307 'comment' => '[...] '. $excerpt .' [...]',
308 'hostname' => ip_address(),
309 'format' => variable_get('pingback_input_format', FILTER_FORMAT_DEFAULT),
310 'name' => $title,
311 'homepage' => $pagelinkedfrom,
312 );
313 comment_save($edit);
314 /*
315 //bypass the hiding in pingback_form_alter() because we want to use the input format
316 $GLOBALS['pingback_bypass_format_hiding'] = TRUE;
317 drupal_execute('comment_form', $edit, array());
318 $GLOBALS['pingback_bypass_format_hiding'] = FALSE;
319 watchdog('debug', print_r(form_get_errors(), TRUE));
320 */
321 $message = t('Pingback from @source to @target registered! Keep the web talking! :-)', array('@source' => $pagelinkedfrom, '@target' => $pagelinkedto));
322 return $message;
323 }
324
325 /**
326 * Theme Pingback comments.
327 */
328 function theme_pingback($pb, $links = 0) {
329 return theme('comment', $pb, $links);
330 }
331
332 /**
333 * Discover a pingback server with pingback autodiscovery schemes.
334 * @param $target the absolute URL to search for its server. This should have passed check_url() first.
335 */
336 function pingback_discover($target) {
337 $server = '';
338 //#1: send a HEAD to check for X-Pingback header
339 $r = drupal_http_request($target, array(), 'HEAD');
340 if (empty($r->error)) {
341 if (is_array($r->headers) && isset($r->headers['X-Pingback'])) {
342 $server = $r->headers['X-Pingback'];
343 }
344 else {
345 //#2: search for <link rel="pingback" href="(server)" /> tags
346 $get = drupal_http_request($target);
347 if (empty($get->error)) {
348 //this regexp is the one provided in the spec
349 if (preg_match('#<link rel="pingback" href="([^"]+)" ?/?>#', $get->data, $matches)) {
350 $server = $matches[1];
351 }
352 }
353 }
354 }
355 if (!empty($server)) {
356 return check_url($server);
357 }
358 return '';
359 }
360
361 /**
362 * Send pingbacks. Does nothing if the target does not have a pingback server.
363 * @param $nid the source node ID.
364 * @param $target the target absolute URL.
365 * @param $source_is_absolute if this value is set to TRUE, $nid is interpreted as an absolute URL (which may originate not from the host site).
366 * @return TRUE on success, FALSE otherwise.
367 */
368 function pingback_send($nid, $target, $source_is_absolute = FALSE) {
369 if (!valid_url($target, TRUE)) {
370 watchdog('pingback', 'Target not valid URL: @url', array('@url' => $target), WATCHDOG_WARNING);
371 return FALSE;
372 }
373 if (!$source_is_absolute) {
374 $source = url("node/$nid", array('absolute' => TRUE));
375 $result = db_result(db_query("SELECT COUNT(*) FROM {pingback_sent} WHERE nid = %d AND url = '%s'", $nid, $target));
376 if ($result > 0) {
377 watchdog('pingback', 'Pingback already sent for: @nid', array('@nid' => $nid), WATCHDOG_WARNING);
378 return FALSE;
379 }
380 }
381 else {
382 $source = $nid;
383 if (!valid_url($source)) {
384 watchdog('pingback', 'Source not valid URL: @url', array('@url' => $source), WATCHDOG_WARNING);
385 return FALSE;
386 }
387 }
388 $retval = FALSE;
389
390 //server autodiscovery
391 $server = pingback_discover($target);
392
393 if (!empty($server)) {
394 if (xmlrpc($server, 'pingback.ping', $source, $target)) {
395 if (!$source_is_absolute) {
396 db_query("INSERT INTO {pingback_sent} (nid, url, timestamp) VALUES (%d, '%s', %d)", $nid, $target, time());
397 }
398 watchdog('pingback', 'Pingback to %target from %source succeeded.', array('%source' => $source, '%target' => $target));
399 return TRUE;
400 }
401 else {
402 watchdog('pingback', 'Pingback to %target from %source failed. Error @errno: @description', array('%source' => $source, '%target' => $target, '@errno' => xmlrpc_errno(), '@description' => xmlrpc_error_msg()), WATCHDOG_WARNING);
403 return FALSE;
404 }
405 }
406 // watchdog('pingback', 'Server not found', array(), WATCHDOG_WARNING);
407 return FALSE;
408 }
409
410 /**
411 * Sends pingbacks in all URLs in specified node.
412 */
413 function pingback_send_by_nid($nid, $message = TRUE) {
414 global $base_root;
415
416 $node = node_load($nid);
417 $prepared = node_prepare($node);
418 $urls = _pingback_extract_urls($prepared->body);
419 $successful = array();
420 foreach ($urls as $url) {
421 if (pingback_send($node->nid, $url) && $message) {
422 $successful[] = "<a href=\"$url\">$url</a>";
423 }
424 }
425 if ($message && count($successful)) {
426 drupal_set_message(t('!urls pingbacked successfully.', array('!urls' => implode(', ', $successful))));
427 }
428 }
429
430 function pingback_comment_is_pingback($comment) {
431 return $comment->format == variable_get('pingback_input_format', FILTER_FORMAT_DEFAULT);
432 }
433
434 function _pingback_valid_for_node_type($type) {
435 return variable_get("pingback_$type", ($type == 'story' || $type == 'blog') ? 1 : 0);
436 }
437
438 function _pingback_valid_for_node($node) {
439 return $node->comment == COMMENT_NODE_READ_WRITE;
440 }
441
442 function _pingback_extract_urls($text) {
443 //regexp is stolen from trackback.module ;)
444 preg_match_all("/(http|https):\/\/[a-zA-Z0-9@:%_~#?&=.,\/;-]*[a-zA-Z0-9@:%_~#&=\/;-]/", $text, $urls);
445 return array_unique($urls[0]);
446 }
447
448 /**
449 * Map any absolute url from this Drupal site to nid if applicable.
450 *
451 * Can also be used to check whether an absolute path is in the site and points
452 * to a node (e.g. node/1).
453 */
454 function _pingback_url_to_nid($url) {
455 //first check if the url is really in our site, as well as getting the non-base-url part
456 if (preg_match($a = '#^'. preg_quote($GLOBALS['base_url'], '#') .'/(.+)$#', $url, $matches)) {
457 if (!variable_get('clean_url', 0)) {
458 // Clean URLs not enabled. Strip '?q=' from URL.
459 $matches[1] = str_replace('?q=', '', $matches[1]);
460 }
461 if (preg_match($b = '#^node/([0-9]+)$#', drupal_get_normal_path($matches[1]), $matches2)) {
462 return $matches2[1];
463 }
464 }
465 return FALSE;
466 }

  ViewVC Help
Powered by ViewVC 1.1.2