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

Contents of /contributions/modules/publish/publish.module

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


Revision 1.28 - (show annotations) (download) (as text)
Sat Nov 11 04:53:16 2006 UTC (3 years ago) by jvandyk
Branch: MAIN
CVS Tags: HEAD
Changes since 1.27: +5 -5 lines
File MIME type: text/x-php
correct object notation for PHP5
1 <?php
2 // $Id: publish.module,v 1.27 2006/09/06 11:59:52 jvandyk Exp $
3
4 /**
5 * @file
6 * Enable users to publish content to other Drupal sites (running subscribe.module) via XML-RPC.
7 */
8
9 define('PUBLISH_DEBUG_MODE', TRUE);
10 define('PUBLISH_PULL', 0);
11 define('PUBLISH_PUSH', 1);
12 define('PUBLISH_AUTHTYPE_NONE', 0);
13 define('PUBLISH_AUTHTYPE_PERM', 1);
14 define('PUBLISH_AUTHTYPE_SIMPLE', 2);
15 define('PUBLISH_WHENPUB_CRON', 0);
16 define('PUBLISH_WHENPUB_NODESUB', 1);
17
18
19 /********************************************************************
20 * Drupal Hooks :: General Overview
21 ********************************************************************/
22
23 /**
24 * Implementation of hook_help().
25 */
26 function publish_help($section) {
27 switch ($section) {
28 case 'admin/modules#description':
29 return t('Allow site to send content to other Drupal sites.');
30 case 'admin/publish' :
31 return t('<p>Channels can be subscribed to by other Drupal sites using the subscribe module.<p>');
32 case strstr($section, 'admin/publish/pub/nodes'):
33 return t('<p>Choose which which node types you would like to publish to other Drupal sites.</p>');
34 case strstr($section, 'admin/publish/pub/vocabularies'):
35 return t('<p>Choose which vocabularies will accompany the nodes you publish to other Drupal sites. If a checkbox does not appear next to a node type it is because the node type is not associated with the vocabulary (you can change associations using administer &gt; categories &gt; edit vocabulary.)</p>');
36 }
37 }
38
39 /**
40 * Implementation of hook_menu().
41 */
42 function publish_menu($may_cache) {
43 $items = array();
44 $admin = user_access('administer channels');
45
46 if (!$may_cache) {
47 if (arg(2) == 'pub' && is_numeric(arg(4))) {
48 $items[] = array(
49 'path' => 'admin/publish/pub/edit/' . arg(4),
50 'title' => t('edit'),
51 'callback' => 'publish_channel_edit',
52 'type' => MENU_CALLBACK,
53 'access' => $admin);
54 }
55 $items[] = array(
56 'path' =>'admin/publish/subscribers/' . arg(3),
57 'title' => t('subscribers'),
58 'callback' => 'publish_subscribers',
59 'type' => MENU_CALLBACK,
60 'access' => $admin);
61 $items[] = array('path' => 'admin/publish/filters/' . arg(3),
62 'title' => t('filters'),
63 'callback' => 'publish_channel_filter_form',
64 'type' => MENU_CALLBACK,
65 'access' => $admin);
66 $items[] = array('path' => 'admin/publish/edit/' . arg(4),
67 'title' => t('configure'),
68 'callback' => 'publish_channel_edit',
69 'type' => MENU_CALLBACK,
70 'access' => $admin);
71 }
72 else {
73 $items[] = array(
74 'path' => 'admin/publish',
75 'title' => t('publish'),
76 'callback' => 'publish_overview',
77 'access' => $admin);
78 $items[] = array(
79 'path' => 'admin/publish/pub/vocabularies',
80 'title' => t('vocabularies'),
81 'callback' => 'publish_vocabulary_form',
82 'type' => MENU_CALLBACK,
83 'access' => $admin);
84 $items[] = array(
85 'path' => 'admin/publish/list',
86 'title' => t('channels'),
87 'type' => MENU_DEFAULT_LOCAL_TASK,
88 'weight' => -10,
89 'callback' => 'publish_overview',
90 'access' => $admin);
91 $items[] = array(
92 'path' => 'admin/publish/add',
93 'title' => t('add channel'),
94 'type' => MENU_LOCAL_TASK,
95 'callback' => 'publish_channel_form',
96 'access' => $admin);
97 $items[] = array(
98 'path' => 'admin/publish/pub/delete',
99 'title' => t('delete'),
100 'callback' => 'publish_channel_delete_form',
101 'type' => MENU_CALLBACK,
102 'access' => $admin);
103 $items[] = array(
104 'path' => 'admin/publish/pub/filters/delete',
105 'title' => t('filters'),
106 'callback' => 'publish_channel_filter_delete_form',
107 'type' => MENU_CALLBACK,
108 'access' => $admin);
109 }
110 return $items;
111 }
112
113 /**
114 * Implementation of hook_perm().
115 */
116 function publish_perm() {
117 return array('subscribe to channels', 'administer channels');
118 }
119
120 /********************************************************************
121 * Drupal Hooks :: Core
122 ********************************************************************/
123
124 /**
125 * Implementation of hook_cron().
126 *
127 * Used to invoke content transfer when cron runs.
128 *
129 */
130 function publish_cron() {
131 publish_push();
132 }
133
134 /**
135 * Implementation of hook_nodeapi().
136 *
137 * Used to invoke content transfer when a node is submitted.
138 *
139 */
140 function publish_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
141 switch ($op) {
142 case 'insert':
143 case 'update':
144 // exit quickly if node is not in published state
145 if ($node->status != 1) {
146 return;
147 }
148 if (PUBLISH_DEBUG_MODE) watchdog('publish', t('nodeapi firing'));
149 // Find all subscribers to this node.
150 $result = db_query("SELECT * FROM {publish_channel} p INNER JOIN {publish_nodetypes} n ON n.channel_id = p.channel_id INNER JOIN {publish_subscribers} s ON s.channel_id = p.channel_id WHERE n.type = '%s'", $node->type);
151 $exists = db_result(db_query("SELECT nid FROM {publish_queue} WHERE nid = %d", $node->nid));
152 if ($exists) { // delete older version from queue
153 db_query("DELETE FROM {publish_queue} WHERE nid = %d", $node->nid);
154 }
155 $publish_now = FALSE;
156 while ($data = db_fetch_object($result)) {
157 if ($data->whenpub == PUBLISH_WHENPUB_NODESUB) {
158 $publish_now = TRUE;
159 }
160 // add them to the queue to have the node pushed out to them
161 // TODO: we should run the node through the channel's condition filters here?
162 db_query("INSERT INTO {publish_queue} (qid, nid, sid, channel_id, changed) VALUES (%d, %d, %d, %d, %d)", db_next_id('publish_queue'), $node->nid, $data->sid, $data->channel_id, $node->changed);
163 if (PUBLISH_DEBUG_MODE) watchdog('publish', t('channel %cid added node %nid to q for subscriber id %sid', array('%cid' => $data->channel_id, '%nid' => $node->nid, '%sid' => $data->sid)));
164 }
165 // set publish on exit if channel is set to publish at node submission time
166 if ($publish_now) {
167 if (PUBLISH_DEBUG_MODE) watchdog('publish', t('setting publish_exit'));
168 publish_exit(TRUE);
169 }
170 break;
171 }
172 }
173
174 /**
175 * Implementation of hook_exit().
176 *
177 * Used to invoke push content transfer
178 *
179 */
180 function publish_exit($set = FALSE) {
181 static $do_transfer = FALSE;
182
183 if ($do_transfer) {
184 publish_push();
185 return;
186 }
187
188 if ($set) {
189 $do_transfer = TRUE;
190 }
191 }
192
193 /**
194 * Implementation of hook_xmlrpc().
195 *
196 * This is the server side of the xml-rpc request.
197 * Registering xml-rpc methods to callback functions.
198 */
199 function publish_xmlrpc() {
200 return array(
201 array('drupal.publish.maySubscribe', 'publish_xmls_may_subscribe', array('array', 'int','string','string'), t('Return channel info if caller supplies appropriate credentials.')),
202 array('drupal.publish.subscribe', 'publish_xmls_receive_subscription', array('int', 'int', 'string', 'string', 'string', 'string', 'array', 'array', 'array'), t('Establish relationship with subscriber.')),
203 array('drupal.publish.pull', 'publish_xmls_publish', array('array', 'int', 'int', 'string', 'string', 'array'), t('Send nodes matching certain conditions to subscriber.')),
204 array('drupal.publish.getNode', 'publish_xmls_get_node', array('array', 'int', 'string', 'string', 'int'), t('Send single node to subscriber.')),
205 array('drupal.publish.getChannels', 'publish_xmls_get_channels', array('array'), t('Return list of public channels on this site.')),
206 array('drupal.publish.cancelSubscription', 'publish_xmls_cancel_subscription', array('boolean', 'int', 'string'), t('Cancel subscription.'))
207 );
208 }
209
210 /********************************************************************
211 * Module Functions :: Publishing
212 ********************************************************************/
213
214 /**
215 * Display list of channels
216 *
217 * @return
218 * HTML output
219 */
220 function publish_overview() {
221 $header = array(array('data' => t('ID')), array('data' => t('Name')), array('data' => t('Advertise')), array('data' => t('Subscribers')), array('data' => t('Operations')));
222 $result = db_query('SELECT * FROM {publish_channel}');
223 $row = array();
224 while ($data = db_fetch_object($result)) {
225 $edit_items = array();
226 $edit_items[] = l(t('edit'), "admin/publish/pub/edit/$data->channel_id");
227 $edit_items[] = l(t('vocabularies'), "admin/publish/pub/vocabularies/$data->channel_id");
228 $edit_items[] = l(t('filters'), "admin/publish/filters/$data->channel_id");
229 $count = db_result(db_query("SELECT COUNT(*) FROM {publish_subscribers} WHERE channel_id = %d", $data->channel_id));
230 $edit_items[] = l(t('delete'), "admin/publish/pub/delete/$data->channel_id");
231 $row[] = array(
232 array('data' => $data->channel_id, 'valign' => 'top'),
233 array('data' => "$data->name", 'valign' => 'top'),
234 array('data' => $data->advertise ? t('yes') : t('no'), 'valign' => 'top'),
235 array('data' => $count ? l($count, "admin/publish/subscribers/$data->channel_id") : t('none'), 'valign' => 'top'),
236 array('data' => implode("<br />\n", $edit_items)));
237 }
238
239 $output = $row ? theme('table', $header, $row) : t('No channels created. Would you like to <a href="%add-channel">create one</a>?', array('%add-channel' => url('admin/publish/add')));
240
241 return $output;
242 }
243
244 /**
245 * Load a channel from the database
246 *
247 * @param $channel_id
248 * The ID of the channel in question
249 *
250 * @return
251 * An object representing a channel
252 */
253 function publish_channel_load($channel_id) {
254 $channel = db_fetch_object(db_query('SELECT * FROM {publish_channel} WHERE channel_id = %d', $channel_id));
255 if ($channel) {
256 $result = db_query('SELECT * FROM {publish_cond} WHERE channel_id = %d AND sid = 0', $channel_id);
257 $channel->filters = array();
258 while ($data = db_fetch_object($result)) {
259 $channel->filters[] = array('fid' => $data->fid, 'field' => $data->field, 'operator' => $data->cond, 'value' => $data->value);
260 }
261 }
262
263 return $channel;
264 }
265
266 function publish_channel_edit() {
267 $channel_id = arg(4);
268 $channel = publish_channel_load($channel_id);
269 if (!$channel) {
270 drupal_set_message(t('No such channel.', 'error'));
271 drupal_goto('admin/publish');
272 }
273 return publish_channel_form($channel);
274 }
275
276 /**
277 * Build form for editing most channel metadata
278 *
279 * @param $channel
280 * Object representing a channel (see publish_channel_load())
281 * @param $edit
282 * Array with form values
283 *
284 * @return
285 * HTML output
286 */
287 function publish_channel_form($channel = NULL, $edit = array()) {
288 if (!isset($channel->name)) {
289 $output = '<h3>' . t('Adding channel') . '</h3>';
290 }
291 else {
292 $output = '<h3>' . t('Configuring channel') . ' "' . $channel->name . '"' . '</h3>';
293 }
294
295 $form['name'] = array(
296 '#type' => 'textfield',
297 '#title' => t('Name'),
298 '#default_value' => isset($channel->name) ? $channel->name : '',
299 '#size' => '60',
300 '#maxlength' => '255'
301 );
302 $form['description'] = array(
303 '#type' => 'textfield',
304 '#title' => t('Description'),
305 '#default_value' => isset($channel->description) ? $channel->description : '',
306 '#size' => '60',
307 '#maxlength' => '255',
308 '#description' => t('A brief description of this channel.')
309 );
310 $form['advertise'] = array(
311 '#type' => 'checkbox',
312 '#title' => t('Advertise on channel list'),
313 '#default_value' => isset($channel->advertise) ? $channel->advertise : '0',
314 '#description' => t('Show name and description of this channel when another site asks which channels are available.')
315 );
316
317 $published = array();
318 $result = db_query("SELECT * FROM {publish_nodetypes} WHERE channel_id = '%s'", $channel->channel_id);
319 while ($data = db_fetch_object($result)) {
320 $published[] = $data->type;
321 }
322
323 $form['node_types'] = array(
324 '#type' => 'fieldset',
325 '#title' => t('Node types')
326 );
327 $form['node_types']['types'] = array(
328 '#type' => 'checkboxes',
329 '#title' => t('Publish the following node types'),
330 '#default_value' => $published,
331 '#options' => node_get_types(),
332 );
333 $form['authentication'] = array(
334 '#type' => 'fieldset',
335 '#title' => t('Who may subscribe to this channel?')
336 );
337 $form['authentication']['authtype'] = array(
338 '#type' => 'radios',
339 '#title' => t('Choose an authorization model'),
340 '#default_value' => isset($channel->authtype) ? $channel->authtype : 'none',
341 '#options' => array(t('None'), t('Users with %perm permission', array('%perm' => theme('placeholder', 'subscribe to channel'))), t('Username and password (enter below)'))
342 );
343 $form['authentication']['username'] = array(
344 '#type' => 'textfield',
345 '#title' => t('Username'),
346 '#size' => '10',
347 '#maxlength' => '32',
348 '#default_value' => isset($channel->username) ? $channel->username : ''
349 );
350 $form['authentication']['pass'] = array(
351 '#type' => 'password',
352 '#title' => t('Password'),
353 '#size' => '10',
354 '#maxlength' => '32',
355 '#default_value' => isset($channel->pass) ? $channel->pass : ''
356 );
357 $form['domains'] = array(
358 '#type' => 'fieldset',
359 '#title' => t('Domains that may subscribe')
360 );
361 $form['domains']['allowed'] = array(
362 '#type' => 'textarea',
363 '#title' => t('Domains'),
364 '#rows' => '2',
365 '#description' => t('Subscribers will be restricted to domains listed here, e.g. %example. If no domains are listed, all may subscribe.', array('%example' => theme('placeholder', 'example.org, example2.org')))
366 );
367 $form['channel_id'] = array(
368 '#type' => 'value',
369 '#value' => isset($channel->channel_id) ? $channel->channel_id : ''
370 );
371 $form['when'] = array(
372 '#type' => 'fieldset',
373 '#title' => t('How often do you want content published?')
374 );
375 $form['when']['whenpub'] = array(
376 '#type' => 'radios',
377 '#title' => t('When should content be published?'),
378 '#default_value' => isset($channel->whenpub) ? $channel->whenpub : 'cron',
379 '#options' => array(t('At regular intervals'), t('As soon as it is submitted/updated'))
380 );
381 $form['submit'] = array(
382 '#type' => 'submit',
383 '#value' => t('Update Channel Settings')
384 );
385 return drupal_get_form('publish_channel_form', $form);
386 }
387
388 /**
389 * Validate input channel_detail_form
390 */
391 function publish_channel_form_validate($form_id, $form_values, $form) {
392 $errors = array();
393
394 form_set_value($form['name'], check_plain($form_values['name']));
395 if ($form_values['name'] == '') {
396 $errors['name'] = t('You must specify a nonblank descriptor for this channel.');
397 }
398 if ($form_values['allowed'] != '') {
399 $domain_pattern = "/([[:alpha:]][-[:alnum:]]*[[:alnum:]])(\.[[:alpha:]][-[:alnum:]]*[[:alpha:]])+/i";
400 $domains = explode(',', $form_values['allowed']);
401 $err_domains = '';
402 $clean_domains = '';
403 foreach ($domains as $domain) {
404 $domain = trim($domain);
405 if (!preg_match($domain_pattern, $domain)) {
406 $err_domains = $err_domains . ', ' . $domain;
407 }
408 else {
409 $clean_domains = $clean_domains . ',' . $domain;
410 }
411 }
412 if ($err_domains != '') {
413 $errors['allowed'] = t('The following domains are invalid:') . ltrim($err_domains, ',');
414 }
415 else {
416 form_set_value($form['domains']['allowed'], ltrim($clean_domains, ','));
417 }
418 }
419 if (!array_filter($form_values['types'])) {
420 $errors['types'] = t('You must choose at least one node type to publish.');
421 }
422 foreach ($errors as $name => $message) {
423 form_set_error($name, $message);
424 }
425 }
426
427 function publish_channel_form_submit($form_id, $form_values) {
428 // get an array of checked node types
429 $form_values['types'] = array_filter($form_values['types']);
430 if ($form_values['channel_id']) {
431 publish_channel_save($form_values);
432 }
433 else { // new channel
434 $form_values['channel_id'] = publish_channel_save($form_values);
435 }
436 publish_node_save($form_values);
437 return 'admin/publish';
438 }
439
440 /**
441 * Menu callback. Build form for editing channel filters
442 *
443 * @param $edit
444 * Array with form values
445 *
446 * @return
447 * HTML output
448 */
449 function publish_channel_filter_form($edit = array()) {
450 $channel_id = arg(3);
451 $channel = publish_channel_load($channel_id);
452 if (!$channel) {
453 drupal_set_message(t('No such channel.'), 'error');
454 drupal_goto('admin/publish');
455 }
456 $form['field'] = array(
457 '#type' => 'select',
458 '#title' => t('Field'),
459 '#options' => publish_channel_filter_options()
460 );
461 foreach (publish_supported_operators() as $operator) {
462 $operators[$operator] = $operator;
463 }
464 $form['operation'] = array(
465 '#type' => 'select',
466 '#title' => t('Condition'),
467 '#options' => $operators
468 );
469 $form['value'] = array(
470 '#type' => 'textfield',
471 '#title' => t('Value'),
472 '#size' => '25',
473 '#maxlength' => '254'
474 );
475 $form['submit'] = array(
476 '#type' => 'submit',
477 '#value' => t('Add filter')
478 );
479 $form['channel_id'] = array(
480 '#type' => 'value',
481 '#value' => $channel_id);
482 return drupal_get_form('publish_channel_filter_form', $form);
483 }
484
485 function theme_publish_channel_filter_form($form) {
486 $channel = publish_channel_load($form['channel_id']['#value']);
487 if (count($channel->filters)) {
488 $output = t('The following filters are in effect for this channel:');
489 $header = array(t('Field'), t('Condition'), t('Value'), t('Operation'));
490 foreach ($channel->filters as $filter) {
491 $operation = l(t('delete'), 'admin/publish/pub/filters/delete/' . $channel->channel_id . '/' .$filter['fid']);
492 $rows[] = array(
493 array('data' => $filter['field']),
494 array('data' => $filter['condition']),
495 array('data' => $filter['value']),
496 array('data' => $operation)
497 );
498 }
499 $output = theme('table', $header, $rows);
500 }
501 else {
502 $output = t('No filters are currently defined.');
503 }
504
505 // we don't want theming so we do a table the old-fashioned way
506 $output .= '<table><tr>';
507 $output .= '<td>' . form_render($form['field']) . '<td>';
508 $output .= '<td>' . form_render($form['operation']) . '<td>';
509 $output .= '<td>' . form_render($form['value']) . '<td>';
510 $output .= '<td>' . form_render($form['submit']) . '</td>';
511 $output .= '</tr></table>';
512 $output .= form_render($form);
513
514 return $output;
515 }
516
517 function publish_channel_filter_options() {
518 $v_names = array();
519 if (module_exist('taxonomy')) {
520 $vocabularies = taxonomy_get_vocabularies();
521 foreach ($vocabularies as $v) {
522 $v_names[$v->vid] = $v->name;
523 }
524 }
525 $merged = array_merge($v_names, publish_supported_fields());
526 foreach ($merged as $name) {
527 $options[$name] = $name;
528 }
529 return $options;
530 }
531
532 function publish_channel_filter_form_validate($form_id, $edit) {
533 $errors = array();
534
535 if ($edit['field'] == '') {
536 $errors['field'] = t('Please choose a field.');
537 }
538 elseif (!array_key_exists($edit['field'], publish_channel_filter_options())) {
539 $errors['field'] = t('Invalid field.');
540 }
541
542 if (!in_array($edit['operation'], publish_supported_operators())) {
543 $errors['operation'] = t('Invalid condition.');
544 }
545
546 foreach ($errors as $name => $message) {
547 form_set_error($name, $message);
548 }
549 }
550
551 function publish_channel_filter_form_submit($form_id, $form_values) {
552 $channel = publish_channel_load($form_values['channel_id']);
553 publish_channel_filter_save($channel, $form_values);
554 drupal_set_message(t('Filter added.'));
555 return 'admin/publish/filters/' . $form_values['channel_id'];
556 }
557
558 function publish_channel_filter_save(&$channel, $edit = array()) {
559 $options = publish_channel_filter_options();
560 $field = $options[$edit['field']];
561 $condition = $edit['operation'];
562 $value = $edit['value'];
563 // get the new field, condition and value from the form
564 // validate them (make sure they're not already in
565 if (!isset($channel->filters)) {
566 $channel->filters = array();
567 }
568 $duplicate = FALSE;
569 foreach ($channel->filters as $filter) {
570 if (($filter['field'] == $field) && ($filter['condition'] == $condition) && ($filter['value'] == $value)) {
571 $duplicate = TRUE;
572 }
573 }
574 if (!$duplicate) {
575 $fid = db_next_id('publish_filter');
576 $channel->filters[] = array('fid' => $fid, 'field' => $field, 'condition' => $condition, 'value' => $value);
577 // the insertion of 0 into the sid (subscription id) field denotes
578 // that this condition is a local channel condition
579 db_query("INSERT INTO {publish_cond} (fid, channel_id, sid, field, cond, value) VALUES (%d, %d, 0, '%s', '%s', '%s')", $fid, $channel->channel_id, $field, $condition, $value);
580 }
581 else {
582 drupal_set_message(t('This filter already exists.'), 'error');
583 }
584 }
585
586 function publish_channel_filter_delete_form() {
587 $channel_id = arg(5);
588 $filter_id = arg(6);
589 $channel = publish_channel_load($channel_id);
590 if (!$channel) {
591 drupal_set_message(t('No such channel.', 'error'));
592 drupal_goto('admin/publish');
593 }
594 $form['channel_id'] = array('#type' => 'value', '#value' => $channel_id);
595 $form['filter_id'] = array('#type' => 'value', '#value' => $filter_id);
596 $output = confirm_form('publish_channel_filter_delete_form', $form,
597 t('Are you sure you want to delete this filter?'),
598 'admin/publish', t('This action cannot be undone.'),
599 t('Delete'), t('Cancel') );
600 return $output;
601 }
602
603 function publish_channel_filter_delete_form_submit($form_id, $form_values) {
604 publish_channel_filter_delete($form_values['channel_id'], $form_values['filter_id']);
605 return 'admin/publish/filters/' . $form_values['channel_id'];
606 }
607 function publish_channel_filter_delete($channel_id, $fid) {
608 $data = db_fetch_object(db_query('SELECT pc.field, pc.cond, pc.value, pp.name FROM {publish_cond} as pc, {publish_channel} as pp WHERE pc.fid = %d AND pp.channel_id = %d', $fid, $channel_id));
609 $filtertext = $data->field . ' ' . $data->cond . ' ' . $data->value;
610 db_query('DELETE FROM {publish_cond} WHERE fid = %d', $fid);
611 watchdog('special', t('Deleted filter %filter from channel %name.', array('%filter' => theme('placeholder', $filtertext), '%name' => $data->name)));
612 drupal_set_message(t('Deleted filter %filter.', array('%filter' => theme('placeholder', $filtertext))));
613 }
614
615 /**
616 * Load list of publishable node types from the database.
617 *
618 * @param $channel_id
619 * The ID of the channel in question.
620 *
621 * @return
622 * An array with node types as keys
623 */
624 function publish_nodetypes_load($channel_id) {
625 $types = array();
626
627 $result = db_query('SELECT type FROM {publish_nodetypes} WHERE channel_id = %d', $channel_id);
628 while ($data = db_fetch_object($result)) {
629 $types[$data->type] = $data->type;
630 }
631
632 return $types;
633 }
634
635 /**
636 * Save a channel to the database.
637 *
638 * @param $edit
639 */
640 function publish_channel_save($edit) {
641 if (!$edit['clean_domains']) $edit['clean_domains'] = '';
642 $channel_id = $edit['channel_id'];
643 $fields = publish_db_fields('publish_channel');
644
645 // update if this channel_id already exists
646 if (db_result(db_query('SELECT COUNT(channel_id) FROM {publish_channel} WHERE channel_id = %d', $channel_id))) {
647 // Prepare the query:
648 foreach ($edit as $key => $value) {
649 if (in_array($key, $fields)) {
650 $q[] = db_escape_string($key) ." = '%s'";
651 $v[] = $value;
652 }
653 }
654 db_query("UPDATE {publish_channel} SET ". implode(', ', $q) ." WHERE channel_id = ". db_escape_string($edit['channel_id']), $v);
655 watchdog('special', t('%type: updated channel %name', array('%type' => '<em>'. t('publish') .'</em>', '%name' => "<em>" . $edit['name'] . "</em>")));
656 drupal_set_message(t('Updated channel %name', array('%name' => theme('placeholder', $edit['name']))));
657 }
658 else {
659 // create new channel
660 if (!$channel_id) {
661 $edit['channel_id'] = db_next_id('publish_channel');
662 }
663
664 // Prepare the query:
665 foreach ($edit as $key => $value) {
666 if (in_array((string) $key, $fields)) {
667 $k[] = db_escape_string($key);
668 $v[] = $value;
669 $s[] = "'%s'";
670 }
671 }
672 // Insert the node into the database:
673 $query = "INSERT INTO {publish_channel} (". implode(", ", $k) .") VALUES(". implode(", ", $s) .")";
674 db_query($query, $v);
675 watchdog('special', t('%type: created channel %name.', array('%type' => '<em>'. t('publish') .'</em>', '%name' => "<em>" . $edit['name'] . "</em>")));
676 drupal_set_message(t('Created channel %name', array('%name' => theme('placeholder', $edit['name']))));
677 }
678 return $edit['channel_id'];
679 }
680
681 /**
682 * Menu callback.
683 * The HTML form to confirm deletion of a channel.
684 */
685 function publish_channel_delete_form() {
686 $channel_id = arg(4);
687 $channel = publish_channel_load($channel_id);
688 if (!$channel) {
689 drupal_set_message(t('No such channel.', 'error'));
690 drupal_goto('admin/publish');
691 }
692 $form['channel_id'] = array('#type' => 'value', '#value' => $channel_id);
693 $output = confirm_form('publish_delete_form', $form,
694 t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $channel->name))),
695 'admin/publish', t('This action cannot be undone.'),
696 t('Delete'), t('Cancel') );
697 return $output;
698 }
699
700 function publish_delete_form_submit($form_id, $form_values) {
701 $channel = publish_channel_load($form_values['channel_id']);
702 publish_channel_delete($channel);
703 return 'admin/publish';
704 }
705
706 /**
707 * Delete a channel.
708 *
709 * @param $channel
710 * a channel object
711 */
712 function publish_channel_delete($channel) {
713 db_query('DELETE FROM {publish_channel} WHERE channel_id = %d', $channel->channel_id);
714 db_query("DELETE FROM {publish_nodetypes} WHERE channel_id = '%s'", $channel->channel_id);
715 db_query('DELETE FROM {publish_cond} WHERE channel_id = %d', $channel->channel_id);
716
717 watchdog('publish', t('deleted channel %name.', array('%name' => $channel->name)));
718 }
719
720
721 /**
722 * Form to set the publish settings for node types.
723 */
724 function publish_node_form($channel_id, $edit = array()) {
725
726 $nodetypes = array();
727 foreach (node_list() as $type) {
728 $nodetypes[$type] = node_invoke($type, 'node_name');
729 }
730
731 $published = array();
732 $result = db_query("SELECT * FROM {publish_nodetypes} WHERE channel_id = '%s'", $channel_id);
733 while ($data = db_fetch_object($result)) {
734 $published[] = $data->type;
735 }
736
737
738 $node_checkboxes = '';
739 foreach ($nodetypes as $internal_type => $type) {
740 $node_checkboxes .= form_checkbox($type, 'pub_' . $internal_type, $internal_type, in_array($internal_type, $published));
741 }
742
743 $output = form_group(t('Publish the following node types'), $node_checkboxes);
744 $output .= form_hidden('channel_id', $channel_id);
745
746 return $output;
747 }
748
749 /**
750 * Save a nodetype config data to the database.
751 */
752 function publish_node_save($edit) {
753
754 $channel_id = $edit['channel_id'];
755 $submitted_nodetypes = $edit['types'];
756
757 /*
758
759 $nodetypes = array();
760 foreach (node_list() as $type) {
761 $nodetypes[$type] = node_invoke($type, 'node_name');
762 if ($edit['pub_' . $type]) {
763 $submitted_nodetypes[$type] = $type;
764 }
765 }
766
767 $result = db_query("SELECT * FROM {publish_nodetypes} WHERE channel_id = '%s'", $channel_id);
768 while ($data = db_fetch_object($result)) {
769 if (key_exists($data->type, $submitted_nodetypes)) {
770 // node type exists in database and has been checked; no change so delete from array
771 unset($submitted_nodetypes[$data->type]);
772 }
773 else {
774 // node type exists in database but not checked; delete from database
775 db_query("DELETE FROM {publish_nodetypes} WHERE type = '%s'", $data->type);
776 }
777 }
778 */
779 db_query("DELETE FROM {publish_nodetypes} WHERE channel_id = %d", $channel_id);
780
781 // now we are left with the node types to add
782 foreach ($submitted_nodetypes as $type) {
783 db_query("INSERT INTO {publish_nodetypes} (channel_id, type) VALUES ('%s', '%s')", $channel_id, $type);
784 }
785 }
786
787
788 /**
789 * Form to set the publish settings for vocabularies.
790 */
791 function publish_vocabulary_form($channel_id) {
792 $channel = publish_channel_load($channel_id);
793 if (!$channel) {
794 drupal_set_message(t('No such channel.', 'error'));
795 drupal_goto('admin/publish');
796 }
797
798 if (!module_exist('taxonomy')) {
799 drupal_set_message(t('The taxonomy module is disabled. Please enable the taxonomy module.'), 'warning');
800 drupal_goto('/admin/publish');
801 }
802
803 $vocabularies = taxonomy_get_vocabularies();
804
805 //this is where we would do vocabulary autoimport
806 if (!$vocabularies) return $output . t('No vocabularies have been defined for this Drupal site. You will need to define vocabularies using administer -&gt; categories before you can publish them.');
807
808 $result = publish_node_vocabularies($channel_id);
809 $nodetypes = $result[0];
810 $published_vocabularies = $result[1];
811 // for each node type chosen for publication
812 if ($nodetypes) {
813 // probably want to clean this up by using theme('table')
814 // header row
815 $table = '<table>';
816 $table .= '<th>' . t('Node Type') . '</th>';
817 foreach ($vocabularies as $v) {
818 $table .= '<th>' . $v->name . '</th>';
819 }
820 $table .= '</tr>';
821
822 // data rows
823 foreach ($nodetypes as $type => $friendly_type) {
824 $table .= "<tr><td>$friendly_type</td>";
825
826 // which vocabularies apply to this node type?
827 $tax_voc_this_node = taxonomy_get_vocabularies($type);
828 $tax_voc_ids = array();
829 foreach ($tax_voc_this_node as $v) {
830 $tax_voc_ids[$v->vid] = TRUE;
831 }
832 foreach ($vocabularies as $v) {
833 $s = '';
834 // if this vocabulary is applicable
835 if (key_exists($v->vid, $tax_voc_ids)) {
836 // add a checkbox, appropriately checked
837 $form[$type . '|' . $v->name] = array(
838 '#type' => 'checkbox',
839 '#title' => '',
840 '#value' => (int) key_exists((int) $v->vid, $published_vocabularies[$type]),
841 '#parents' => array(),
842 '#name' => 'edit[' . $type . '|' . $v->name . ']',
843 '#return_value' => 1
844 );
845 $s = form_render($form[$type . '|' . $v->name]);
846 }
847 $table .= '<td>' . $s . '</td>';
848 }
849 $table .= '</tr>';
850 }
851
852 $form['markup'] = array(
853 '#type' => 'markup',
854 '#value' => theme('fieldset', array('#title' => t('Vocabularies to Publish'), '#children' => $table . '</table>')));
855 $form['channel_id'] = array(
856 '#type' => 'value',
857 '#value' => $channel_id
858 );
859 $form['submit'] = array(
860 '#type' => 'submit',
861 '#value' => t('Save vocabulary settings')
862 );
863 }
864 else {
865 // no node types have been chosen for publication
866 drupal_set_message(t('No node types have been chosen for publication. Edit channel and choose at least one node type.'));
867 drupal_goto('/admin/publish');
868 }
869
870 return drupal_get_form('publish_vocabulary_form', $form);
871 }
872
873 /**
874 * Save a nodetype's configuration data to the database.
875 *
876 * @param $edit
877 * array from form
878 */
879 function publish_vocabulary_form_submit($form_id, $edit) {
880 $channel_id = $edit['channel_id'];
881
882 // only step through nodes that user has chosen to publish
883 $nodetypes = array();
884 $result = db_query("SELECT * FROM {publish_nodetypes} WHERE channel_id = '%s'", $channel_id);
885 while ($data = db_fetch_object($result)) {
886 $nodetypes[] = $data->type;
887 }
888
889 foreach ($nodetypes as $type) {
890 $vocs_this_node = array();
891 // get vocabularies specific to this node type
892 $vocabularies = taxonomy_get_vocabularies($type);
893 foreach ($vocabularies as $v) {
894 $key = $type . '|' . $v->name;
895 if ($_POST['edit'][$key]) { // hack; TODO: rewrite publish_vocabulary_form
896 $vocs_this_node[$v->vid] = TRUE;
897 }
898 }
899 db_query("DELETE FROM {publish_nodetypes} WHERE channel_id = '%s' AND type = '%s'", $channel_id, $type);
900 db_query("INSERT INTO {publish_nodetypes} (channel_id, type, pub_vocab) VALUES ('%s', '%s', '%s')", $channel_id, $type, serialize($vocs_this_node));
901 }
902 $channel = publish_channel_load($channel_id);
903 drupal_set_message(t("Saved vocabulary publication configuration for channel %name.", array('%name' => theme('placeholder', $channel->name))));
904
905 return 'admin/publish';
906 }
907
908 /**
909 * Retrieve configuration settings for a given nodetype.
910 *
911 * @param $type
912 * the internal type of a node, e.g. 'story'
913 */
914 function publish_vocabulary_load($type) {
915 static $config = array();
916
917 if (!$config[$type]) {
918 $data = db_fetch_object(db_query("SELECT * FROM {publish_nodetypes} WHERE type = '%s'", $type));
919 if ($data->pub_vocab) {
920 $data->pub_vocab = unserialize($data->pub_vocab);
921 }
922 $config[$type] = $data;
923 }
924
925 return $config[$type];
926 }
927
928 /**
929 * Retrieve published node types and published vocabularies for those node types
930 *
931 * @param $channel_id
932 * the ID of the channel
933 *
934 * @return
935 * array containing
936 * - an associative array of published node type names keyed by internal node type name
937 * - an associative array of published vocabularies keyed by internal node type name
938 */
939 function publish_node_vocabularies($channel_id) {
940 static $nodetypes = array();
941 static $published_vocabularies = array();
942
943 $lookup = node_get_types();
944 $result = db_query("SELECT * FROM {publish_nodetypes} WHERE channel_id = '%s'", $channel_id);
945 while ($data = db_fetch_object($result)) {
946 $nodetypes[$data->type] = $lookup[$data->type];
947 if ($data->pub_vocab) {
948 $published_vocabularies[$data->type] = unserialize($data->pub_vocab);
949 }
950 else {
951 $published_vocabularies[$data->type] = array();
952 }
953 }
954
955 return array($nodetypes, $published_vocabularies);
956 }
957
958 /**
959 * Get the list of subscribers for a channel.
960 *
961 * @param $channel_id
962 * the ID of the channel
963 *
964 * @return
965 * an HTML table of subscribers and related information
966 */
967 function publish_subscribers () {
968 $channel_id = arg(3);
969 $channel = publish_channel_load($channel_id);
970 $output = '<h3>' . t('Subscribers to channel') . " '$channel->name':</h3>";
971 $header = array(array('data' => t('Subscriber')), array('data' => t('Status')), array('data' => 'Operations', 'colspan' => '1'));
972 $row = array();
973
974 $result = db_query("SELECT url, sub_status FROM {publish_subscribers} WHERE channel_id = '%s'", $channel_id);
975 while ($data = db_fetch_object($result)) {
976 // build table
977 $row[] = array(array('data' => $data->url), array('data' => $data->sub_status), array('data' => ''));
978 }
979 $output .= ($row) ? theme('table', $header, $row) : '<p>' . t('No one is currently subscribed to this channel.') . '</p>';
980
981 return $output;
982 }
983
984 /**
985 * Compile array of publishable nodes according to given conditions.
986 *
987 * @param $channel_id
988 * The ID of the channel in question.
989 *
990 * @param $cond
991 * Array of triplet arrays containing field, condition, value.
992 * Example: array(array('created', '>', '0'))
993 * Example: array(array('created', '>', '0'), array('nid', '<', '5'))
994 *
995 * @return
996 * An array of node objects.
997 */
998 function publish_publish($channel_id, $cond) {
999 static $channels = array();
1000
1001 if (isset($channels[$channel_id])) {
1002 $channel = $channels[$channel_id];
1003 }
1004 else {
1005 $channel = publish_channel_load($channel_id);
1006 $channels[$channel_id] = $channel;
1007 }
1008 $node_ids = array();
1009 $published_types = publish_nodetypes_load($channel_id);
1010 if (!$published_types) return array();
1011
1012 $where = '';
1013 foreach ($published_types as $type) {
1014 $where .= "OR type = '" . (function_exists('db_escape_string') ? db_escape_string($type) : check_query($type)) . "' ";
1015 }
1016 // strip off leading 'OR '
1017 $where = $where ? '(' . strstr($where, " ") . ')' : '';
1018
1019 list($clean_cond, $taxonomy_cond) = publish_cond_validate(array_merge($channel->filters, $cond));
1020 $filter = array();
1021
1022 foreach ($clean_cond as $triplet) {
1023 $field = $triplet['field'];
1024 $operator = $triplet['operator'];
1025 $value = $triplet['value'];
1026
1027 // check if value is numeric. If not, put single quotes around it and clean it
1028 $quot_start = ' ';
1029 $quot_end = ' ';
1030 $clean_value = null;;
1031
1032 if (!is_numeric($value)) {
1033 $quot_start = " '";
1034 $quot_end = "' ";
1035 $clean_value = function_exists('db_escape_string') ? db_escape_string($value) : check_query($value);
1036 }
1037
1038 $filter[] = $field . ' ' . $operator . $quot_start . ($clean_value ? $clean_value : $value) . $quot_end;
1039 }
1040
1041 if ($filter) {
1042 $where .= $filter[1] ? implode($filter, ' AND') : ' AND ' . $filter[0];
1043 }
1044
1045 $result = db_query("SELECT nid FROM {node} WHERE $where");
1046 while ($data = db_fetch_object($result)) {
1047 $node_ids[] = $data->nid;
1048 }
1049
1050 return _publish_publish($node_ids, $taxonomy_cond);
1051 }
1052
1053 /**
1054 * Return an object representing a subscription to this site.
1055 *
1056 * @param $sid
1057 * the ID of the subscription
1058 */
1059 function publish_subscriptions_load($sid) {
1060 return db_fetch_object(db_query('SELECT * FROM {publish_subscribers} WHERE sid = %d', $sid));
1061 }
1062
1063 /**
1064 * Push content in the publication queue out to subscribers
1065 */
1066 function publish_push() {
1067 global $base_url;
1068 $qtime = variable_get('publish_queue_time', 300); // 5 minutes
1069 // this loops through the queue (oldest nodes first) with a time window set by qtime
1070 // qtime should be long enough that an attempt to send each item
1071 // in the queue can be made within the time window
1072 // we use db_fetch_object() to get one node at a time
1073 while ($data = db_fetch_object(db_query("SELECT * FROM {publish_queue} WHERE last_attempt < %d ORDER BY changed", time() - $qtime))) {
1074 if (PUBLISH_DEBUG_MODE) watchdog('publish', t('beginning push for channel %channel, subscriber %subscriber', array('%channel' => $data->channel_id, '%subscriber' => $data->sid)));
1075
1076 // see if there are any other nodes we can include for this subscriber
1077 // while we have the overhead of sending xmlrpc anyway
1078 $node_ids = array();
1079 $qids = array();
1080 $result = db_query("SELECT qid, nid, attempts FROM {publish_queue} WHERE sid = %d AND channel_id = %d", $data->sid, $data->channel_id);
1081 while ($row = db_fetch_object($result)) {
1082 if (PUBLISH_DEBUG_MODE) watchdog('publish', t('preparing to push qid %qid; nid %nid', array('%qid' => $row->qid, '%nid' => $row->nid)));
1083 $node_ids[] = $row->nid;
1084 $qids[$row->nid] = array('qid' => $row->qid, 'attempts' => $row->attempts);
1085 }
1086 $nodes = array();
1087 foreach ($node_ids as $nid) {
1088 // run the nodes through the channel's condition filters
1089 // note that if push is happening from nodeapi insert/update (not cron),
1090