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

Contents of /contributions/modules/subscribe/subscribe.module

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


Revision 1.27 - (show annotations) (download) (as text)
Wed May 9 13:46:45 2007 UTC (2 years, 6 months ago) by jvandyk
Branch: MAIN
CVS Tags: HEAD
Changes since 1.26: +2 -2 lines
File MIME type: text/x-php
#142418 by bottdev: correctly remove subscription_node entry upon node deletion
1 <?php
2 // $Id: subscribe.module,v 1.26 2006/11/11 05:05:31 jvandyk Exp $
3
4 /**
5 * @file
6 * Allow site to receive content via XML-RPC from Drupal sites running the publish module.
7 */
8
9 /********************************************************************
10 * Drupal Hooks :: General Overview
11 ********************************************************************/
12
13 /**
14 * Implementation of hook_help().
15 */
16 function subscribe_help($section) {
17 switch ($section) {
18 case 'admin/modules#description':
19 return t('Allow site receive content from Drupal sites running the publish module.');
20 case strstr($section, 'admin/subscribe/pull'):
21 return t('<p>Manually request updated content from the channel. Note that there may be a long pause after you initiate content tranfer.</p>');
22 }
23 }
24
25 /**
26 * Implementation of hook_menu().
27 */
28 function subscribe_menu($may_cache) {
29 $items = array();
30 $access = user_access('manage content subscriptions');
31
32 if ($may_cache) {
33 $items[] = array('path' => 'admin/subscribe', 'title' => t('subscribe'),
34 'callback' => 'subscribe_overview',
35 'access' => $access);
36 $items[] = array('path' => 'admin/subscribe/overview', 'title' => t('list'),
37 'type' => MENU_DEFAULT_LOCAL_TASK,
38 'weight' => -10);
39 $items[] = array('path' => 'admin/subscribe/sub/wizard', 'title' => t('subscribe to channel'),
40 'callback' => 'subscribe_url_form',
41 'access' => $access,
42 'type' => MENU_LOCAL_TASK);
43 $items[] = array('path' => 'admin/subscribe/sub/vocabmap', 'title' => t('Channel options'),
44 'callback' => 'subscribe_vocabmap_form',
45 'access' => $access,
46 'type' => MENU_CALLBACK);
47 $items[] = array('path' => 'admin/subscribe/delete', 'title' => t('Delete subscription'),
48 'callback' => 'subscribe_delete_form',
49 'access' => $access,
50 'type' => MENU_CALLBACK);
51 $items[] = array('path' => 'admin/subscribe/pull', 'title' => t('update content'),
52 'callback' => 'subscribe_pull_form',
53 'access' => $access,
54 'type' => MENU_CALLBACK);
55 }
56
57 return $items;
58 }
59
60 /**
61 * Implementation of hook_perm().
62 */
63 function subscribe_perm() {
64 return array('manage content subscriptions');
65 }
66
67 /********************************************************************
68 * Drupal Hooks :: Core
69 ********************************************************************/
70
71 /**
72 * Implementation of hook_xmlrpc().
73 *
74 * This is the server side of the xml-rpc request.
75 * Registering xml-rpc methods to callback functions.
76 */
77 function subscribe_xmlrpc() {
78 return array('drupal.subscribe.receive' => 'subscribe_xmls_receive');
79 }
80
81 /**
82 * Implementation of hook_nodeapi().
83 */
84 function subscribe_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
85 switch ($op) {
86 case 'load':
87 if ($remote = subscribe_node_load($node->nid)) {
88 foreach ($remote as $key => $value) {
89 $node->$key = $value;
90 }
91 }
92 break;
93
94 case 'view':
95 if (isset($node->remote_nid)) {
96 $node = theme_external_node($node, 0, 1);
97 }
98 break;
99
100 case 'delete':
101 if (isset($node->remote_nid) && isset($node->sid)) {
102 return db_query('DELETE FROM {subscribe_node} WHERE remote_nid = %d AND sid = %d', $node->remote_nid, $node->sid);
103 }
104 break;
105 }
106 }
107
108 /**
109 * Return the remote metadata for a node that's been imported.
110 *
111 * @param $nid
112 * the local nid of node
113 */
114 function subscribe_node_load($nid) {
115 return db_fetch_object(db_query('SELECT sn.remote_nid, sn.remote_uid, sn.remote_author, sn.sid, ss.base_url as remote_site_url, ss.name as remote_site FROM {subscribe_node} sn INNER JOIN {subscribe_subscriptions} ss ON ss.sid = sn.sid WHERE sn.nid = %d', $nid));
116
117 }
118
119 /**
120 * Theme function to control the look and feel of an external node.
121 */
122 function theme_external_node($node, $teaser = 0, $page = 0) {
123 $node->title = t('%node-title (from %remote-url)', array('%node-title' => $node->title, '%remote-url' => $node->remote_site_url));
124
125 $remote_url = $node->remote_site_url. '/';
126 $item[] = t('<a href="%remote-url">Visit this page</a>', array('%remote-url' => $remote_url. "node/$node->remote_nid"));
127 $item[] = t('Authored by: <a href="%remote-author-url">%remote-author</a>', array('%remote-author' => $node->remote_author, '%remote-author-url' => $remote_url. "user/$node->remote_uid"));
128
129 $referring_info = theme('item_list', $item);
130
131 $node->body .= $referring_info;
132 $node->teaser .= $referring_info;
133
134 return $node;
135 }
136
137 /********************************************************************
138 * Module Functions :: Subscribing
139 ********************************************************************/
140
141 /**
142 *
143 * Display all subscriptions.
144 */
145 function subscribe_overview() {
146 $output = '<h3>'. t('Subscribed to:') .'</h3>';
147 $header = array(t('Name'),t('URI'), t('Last Received'), t('Operations'));
148 $result = db_query('SELECT * FROM {subscribe_subscriptions}');
149 $row = array();
150 while ($sub = db_fetch_object($result)) {
151 $row[] = array(
152 $sub->name,
153 $sub->url,
154 $sub->last_update ? format_date($sub->last_update) : t('never'),
155 '<table><tr><td>' .
156 // l(t('edit'), "admin/subscribe/sub/$sub->sid/edit") . '</td><td>' .
157 l(t('pull'), "admin/subscribe/pull/$sub->sid") . '</td><td>' .
158 l(t('delete'), "admin/subscribe/delete/$sub->sid") . '</td></tr></table>'
159 );
160 }
161 $output .= ($row) ? theme('table', $header, $row) : '<p>' . t('You currently have no subscriptions.') . '</p><p>' . t('Would you like to') . ' ' . l(t('subscribe to a channel'),'admin/subscribe/sub/wizard') . '?</p>';
162
163 return $output;
164 }
165
166 function subscribe_delete_form() {
167 $sid = arg(3);
168 $sub = subscribe_subscription_load($sid);
169 if (!$sid || !$sub->sid) {
170 drupal_set_message(t('Invalid channel ID.', 'error'));
171 drupal_goto('admin/subscribe');
172 }
173 $form['sid'] = array('#type' => 'value', '#value' => $sid);
174 $output = confirm_form('subscribe_delete_form', $form,
175 t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $sub->name))),
176 'admin/subscribe', t('This action cannot be undone.'),
177 t('Delete'), t('Cancel') );
178 return $output;
179 }
180
181 function subscribe_delete_form_submit($form, $form_values) {
182 if ($form_values['confirm']) {
183 subscribe_subscription_delete($form_values['sid']);
184 }
185 return 'admin/subscribe';
186 }
187
188 function subscribe_delete($edit) {
189 $sub = subscribe_subscription_load($edit['sid']);
190 if ($edit['confirm']) {
191 global $user;
192 db_query('DELETE FROM {subscribe_subscriptions} WHERE sid = %d', $sub->sid);
193 watchdog('subscribe', t('%u: deleted %n', array('%u' => $user->name, '%n' => $sub->name)));
194 $result = subscribe_xmlc_cancel_subscription($sub);
195 }
196 else {
197 $extra = form_hidden('sid', $sub->sid);
198 $output = theme('confirm',
199 t('Are you sure you want to delete %title?', array('%title' => '<em>'. $sub->name .'</em>')),
200 $_GET['destination'] ? $_GET['destination'] : 'admin/subscribe',
201 t('This action cannot be undone. Nodes imported from this channel will not be deleted.'),
202 t('Delete'),
203 t('Cancel'),
204 $extra);
205 }
206
207 return $output;
208 }
209
210 function subscribe_url_form() {
211 // this form is a great opportunity for AJAXification
212 $edit = isset($_SESSION['edit']) ? $_SESSION['edit'] : array();
213 $form = array();
214 $form['channel'] = array(
215 '#type' => 'fieldset',
216 '#title' => 'Subscribe to channel'
217 );
218 $form['channel']['url'] = array(
219 '#type' => 'textfield',
220 '#title' => t('URL of site including XML-RPC endpoint'),
221 '#size' => '70',
222 '#maxlength' => '255',
223 '#description' => t('E.g. http://example.com/xmlrpc.php'),
224 '#default_value' => isset($edit['url']) ? $edit['url'] : 'http://'
225 );
226 $form['channel']['channel']['channel_id'] = array(
227 '#type' => 'textfield',
228 '#title' => 'Channel identifier',
229 '#size' => '10',
230 '#maxlength' => '255',
231 '#description' => t('Typically an integer corresponding to the channel ID'),
232 '#default_value' => isset($edit['channel_id']) ? $edit['channel_id'] : ''
233 );
234 $form['channel']['username'] = array(
235 '#type' => 'textfield',
236 '#title' => t('Username'),
237 '#size' => '10',
238 '#maxlength' => '60',
239 '#default_value' => isset($edit['username']) ? $edit['username'] : ''
240 );
241 $form['channel']['pass'] = array(
242 '#type' => 'password',
243 '#title' => t('Password'),
244 '#size' => '10',
245 '#maxlength' => '32'
246 );
247 $form['submit'] = array(
248 '#type' => 'submit',
249 '#value' => t('Continue')
250 );
251 unset($_SESSION['edit']);
252 return drupal_get_form('subscribe_url_form', $form);
253 }
254
255 /**
256 * Validator for initial subscription form.
257 */
258 function subscribe_url_form_validate($form_id, $form_values) {
259 if ($form_values['url'] == '') {
260 form_set_error('url', t('You must specify the URL of the channel.'));
261 }
262 elseif (!valid_url($form_values['url'], TRUE)) {
263 form_set_error('url', t('That is not a valid URL.'));
264 }
265 if (form_get_errors()) {
266 $_SESSION['edit'] = $form_values;
267 }
268 }
269
270 function subscribe_url_form_submit($form_id, $form_values) {
271 $url = $form_values['url'];
272 $channel_id = check_plain($form_values['channel_id']);
273 $username = check_plain($form_values['username']);
274 $pass = check_plain($form_values['pass']);
275 $result = subscribe_xmlc_may_subscribe($url, $channel_id, $username, $pass);
276 if ($result[0]) { // if user may subscribe
277 $form_values = array();
278 $form_values['name'] = $result[1];
279 $form_values['node_types'] = $result[2];
280 $form_values['vocabularies'] = $result[3];
281 $form_values['base_url'] = $result[4];
282 $form_values['channel_id'] = $channel_id;
283 $form_values['username'] = $username;
284 $form_values['pass'] = $pass;
285 $form_values['url'] = $url;
286
287 // now that we have a response we can do preliminary validation
288
289 if (!$form_values['node_types']) {
290 drupal_set_message(t('The channel is not currently publishing any node types. This is probably a misconfiguration on the publishing site.'), 'error');
291 drupal_goto('admin/subscribe/sub/wizard');
292 }
293
294 $nodetypes = node_get_types();
295 $missing = array();
296 foreach (array_keys($form_values['node_types']) as $type) {
297 if (!key_exists($type, $nodetypes)) {
298 $missing[] = $type;
299 }
300 }
301 if ($missing) {
302 if (count($missing) == 1) {
303 $type = $missing[0];
304 drupal_set_message(t('The remote site is publishing nodes of type %type but you do not support nodes of this type on your site. Please enable the appropriate module.', array('%type' => theme('placeholder', $type))), 'error');
305 }
306 else {
307 $phrase = '';
308 foreach ($missing as $type) {
309 $phrase .= ', ' . $type;
310 }
311 drupal_set_message(t('The remote site is publishing nodes of the following types: %types; but you do not support nodes of these types on your site. Please enable the appropriate modules.', array('%types' => theme('placeholder', ltrim($phrase, ',')), 'error')));
312 }
313 drupal_goto('admin/subscribe/sub/wizard');
314 }
315 }
316 else {
317 $msg = t('Unable to subscribe.');
318 drupal_set_message($msg, 'error');
319 $_SESSION['edit'] = $form_values;
320 drupal_goto('admin/subscribe/sub/wizard');
321 }
322 $_SESSION['edit'] = $form_values;
323 return ('admin/subscribe/sub/vocabmap');
324 }
325
326
327 /**
328 * The form for mapping vocabularies from remote vocabularies to local vocabularies
329 * if the publishing site is publishing vocabularies; also sets push/pull.
330 */
331 function subscribe_vocabmap_form() {
332 if (!isset($_SESSION['edit'])) {
333 drupal_set_message(t('Unable to retrieve session data.'), 'error');
334 drupal_goto('/subscribe/sub/wizard');
335 }
336 $edit = $_SESSION['edit'];
337 $remote_vocabularies = array();
338
339 foreach ($edit['vocabularies'] as $vid => $va) {
340 $remote_vocabularies[$vid] = (object)$va;
341 }
342
343 $vocabularies = taxonomy_get_vocabularies();
344 $voc_names = array();
345 $voc_options = array();
346 if (!$vocabularies && $remote_vocabularies) {
347 drupal_set_message(t('In order to map remote vocabularies to local vocabularies, you must first create your local vocabularies using administer &gt; categories.'), 'error');
348 }
349
350 foreach ($vocabularies as $v) {
351 $voc_names[$v->name] = $v->vid;
352 $voc_options[$v->vid] = $v->name;
353 }
354 $voc_options['0'] = t('discard');
355
356 // lots of error checking needs to go here to make sure
357 // the local vocabulary select box only includes vocabularies
358 // whose attributes match the remote vocabularies
359
360 $form = array();
361 $form['vocabmap'] = array();
362
363 foreach ($remote_vocabularies as $vid => $v) {
364 // autoselect
365 $selected_voc = '';
366 if (isset($voc_names[$v->name])) {
367 $selected_voc = $voc_names[$v->name];
368 }
369
370 $form['vocabmap']['localvoc' . $vid] = array(
371 '#type' => 'select',
372 '#options' => $voc_options,
373 '#default_value' => $selected_voc,
374 '#vname' => $v->name
375 );
376 }
377
378 $form['receiving'] = array(
379 '#type' => 'fieldset',
380 '#title' => t('Receiving Content')
381 );
382 $form['receiving']['pushpull'] = array(
383 '#type' => 'radios',
384 '#title' => t('Method'),
385 '#default_value' => isset($edit['pushpull']) ? $edit['pushpull'] : 0,
386 '#options' => array(t('Push: Receive content from channel automatically when the channel decides to send it.'), t('Pull: Receive content from channel only when you manually request it (your server must allow HTTP out).')),
387 '#description' => t('Choose how you would like this site to receive content.')
388 );
389 $form['submit'] = array(
390 '#type' => 'submit',
391 '#value' => t('Subscribe to channel')
392 );
393
394 return drupal_get_form('subscribe_vocabmap_form', $form);
395 }
396
397 function theme_subscribe_vocabmap_form($form) {
398 $header = array(t('Remote Vocabulary'), '', t('Local Vocabulary'));
399 $rows = array();
400 foreach (element_children($form['vocabmap']) as $key) {
401 $rows[] = array(
402 array('data' => $form['vocabmap'][$key]['#vname']),
403 array('data' => '&#8594;'),
404 array('data' => form_render($form['vocabmap'][$key]))
405 );
406 }
407 if ($rows) {
408 $output = theme('fieldset', array('#title' => t('Vocabulary Mapping'), '#children' => theme_table($header, $rows)));
409 }
410 $output .= form_render($form);
411 return $output;
412 }
413
414 function subscribe_vocabmap_form_validate($form, $form_values) {
415 if (!isset($_SESSION['edit'])) {
416 drupal_set_message(t('Unable to retrieve session data.'), 'error');
417 drupal_goto('/subscribe/sub/wizard');
418 }
419 $edit = $_SESSION['edit'];
420 if (!subscribe_subscription_validate($edit['url'], $edit['channel_id'])) {
421 drupal_set_message(t('You are already subscribed to this channel.'), 'error');
422 drupal_goto('admin/subscribe');
423 }
424 }
425
426 function subscribe_vocabmap_form_submit($form, $edit) {
427 if (!isset($_SESSION['edit'])) {
428 drupal_set_message(t('Unable to retrieve session data.'), 'error');
429 drupal_goto('/subscribe/sub/wizard');
430 }
431 $edit = array_merge($_SESSION['edit'], $edit);
432 unset($_SESSION['edit']);
433 $edit['channel_id'] = check_plain($edit['channel_id']);
434 $edit['username'] = check_plain($edit['username']);
435 $edit['pass'] = check_plain($edit['pass']);
436
437 $remote_vids = array();
438 $local_vids = array();
439 $vocab_map = _subscribe_extract_vocab_map($edit);
440 foreach ($vocab_map as $rvid => $local_vid) {
441 if ($local_vid != 0) {
442 $remote_vids[] = $rvid;
443 $local_vids[] = $local_vid;
444 }
445 }
446
447 $result = subscribe_xmlc_subscribe($edit['url'], $edit['channel_id'], $edit['username'], $edit['pass'], $edit['pushpull'], $local_vids, $remote_vids, array());
448 if ($result['status'] = 'ok') {
449 $edit['rsid'] = $result['sid'];
450 $edit['method'] = $edit['pushpull'];
451 $edit['sid'] = subscribe_subscription_save($edit);
452 subscribe_vocabmap_save($vocab_map, $edit['sid']);
453 drupal_set_message(t('Subscription to channel %name established.', array('%name' => theme('placeholder', $edit['name']))));
454 drupal_goto('admin/subscribe');
455 }
456 else {
457 drupal_set_message(t('Could not subscribe.'), 'error');
458 drupal_goto('admin/subscribe');
459 }
460 }
461
462 /**
463 * Save local-remote vocabulary mapping.
464 *
465 * @param $local_vocabularies
466 * Array which must contain local/remote mappings
467 * encoded as e.g., $edit['localvoc2'] = 4.
468 * @param $sid subscription id
469 *
470 * @return
471 *
472 */
473 function subscribe_vocabmap_save($local_vocabularies, $sid) {
474 db_query("DELETE FROM {subscribe_vocab_map} WHERE sid = '%s'", $sid);
475 foreach ($local_vocabularies as $lvid => $rvid) {
476 db_query("INSERT INTO {subscribe_vocab_map} (sid, remote_vid, local_vid) VALUES (%d, %d, %d)", $sid, $rvid, $lvid);
477 }
478 }
479
480 /**
481 * Generate a mapping of local vocabularies for a subscription
482 * keyed by remote vocabulary ID: $vocabmap[3] = 4 means that
483 * vocabulary 4 on the remote site maps to vocabulary 3 on this site.
484 *
485 * @param $sid
486 * the local subscription ID
487 */
488 function subscribe_get_vocabmap($sid) {
489 $vocabmap = array();
490 $result = db_query("SELECT * FROM {subscribe_vocab_map} WHERE sid = %d", $sid);
491 while ($data = db_fetch_object($result)) {
492 $vocabmap[$data->remote_vid] = taxonomy_get_vocabulary($data->local_vid);
493 }
494
495 return $vocabmap;
496 }
497
498
499 /**
500 * Using the URL of the channel, determine whether we already
501 * have a subscription to this channel.
502 *
503 * @param $url
504 * the URL of the channel
505 * @param $channel_id
506 * the id of the channel
507 */
508 function subscribe_subscription_validate($url, $channel_id) {
509 $result = db_query("SELECT * FROM {subscribe_subscriptions} WHERE url = '%s' AND channel_id = %d", $url, $channel_id);
510 $data = db_fetch_object($result);
511 return !$data;
512 }
513
514 /**
515 * Return a subscription object representing a database row for a given sid.
516 *
517 * @param $sid
518 * the local ID of the subscription
519 */
520 function subscribe_subscription_load($sid) {
521 return db_fetch_object(db_query('SELECT * FROM {subscribe_subscriptions} WHERE sid = %d', $sid));
522 }
523
524 /**
525 * Save a subscription to a channel to the database.
526 *
527 * @param $edit
528 * array from form;
529 */
530 function subscribe_subscription_save($edit) {
531 global $base_url;
532 global $user;
533 $fields = _subscribe_db_fields('subscribe_subscriptions');
534 // sid rsid name base_url url channel_id domain username pass token method last_update
535
536 $parts = parse_url($edit['url']);
537 $edit['domain'] = $parts['host'];
538
539 if ($edit['sid'] && db_result(db_query('SELECT COUNT(sid) FROM {subscribe_subscriptions} WHERE sid = %d', $edit['sid']))) { // existing subscription
540 // Prepare the query:
541 foreach ($edit as $key => $value) {
542 if (in_array($key, $fields)) {
543 $q[] = db_escape_string($key) ." = '%s'";
544 $v[] = $value;
545 }
546 }
547 db_query('UPDATE {subscribe_subscriptions} SET '. implode(', ', $q) .' WHERE sid = '. db_escape_string($edit['sid']), $v);
548 }
549 else { // new subscription
550 $edit['sid'] = db_next_id('subscribe');
551 $edit['token'] = md5($edit['username'] . $edit['pass'] . $base_url);
552 // Prepare the query:
553 foreach ($edit as $key => $value) {
554 if (in_array((string) $key, $fields)) {
555 $k[] = db_escape_string($key);
556 $v[] = $value;
557 $s[] = "'%s'";
558 }
559 }
560 // Insert the subscription record into the database:
561 db_query('INSERT INTO {subscribe_subscriptions} ('. implode(", ", $k) .') VALUES('. implode(', ', $s) .')', $v);
562 watchdog('subscribe', t('%u subscribed to channel %c', array('%u' => $user->name, '%c' => $edit['name'])));
563 }
564
565 return $edit['sid'];
566 }
567
568 function subscribe_subscription_delete($sid) {
569 global $user;
570 $sub = subscribe_subscription_load($sid);
571 db_query('DELETE FROM {subscribe_subscriptions} WHERE sid = %d', $sid);
572 db_query('DELETE FROM {subscribe_vocab_map WHERE sid = %d', $sid);
573 watchdog('subscribe', t('%u: deleted subscription %n', array('%u' => $user->name, '%n' => $sub->name)));
574 $result = subscribe_xmlc_cancel_subscription($sub);
575 }
576
577 /**
578 * Present the form where a user can request to receive updated data
579 * from a channel.
580 *
581 * @param $sid
582 * the local subscription ID
583 */
584 function subscribe_pull_form() {
585
586 $sid = arg(3);
587 $sub = subscribe_subscription_load($sid);
588 if (!$sid || !$sub->sid) {
589 drupal_set_message(t('Invalid channel ID.', 'error'));
590 drupal_goto('admin/subscribe');
591 }
592 $form['sid'] = array(
593 '#type' => 'value',
594 '#value' => $sid
595 );
596 $form['update'] = array(
597 '#type' => 'fieldset',
598 '#title' => t('Request content from channel %channel', array('%channel' => theme('placeholder', $sub->name)))
599 );
600 $form['update']['submit'] = array('#type' => 'submit',
601 '#value' => t('Update Content')
602 );
603
604 return drupal_get_form('subscribe_pull_form', $form);
605 }
606
607 function subscribe_pull_form_submit($form, $form_values) {
608 if ($form_values['sid']) {
609 $sub = subscribe_subscription_load($form_values['sid']);
610 subscribe_pull($sub);
611 }
612 return 'admin/subscribe';
613 }
614
615 /**
616 * Used to invoke content transfer at subscribing site's request.
617 *
618 * @param $subscription
619 * a subscription object, e.g. as returned by subscribe_subscription_load()
620 * @param $cond
621 * an array of triplets to constrain the results (see publish_publish())
622 */
623 function subscribe_pull($subscription, $cond = array()) {
624 $changed_filter_present = FALSE;
625 foreach ($cond as $triplet) {
626 if ($triplet[0] == 'changed') {
627 $changed_filter_present = TRUE;
628 }
629 }
630
631 if (!$changed_filter_present) {
632 $cond[] = array('changed', '>', $subscription->last_update);
633 }
634
635 $result = subscribe_xmlc_pull($subscription->url, $subscription->channel_id, $subscription->username, $subscription->pass, $subscription->rsid, $cond);
636 if (!$result) {
637 $error = xmlrpc_error_msg();
638 drupal_set_message($error, 'error');
639 }
640 if ($result['count']) {
641 $site_metadata = array('site_url' => $result['site_url'], 'site_name' => $result['site_name'], 'sid' => $subscription->sid);
642 list($n_counter, $u_counter, $t_counter) = subscribe_import($result['nodes'], $site_metadata);
643 drupal_set_message(t("New content from %name. Nodes imported: %ncount; nodes updated: %ucount; terms imported: %tcount.", array('%ncount' => $n_counter, '%ucount' => $u_counter, '%tcount' => $t_counter, '%name' => $subscription->name)));
644 db_query("UPDATE {subscribe_subscriptions} SET last_update = %d WHERE sid = %d", time(), $subscription->sid);
645 drupal_goto('admin/subscribe');
646 }
647 else {
648 drupal_set_message(t('Pull attempt failed for %url.', array('%url' => theme('placeholder', check_plain($subscription->url)))), 'error');
649 }
650 }
651
652 /********************************************************************
653 * Module Functions :: Node Importation
654 ********************************************************************/
655
656 /**
657 * Import nodes into this Drupal site.
658 *
659 * @param $nodes
660 * an array of nodes, typically from a remote site
661 * @param $site_metadata
662 * url and name of remote site and local subscription ID
663 * corresponding to the channel of the remote site
664 *
665 * @return an array of count of nodes imported, count of taxonomy terms imported
666 */
667 function subscribe_import($nodes, $site_metadata) {
668
669 // required metadata
670 $site_url = $site_metadata['site_url'];
671 $site_name = $site_metadata['site_name'];
672 $sid = $site_metadata['sid'];
673
674 $vocab_map = subscribe_get_vocabmap($sid);
675 $n_counter = 0;
676 $u_counter = 0;
677 $t_counter = 0;
678
679 foreach ($nodes as $n) {
680 $node = (object)$n;
681
682 // get "external" information
683 $remote_url = $node->url;
684 $remote_nid = $node->nid;
685 $remote_terms = $node->taxonomy_terms;
686 $remote_uid = $node->uid;
687 $remote_author = $node->name;
688
689 // discard remote taxonomy
690 unset($node->taxonomy);
691
692 // validate or map user
693 // currently we just assign to uid 1
694 $node->uid = '1';
695
696 // do we already have a copy of this node?
697 $local_nid = db_result(db_query("SELECT nid FROM {subscribe_node} WHERE remote_nid = %d AND sid = %d", $node->nid, $sid));
698
699 if ($local_nid) {
700 // substitute the local nid for the remote nid
701 $node->nid = $local_nid;
702
703 // we assign the revision ID of the old local node to the incoming, updated node
704 $local_node = node_load($local_nid);
705 $node->vid = $local_node->vid;
706
707 node_save($node);
708 $u_counter = $u_counter + 1;
709
710 // will terms be orphaned now if remote terms have changed?
711 }
712 else {
713 // force a new local nid
714 $node->nid = '';
715
716 // import node
717 node_save($node);
718 $local_nid = $node->nid;
719 watchdog('subscribe', t('%type: added %title from site %site.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%site' => theme('placeholder', $site_name))), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
720 $n_counter = $n_counter + 1;
721
722 // save map of remote/local node ID's (and uids?)
723 db_query("INSERT INTO {subscribe_node} (nid, sid, remote_nid, remote_uid, remote_author) VALUES (%d, %d, %d, %d, '%s')", $local_nid, $sid, $remote_nid, $remote_uid, $remote_author);
724
725 // add vocabulary information
726 foreach ($node->taxonomy_terms as $remote_term) {
727 // which vocabulary will we be adding it to?
728 $local_vid = $vocab_map[$remote_term['vid']]->vid;
729 if (!$local_vid) {
730 watchdog('subscribe', t('Discarding term %term because remote vocabulary id %vid is not mapped to a local vocabulary.', array('%term' => check_plain($remote_term->name), '%vid' => $remote_term->vid)));
731 continue;
732 }
733
734 $db_result = db_query("SELECT * FROM {term_data} WHERE LOWER('%s') LIKE LOWER(name) AND vid = %d", trim($remote_term['name']), $local_vid);
735 $local_term = db_fetch_object($db_result);
736
737 if ($local_term) { // existing term
738 // only add if this term is not already mapped to this node
739 $result = db_query("SELECT nid FROM {term_node} WHERE nid = %d AND tid = %d", $local_nid, $local_term->tid);
740 $nid = db_result($result);
741 if (!$nid) { // this nid-tid pair has not yet been mapped
742 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $local_nid, $local_term->tid);
743 }
744 }
745 else { // term does not exist locally; add new term
746 unset($remote_term['tid']); // local Drupal will assign a new tid
747 $remote_term['vid'] = $local_vid;
748 $remote_term['weight'] = 0; // discard remote weights
749 taxonomy_save_term($remote_term); // sets $remote_term->tid
750 $t_counter = $t_counter + 1;
751 db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $local_nid, $remote_term->tid);
752 }
753 }
754 }
755 }
756 return array($n_counter, $u_counter, $t_counter);
757 }
758
759
760 /********************************************************************
761 * Module Functions :: Sending XML-RPC
762 ********************************************************************/
763
764 /**
765 * Ask the channel if we may subscribe to it.
766 *
767 * @param $edit
768 * array containing a url key with url of the channel
769 *
770 * @return
771 * array containing maySubscribe (boolean), ppName (string),
772 * nodeTypes (array of published node types), vocabularies (array
773 * of published vocabularies)
774 */
775 function subscribe_xmlc_may_subscribe($url, $channel_id, $username, $pass) {
776
777 $result = xmlrpc($url, 'drupal.publish.maySubscribe', (int)$channel_id, $username, $pass);
778 if (!$result) {
779 $error = xmlrpc_error_msg();
780 if (!$error) {
781 $error = t('Are you sure you entered a valid XML-RPC endpoint?');
782 }
783 drupal_set_message($error, 'error');
784 watchdog('error', $error);
785 }
786 else {
787 return $result;
788 }
789 }
790
791 /**
792 * XMLRPC client to request that remote site which has the publish
793 * module installed establish a publishing relationship with client
794 * site (that is, this site).
795 *
796 * @param $url
797 * the URL of the channel on the publishing site
798 * which generally looks like http://drupal.org/publish/6
799 * @param $username
800 * the username with access on the publishing site
801 * @param $password
802 * the password with access on the publishing site
803 * @param $cond
804 * array of condition triplets; see publish_publish()
805 * e.g. ['nid', '=', '3']
806 *
807 * @return
808 * Subscription ID, a numeric key to to our subscription on the
809 * publishing site (similar to a user ID).
810 */
811 function subscribe_xmlc_subscribe($url, $channel_id, $username, $pass, $method, $local_vids, $remote_vids, $cond = array()) {
812 global $base_url;
813 $destination = $base_url . '/xmlrpc.php'; // so the remote server knows where to send updates
814
815 $result = xmlrpc($url, 'drupal.publish.subscribe', (int)$channel_id, $username, $pass, $destination, $method, $local_vids, $remote_vids, $cond);
816
817 if (!$result) {
818 $error = xmlrpc_error_msg();
819 drupal_set_message($error, 'error');
820 watchdog('error', $error);
821 }
822 else {
823 return $result;
824 }
825 }
826
827 /**
828 * XMLRPC client to request that remote site which has the publish
829 * module installed send an array of nodes to the client
830 * site (that is, this site).
831 *
832 * @param $rsid
833 * the subscription ID of this client on the publishing site
834 * @param $username
835 * the username with access on the publishing site
836 * @param $password
837 * the password with access on the publishing site
838 * @param $url
839 * the URI of the XML-RPC endpoint on the publishing site
840 * which generally looks like http://example.org/xmlrpc.php
841 * @param $cond
842 * array of condition triplets; see publish_publish()
843 * e.g. ['nid', '=', '3']
844 *
845 * @return
846 * Subscription ID, a numeric key to to our subscription on the
847 * publishing site (similar to a user ID).
848 */
849 function subscribe_xmlc_pull($url, $channel_id, $username, $pass, $rsid, $cond = array()) {
850
851 $result = xmlrpc($url, 'drupal.publish.pull', (int)$channel_id, (int)$rsid, $username, $pass, $cond);
852
853 if (!$result) {
854 $error = xmlrpc_error_msg();
855 drupal_set_message($error, 'error');
856 watchdog('error', $error);
857 }
858 else {
859 return $result;
860 }
861 }
862
863 function subscribe_xmlc_cancel_subscription($sub) {
864 $result = xmlrpc($sub->url, 'drupal.publish.cancelSubscription', (int)$sub->rsid, $sub->token);
865 if (!$result) {
866 $error = xmlrpc_error_msg();
867 drupal_set_message($error, 'error');
868 watchdog('error', $error);
869 }
870
871 return $result;
872 }
873
874
875 /********************************************************************
876 * Module Functions :: Receiving XML-RPC
877 ********************************************************************/
878
879 /*
880 * Receive nodes that a remote site is pushing to us
881 *
882 $message = array(
883 'token' => token,
884 'count' => count($nodes),
885 'site_url' => $base_url,
886 'site_name' => variable_get('site_name', 'drupal'),
887 'nodes' => $nodes
888 );
889 *
890 */
891
892 function subscribe_xmls_receive($message) {
893 $subscription = db_fetch_object(db_query("SELECT * FROM {subscribe_subscriptions} WHERE token = '%s' AND method = 'push' AND rsid = %d", $message['token'], $message['rsid']));
894 if (!$subscription) {
895 return xmlrpc_error(801, t('No such subscription'));
896 }
897
898
899 // verify that it's from a domain that we trust
900 $ip = $_SERVER['REMOTE_ADDR'];
901 if ($ip != '127.0.0.1') {
902 $host = gethostbyaddr($ip);
903 $parts = parse_url($host);
904 if ($parts['host'] != $subscription->domain) {
905 return xmlrpc_error(701, t('Publishing domain not trusted.'));
906 }
907 }
908
909 $site_metadata = array('site_url' => $message['site_url'], 'site_name' => $message['site_name'], 'sid' => $subscription->sid);
910 list($n_counter, $u_counter, $t_counter) = subscribe_import($message['nodes'], $site_metadata);
911 $result = t("New content from %name. Nodes imported: %ncount; noded updated: %ucount; terms imported: %tcount.", array('%ncount' => $n_counter, '%ucount' => $u_counter, '%tcount' => $t_counter, '%name' => $subscription->name));
912
913 return $result;
914 }
915
916 /********************************************************************
917 * Module Functions :: Private
918 ********************************************************************/
919
920 /**
921 * Utility function to get vocabulary mapping out of our encoded form.
922 *
923 * @return
924 * associative array keyed by local vocabulary ID
925 */
926 function _subscribe_extract_vocab_map($edit) {
927 $localvoc = array();
928 foreach ($edit as $key => $val) {
929 if (strstr($key, 'localvoc')) {
930 $lvid = substr($key, 8);
931 if (is_numeric($lvid) && ($lvid > 0)) $localvoc[$lvid] = $val;
932 }
933 }
934 return $localvoc;
935 }
936
937 /**
938 * Return an array of database column names for a given table.
939 *
940 * This function makes changing the database and module updates a little easier.
941 */
942 function _subscribe_db_fields($table_name) {
943 switch ($table_name) {
944 case 'subscribe_subscriptions':
945 return array('sid', 'rsid', 'name', 'base_url', 'url', 'channel_id', 'domain', 'username', 'pass', 'token', 'method', 'last_update');
946 }
947 }

  ViewVC Help
Powered by ViewVC 1.1.2