/[drupal]/contributions/modules/subscriptions/subscriptions_content.module
ViewVC logotype

Contents of /contributions/modules/subscriptions/subscriptions_content.module

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


Revision 1.42 - (show annotations) (download) (as text)
Tue Nov 3 22:17:54 2009 UTC (3 weeks, 2 days ago) by salvis
Branch: MAIN
Changes since 1.41: +16 -3 lines
File MIME type: text/x-php
#611850: Really keep users from getting notifications from blocked content types, reported by Break9.
1 <?php
2 // $Id: subscriptions_content.module,v 1.41 2009/09/28 13:26:15 salvis Exp $
3
4 /**
5 * @file
6 * Subscriptions to content events
7 *
8 * Subscriptions_content extends the Subscriptions module to allow users to
9 * subscribe by content type. If a user subscribes to a content he will receive
10 * notifications each time a node is published.
11 * Subscriptions_content also provides the ability to subscribe to comments and
12 * updates of nodes by content type or by other kinds of subscriptions (defined
13 * by other Subscriptions submodules).
14 */
15
16 /**
17 * Implementation of hook_subscriptions().
18 *
19 * @ingroup hooks
20 */
21 function subscriptions_content_subscriptions($op, $arg0 = NULL, $arg1 = NULL, $arg2 = NULL) {
22 static $stypes = array(
23 'node' => array('node', 'nid'),
24 'type' => array('node', 'type'),
25 );
26 $function = '_subscriptions_content_'. $op;
27 if (function_exists($function)) {
28 return $function($arg0, $arg1, $arg2);
29 }
30 switch ($op) {
31 case 'queue':
32 // $arg0 is $event array.
33 if ($arg0['module'] == 'node') {
34 $node = $arg0['node'];
35 $params['node']['nid']['value'] = $node->nid;
36 $params['node']['type']['value'] = $node->type;
37 if ($arg0['type'] == 'comment') {
38 $where = 's.send_comments = 1';
39 }
40 elseif ($arg0['type'] == 'node' && $arg0['action'] == 'update') {
41 $where = 's.send_updates = 1';
42 }
43 if (isset($where)) {
44 $params['node']['nid']['where'] = $where;
45 $params['node']['type']['where'] = $where;
46 }
47 return $params;
48 }
49 break;
50 case 'fields': // Parameter is module
51 if ($arg0 == 'node' || $arg0 == 'comment') {
52 return array(
53 'nid' => array(
54 'mailvars_function' => '_subscriptions_content_node_mailvars',
55 'mailkey' => 'subscriptions_content_node-nid',
56 '!subs_type' => t('thread'),
57 ),
58 'type' => array(
59 'mailvars_function' => '_subscriptions_content_node_mailvars',
60 'mailkey' => 'subscriptions_content_node-type',
61 '!subs_type' => t('content type'),
62 ),
63 );
64 }
65 break;
66 case 'stypes':
67 return $stypes;
68 case 'stype':
69 return (isset($stypes[$arg0]) ? array_merge( $stypes[$arg0], array($arg1, $arg2)) : NULL);
70 }
71 }
72
73 /**
74 * Implementation of hook_node_options(), subhook of hook_subscriptions().
75 *
76 * This is called by subscriptions_ui_node_form() in subscriptions_ui.module.
77 *
78 * @ingroup form
79 * @ingroup hooks
80 *
81 * @see subscriptions_ui_node_form()
82 */
83 function _subscriptions_content_node_options($account, $node) {
84 // Default node, field are the first three indexes, but they can be overridden in params
85 // Thread
86 $options = array();
87 $statics = variable_get('subscriptions_static_content_types', array());
88 if (!in_array($node->type, $statics)) {
89 $options['nid'][] = array(
90 'name' => t('Subscribe to this page'),
91 'params' => array('module' => 'node', 'field' => 'nid', 'value' => $node->nid),
92 'link' => 'node/'. $node->nid,
93 );
94 $options['nid']['weight'] = -4;
95 }
96 $unlisteds = variable_get('subscriptions_unlisted_content_types', array());
97 if (user_access('subscribe to content types', $account)) {
98 $unlisted_tag = '';
99 if (in_array($node->type, $unlisteds)) {
100 if (user_access('subscribe to all content types', $account)) {
101 $unlisted_tag = '&nbsp;'. SUBSCRIPTIONS_UNAVAILABLE;
102 }
103 else {
104 return $options;
105 }
106 }
107 // Content type
108 $type_name = node_get_types('name', $node->type);
109 if ($type_name) {
110 $options['type'][] = array(
111 'name' => t('To %type content', array('%type' => $type_name)) . $unlisted_tag,
112 'params' => array('module' => 'node', 'field' => 'type', 'value' => $node->type),
113 'link' => 'type/'. $node->type,
114 );
115 // Content type and author
116 $options['type'][] = array(
117 'name' => t('To %type content by %name', array('%type' => $type_name, '%name' => ($node->uid ? $node->name : variable_get('anonymous', '???')))) . $unlisted_tag,
118 'params' => array('module' => 'node', 'field' => 'type', 'value' => $node->type, 'author_uid' => $node->uid),
119 'link' => 'type/'. $node->type .'/'. $node->uid,
120 );
121 $options['type']['weight'] = -2;
122 }
123 }
124 return $options;
125 }
126
127 /**
128 * Implementation of hook_access(), subhook of hook_subscriptions().
129 *
130 * @ingroup hooks
131 */
132 function _subscriptions_content_access($load_function, $load_args, $node) {
133 ///global $user; /// keep this for remote debugging
134 if (($load_function == 'subscriptions_content_node_load' || ($load_function == 'subscriptions_content_comment_load' && $node->_subscriptions_comments)) &&
135 ($node->status || user_access('administer nodes')) && node_access('view', $node)) {
136 if (!empty($node->type) && subscriptions_content_type_is_blocked($node->type) && !user_access('subscribe to all content types')) {
137 return FALSE;
138 }
139 // We vote 'yes'. Other modules might vote 'no' and then that wins.
140 ///watchdog('subs debug', "_sca returns TRUE for node $node->nid, user $user->uid."); ///
141 return TRUE;
142 }
143 ///watchdog('subs debug', "_sca: node_access('view', $node->nid) returns ". node_access('view', $node) ." for user $user->uid."); ///
144 }
145
146 /**
147 * Implementation of hook_types(), subhook of hook_subscriptions().
148 *
149 * This is called by subscriptions_types() in subscriptions.module.
150 *
151 * @return
152 * Returns information about types for Subscriptions module interface.
153 *
154 * @ingroup form
155 * @ingroup hooks
156 *
157 * @see subscriptions_types()
158 */
159 function _subscriptions_content_types() {
160 $tr = 't';
161 $types['node'] = array(
162 'title' => 'Pages/Threads',
163 'access' => 'subscribe to content',
164 'page' => 'subscriptions_content_page_node',
165 'fields' => array('node', 'nid'),
166 'weight' => -4,
167 );
168 $types['type'] = array(
169 'title' => 'Content types',
170 'access' => 'subscribe to content types',
171 'page' => 'subscriptions_content_page_type',
172 'fields' => array('node', 'type'),
173 'weight' => -2,
174 );
175 return $types;
176
177 // help potx to pick up these strings:
178 t('subscribe to content');
179 t('subscribe to content types');
180 }
181
182 /**
183 * Implementation of hook_nodeapi().
184 *
185 * Catch node inserts and updates and send them to the subscriptions queue;
186 * catch node deletes and remove any associated thread subscriptions.
187 */
188 function subscriptions_content_nodeapi(&$node, $op, $arg = 0) {
189 global $user;
190 static $unpublished_nid;
191 switch ($op) {
192 case 'load':
193 case 'prepare':
194 $unpublished_nid = ($node->status ? NULL : $node->nid);
195 $node->subscriptions_notify = TRUE;
196 break;
197
198 case 'update':
199 if ($unpublished_nid == $node->nid && $node->status) {
200 // An unpublished node just became published -- treat this as an 'insert'!
201 $op = 'insert';
202 }
203 // fall through
204
205 case 'insert':
206 $event = array(
207 'module' => 'node',
208 'uid' => $node->uid,
209 'load_function' => 'subscriptions_content_node_load',
210 'load_args' => $node->nid,
211 'type' => 'node',
212 'action' => $op,
213 'is_new' => ($op == 'insert'),
214 'node' => $node,
215 );
216 $unpublished_nid = NULL;
217
218 // Check direct subscription and auto subscription
219 //if (isset($node->subscriptions_subscribe)) {
220 // if ($node->subscriptions_subscribe) {
221 // subscriptions_write_subscription('node', 'nid', $node->nid, -1, $user->uid);
222 // }
223 //} else
224 if ($node->uid > 0 && !$arg) {
225 _subscriptions_content_autosubscribe($node->type, 'node', 'nid', $node->nid, ($op == 'insert' ? 'on_post' : 'on_update'));
226 }
227
228 if (!isset($node->subscriptions_notify) || $node->subscriptions_notify) {
229 subscriptions_queue($event);
230 }
231 break;
232
233 case 'delete':
234 db_query("DELETE FROM {subscriptions} WHERE module = 'node' AND field = 'nid' AND value = '%s'", $node->nid);
235 break;
236 }
237 }
238
239 /**
240 * Implementation of hook_comment().
241 *
242 * Catch comment inserts and updates and send them to the subscriptions queue.
243 */
244 function subscriptions_content_comment($comment, $op) {
245 global $user;
246 static $is_unpublished;
247 static $inserted_cid = 0;
248 $comment = (array)$comment; // $comment can be an object or an array.
249
250 if ($op == 'form') {
251 $is_unpublished = (isset($comment['admin']) ? $comment['admin']['status']['#default_value'] : !user_access('post comments without approval'));
252 if (user_access('administer comments')) {
253 $tr = 't';
254 $form['subscriptions_notify'] = array(
255 '#type' => 'checkbox',
256 '#title' => t('Send subscriptions notifications'),
257 '#default_value' => TRUE,
258 ); // hook_form_alter() will move the checkbox inside the Administration fieldset!
259
260 if (isset($comment['admin']) && $is_unpublished) { // for update
261 $form['subscriptions_notify']['#description'] = t('Subscriptions notifications are not sent for unpublished comments (except to users who have the %administer_comments permission), but when you change !Status to %Published, Subscriptions will send out "new" notifications, unless you suppress this here.',
262 array('%administer_comments' => $tr('administer comments'),
263 '!Status' => $tr('Status'),
264 '%Published' => $tr('Published')));
265 }
266 elseif ($is_unpublished) {
267 $form['subscriptions_notify']['#description'] = t('Subscriptions notifications are not sent for unpublished comments, except to users who have the %administer_comments permission, and you can even suppress the latter here.',
268 array('%administer_comments' => $tr('administer comments')));
269 }
270 else {
271 $form['subscriptions_notify']['#description'] = t('You can suppress sending subscriptions notifications here; this option is not saved.');
272 }
273 }
274 return (isset($form) ? $form : NULL);
275 }
276
277 if (!isset($comment['nomail']) && ($op == 'insert' || $op == 'update' || ($op == 'publish' && $comment['cid'] != $inserted_cid))) {
278 if (!isset($comment['subscriptions_notify']) || $comment['subscriptions_notify']) {
279 $node = node_load($comment['nid']);
280 $event = array(
281 'module' => 'node',
282 'load_function' => 'subscriptions_content_comment_load',
283 'load_args' => $comment['cid'],
284 'uid' => $comment['uid'],
285 'type' => 'comment',
286 'action' => $op,
287 'is_new' => ($op != 'update' || $is_unpublished && $comment['status'] == COMMENT_PUBLISHED),
288 'node' => $node,
289 'comment' => (object)$comment,
290 );
291 subscriptions_queue($event);
292 $inserted_cid = $comment['cid'];
293 }
294 _subscriptions_content_autosubscribe($node->type, 'node', 'nid', $comment['nid'], 'on_comment');
295 }
296 }
297
298 /**
299 * Implementation of hook_form_alter().
300 *
301 * Add the Content Settings part to admin/settings/subscriptions,
302 *
303 * @ingroup hooks
304 * @ingroup form
305 */
306 function subscriptions_content_form_subscriptions_settings_form_alter(&$form, &$form_state) {
307 $tr = 't';
308
309 // General content settings
310 $select = array();
311 $select[0] = '<'. t('none') .'>';
312 $nodetypes = node_get_types();
313 foreach ($nodetypes as $ntype => $nname) {
314 $select[$ntype] = $nname->name;
315 }
316 $form['content'] = array(
317 '#type' => 'fieldset',
318 '#title' => t('Content settings'),
319 '#collapsible' => TRUE,
320 '#weight' => -10,
321 );
322 $form['content']['subscriptions_unlisted_content_types'] = array(
323 '#type' => 'select',
324 '#title' => t('Unlisted content types'),
325 '#default_value' => variable_get('subscriptions_unlisted_content_types', array()),
326 '#options' => $select,
327 '#description' => t('Select content types which should be <strong>removed from subscription listings</strong>.<br />The content may still be available for subscribing via different kinds of subscriptions, but subscribing by content type will be unavailable for the selected types.'),
328 '#multiple' => TRUE,
329 );
330 $form['content']['subscriptions_blocked_content_types'] = array(
331 '#type' => 'select',
332 '#title' => t('Blocked content types'),
333 '#default_value' => variable_get('subscriptions_blocked_content_types', array()),
334 '#options' => $select,
335 '#description' => t('Select content types which should be <strong>completely unavailable for subscribing</strong>, i.e. content of the selected types will never trigger notifications for regular users.'),
336 '#multiple' => TRUE,
337 );
338 $form['content']['subscriptions_blocked_content_types_note'] = array(
339 '#type' => 'item',
340 '#title' => t('Note'),
341 '#value' => t("The %permission permission grants normal access to unlisted and blocked content types; this is intended as an administrative function, and the content types and links will be marked with a '!symbol' symbol (and appear !red_ON like this !red_OFF in the case of blocked types).", array('%permission' => t('subscribe to all content types'), '!symbol' => SUBSCRIPTIONS_UNAVAILABLE, '!red_ON' => '<span class="error">', '!red_OFF' => '</span>')),
342 );
343 $form['content']['subscriptions_blocked_nodes'] = array(
344 '#type' => 'textfield',
345 '#title' => t('Blocked nodes'),
346 '#size' => 100,
347 '#maxlength' => 1000,
348 '#default_value' => variable_get('subscriptions_blocked_nodes', ''),
349 '#description' => t('Enter the IDs of nodes that should be <strong>completely unavailable for subscribing</strong>, separated by spaces.'),
350 );
351 $form['#validate'][] = '_subscriptions_content_validate_blocked_nodes';
352
353 $statics = variable_get('subscriptions_static_content_types', array());
354 $avoid_empty_subscribe_links = variable_get('subscriptions_avoid_empty_subscribe_links', 0);
355 $form['content']['static_content'] = array(
356 '#type' => 'fieldset',
357 '#title' => t('Static content'),
358 '#collapsible' => TRUE,
359 '#collapsed' => (empty($statics) || (count($statics) == 1 && isset($statics[0]))) && !$avoid_empty_subscribe_links,
360 );
361 $form['content']['static_content']['subscriptions_static_content_types'] = array(
362 '#type' => 'select',
363 '#title' => t('Static content types'),
364 '#default_value' => $statics,
365 '#options' => $select,
366 '#description' => t('Select content types which do not change nor receive comments and thus should not have the %option option.', array('%option' => t('Subscribe to this page'))),
367 '#multiple' => TRUE,
368 );
369 $form['content']['static_content']['subscriptions_avoid_empty_subscribe_links'] = array(
370 '#type' => 'checkbox',
371 '#title' => t('Avoid empty %Subscribe links', array('%Subscribe' => t('Subscribe'))),
372 '#default_value' => $avoid_empty_subscribe_links,
373 '#description' => t('Nodes of %Static_content_types may end up with no %Subscribe options at all. Turn this option on to avoid displaying %Subscribe links in this case. The default is OFF, because this option causes processing overhead for each node view operation.', array('%Static_content_types' => t('Static content types'), '%Subscribe' => t('Subscribe'))),
374 );
375
376 $form['content']['subscriptions_generate_full_node'] = array(
377 '#type' => 'checkbox',
378 '#title' => t('Generate the %full_node variable', array('%full_node' => '!full_node')),
379 '#default_value' => variable_get('subscriptions_generate_full_node', 0),
380 '#description' => t("Generating this variable causes considerable overhead even if it's not used, and <strong>it may even cause errors</strong>, depending on the !content_type! Default is OFF.", array('!content_type' => $tr('content type'))),
381 );
382 }
383
384 /**
385 * Validate the 'subscriptions_blocked_nodes' input.
386 */
387 function _subscriptions_content_validate_blocked_nodes($form, $form_state) {
388 $form_values = $form_state['values'];
389 $values = $form_values['subscriptions_blocked_nodes'];
390 if (!empty($values)) {
391 $values = explode(' ', $values);
392 foreach ($values as $v) {
393 if (!empty($v) && !is_numeric($v)) {
394 form_set_error('subscriptions_blocked_nodes', t('Enter a series of numbers, separated by spaces.'));
395 break;
396 }
397 }
398 }
399 }
400
401 /**
402 * Implementation of hook_form_alter().
403 *
404 * Add the Send Subscriptions Notifications checkbox to the Publishing Options
405 * fieldset on the node edit form.
406 *
407 * @ingroup hooks
408 * @ingroup form
409 */
410 function subscriptions_content_form_alter(&$form, &$form_state, $form_id) {
411 global $user;
412
413 if (isset($form['type']['#value']) && $form['type']['#value'] .'_node_form' == $form_id) {
414 if (isset($form['options'])) {
415 $tr = 't';
416 $form['options']['subscriptions_notify'] = array(
417 '#type' => 'checkbox',
418 '#title' => t('Send subscriptions notifications'),
419 '#description' => t('You may want to turn this OFF when you only change %Promoted_to_front_page or %Sticky_at_top_of_lists, otherwise Subscriptions will send out "update" notifications; this option is not saved.<br />Subscriptions does not send notifications for unpublished nodes (except to users who have the %administer_nodes permission), but when you set %Published to ON, Subscriptions will send out "new" notifications, unless you turn this off here.',
420 array('%Promoted_to_front_page' => $tr('Promoted to front page'),
421 '%Sticky_at_top_of_lists' => $tr('Sticky at top of lists'),
422 '%administer_nodes' => $tr('administer nodes'),
423 '%Published' => $tr('Published'))),
424 '#weight' => 5,
425 '#default_value' => (isset($form['#node']->subscriptions_notify) ? $form['#node']->subscriptions_notify : TRUE),
426 );
427 }
428 }
429 }
430
431 /**
432 * Implementation of hook_form_alter().
433 *
434 * Add submit handler to catch bulk content moderation submissions.
435 *
436 * @ingroup hooks
437 * @ingroup form
438 */
439 function subscriptions_content_form_node_admin_nodes_alter(&$form, &$form_state) {
440
441 $handlers = array_reverse($form['#submit']);
442 $handlers[] = 'subscriptions_content_node_admin_nodes_submit';
443 $form['#submit'] = array_reverse($handlers); // run before node_admin_nodes_submit()!
444 }
445
446 /**
447 * Implementation of hook_form_alter().
448 *
449 * Add the Send Subscriptions Notifications checkbox to the Administration Options
450 * fieldset on the comment edit form.
451 *
452 * @ingroup hooks
453 * @ingroup form
454 */
455 function subscriptions_content_form_comment_form_alter(&$form, &$form_state) {
456
457 if (isset($form['subscriptions_notify'])) {
458 // create the Administration fieldset if it doesn't exist:
459 if (!isset($form['admin'])) {
460 $tr = 't';
461 $form['admin'] = array(
462 '#type' => 'fieldset',
463 '#title' => $tr('Administration'),
464 '#collapsible' => TRUE,
465 '#collapsed' => TRUE,
466 '#weight' => -2,
467 );
468 }
469 // move the checkbox inside the Administration fieldset:
470 $form['admin']['subscriptions_notify'] = $form['subscriptions_notify'];
471 unset($form['subscriptions_notify']);
472 }
473 }
474
475 /**
476 * Handle bulk publishing.
477 */
478 function subscriptions_content_node_admin_nodes_submit($form, &$form_state) {
479 if ($form_state['values']['operation'] == 'publish') {
480 foreach ($form_state['values']['nodes'] as $nid) {
481 if ($nid != 0 && ($node = node_load($nid)) && !$node->status) {
482 subscriptions_content_nodeapi($node, 'prepare');
483 subscriptions_content_nodeapi($node, 'update', TRUE); // (avoid autosubscribe)
484 }
485 }
486 }
487 }
488
489 /**
490 * Auto-subscribe, if the content type is not blocked.
491 *
492 * @param $content_type
493 * Content type of the node to subscribe to.
494 * @param $module
495 * Parameter for subscriptions_autosubscribe().
496 * @param $field
497 * Parameter for subscriptions_autosubscribe().
498 * @param $value
499 * Parameter for subscriptions_autosubscribe().
500 * @param $context
501 * Parameter for subscriptions_autosubscribe().
502 */
503 function _subscriptions_content_autosubscribe($content_type, $module, $field, $value, $context)
504 {
505 if (subscriptions_content_type_is_blocked($content_type)) {
506 return;
507 }
508 subscriptions_autosubscribe($module, $field, $value, $context);
509 }
510
511 /**
512 * Return TRUE if the content type is blocked.
513 *
514 * @param $content_type
515 */
516 function subscriptions_content_type_is_blocked($content_type)
517 {
518 $blockeds = variable_get('subscriptions_blocked_content_types', array());
519 return in_array($content_type, $blockeds);
520 }
521
522 /**
523 * Fill given array of mailvars with given node values.
524 *
525 * Callback of function _subscriptions_content_node_mailvars().
526 *
527 * @param $mailvars
528 * Array of mailvars to be full-filled.
529 * @param $node
530 * Node object used to fill $mailvars.
531 * @param $field
532 * Internal use for filling variable !term_name.
533 * @param $s
534 * Subscription object.
535 */
536 function _subscriptions_content_node_mailvars(&$mailvars, $node, $field, $s) {
537 global $language;
538 include_once drupal_get_path('module', 'subscriptions_mail') .'/subscriptions_mail.templates.inc';
539
540 $mailvars['!url'] = url('node/'. $node->nid, array('absolute' => TRUE));
541 $mailvars['!node_type'] = $node->type;
542 $mailvars['!title'] = trim($node->title);
543 $mailvars['!teaser'] = _subscriptions_content_format_text($node->teaser, $node->format);
544 $mailvars['!body'] = _subscriptions_content_format_text($node->body, $node->format);
545 $mailvars['!full_node'] = (variable_get('subscriptions_generate_full_node', 0) ? _subscriptions_content_format_text(node_view($node, FALSE, TRUE, FALSE)) : '!full_node');
546 $mailvars['!is_new'] = (integer) !empty($node->_subscriptions_is_new);
547 $mailvars['!is_updated'] = (integer) !empty($node->_subscriptions_is_updated);
548 $mailvars['!is_old'] = (integer)(empty($node->_subscriptions_is_new) && empty($node->_subscriptions_is_updated));
549 $mailvars['!is_published'] = $node->status;
550 $mailvars['!has_new_comments'] = (integer)!empty($node->_subscriptions_comments);
551 if ($field == 'tid') {
552 $mailvars['!term_name'] = db_result(db_query('SELECT name FROM {term_data} WHERE tid = %d', $s['value']));
553 }
554 elseif (!empty($node->tid)) {
555 $mailvars['!term_name'] = db_result(db_query('SELECT name FROM {term_data} WHERE tid = %d', $node->tid));
556 }
557 else {
558 unset($mailvars['!term_name']);
559 }
560 if ($s['load_function'] == 'subscriptions_content_comment_load' || ($s['load_function'] == 'subscriptions_content_node_load' && isset($node->_subscriptions_comments))) {
561 $comment_template = subscriptions_mail_template_load(SUBSCRIPTIONS_COMMENT_MAILKEY, $language->language, 'body', 'CITEM');
562 $separator = subscriptions_mail_template_load(SUBSCRIPTIONS_COMMENT_MAILKEY, $language->language, 'subject', 'SEP');
563 $mailvars['!comments'] = _subscriptions_content_format_comments($node, $comment_template, $separator);
564 }
565 else {
566 $mailvars['!comments'] = '';
567 }
568 $files = '';
569 if (isset($node->files) && user_access('view uploaded files')) {
570 foreach($node->files as $file) {
571 $files .= file_create_url($file->filepath) ."\n";
572 }
573 }
574 $mailvars['!has_files'] = empty($files) ? 0 : 1;
575 $mailvars['!files'] = $files;
576 }
577
578 /**
579 * Convert text with formatting into plain text.
580 */
581 function _subscriptions_content_format_text($text, $format = NULL) {
582 static $have_img_assist;
583 if (!isset($have_img_assist)) {
584 $have_img_assist = module_exists('img_assist');
585 }
586 if (!empty($have_img_assist)) {
587 foreach (img_assist_get_macros($text) as $unexpanded_macro => $macro) {
588 $expanded_macro = img_assist_render_image($macro);
589 if (preg_match('/<img src="([^"]*)".*title="([^"]*)"/', $expanded_macro, $matches)) {
590 $text = str_replace($unexpanded_macro, " [<a href=\"$matches[1]\">$matches[2]</a>] ", $text);
591 }
592 }
593 }
594
595 if (isset($format)) {
596 $text = check_markup($text, $format, FALSE);
597 }
598 $text = drupal_html_to_text($text);
599 return trim($text);
600 }
601
602 /**
603 * Given a comment template returns a formatted text of comments for given
604 * node.
605 */
606 function _subscriptions_content_format_comments($node, $comment_template, $separator) {
607 $comments = array();
608 foreach ($node->_subscriptions_comments as $comment) {
609 $mailvars = array(
610 '!comment_name' => ($comment->uid == 0 ? variable_get('anonymous', '!comment_name') : $comment->name),
611 '!comment_title' => trim($comment->subject),
612 '!comment_text' => _subscriptions_content_format_text($comment->comment, $comment->format),
613 '!comment_url' => url('node/'. $comment->nid, array('fragment' => 'comment-'. $comment->cid, 'absolute' => TRUE)),
614 '!comment_is_new' => (integer)$comment->_subscriptions_is_new,
615 '!comment_is_published' => (integer)($comment->status == COMMENT_PUBLISHED),
616 );
617 $template = module_invoke('subscriptions_mail', 'template_preprocess', $comment_template, $mailvars);
618 $comments[] = strtr($template ? $template : $comment_template, $mailvars);
619 }
620 return implode($separator, $comments);
621 }
622
623 /**
624 * Custom function for loading nodes.
625 * Loads not only the node but also any attached comments that are in the queue.
626 *
627 * Function name stored in {subscriptions_queue}.load_func and called by
628 * subscriptions_mail().
629 *
630 * @param $nid
631 * Node ID.
632 * @param $sqid
633 * Subscriptions queue ID.
634 * @param $is_new
635 * TRUE if this is a new-node notification.
636 *
637 * @return node as array().
638 */
639 function subscriptions_content_node_load($nid, $sqid, $is_new) {
640 // Do not cache because for different users the node can be different,
641 // subscriptions_mail_cron caches per uid.
642 $node = _subscriptions_content_load($nid, 0);
643 if (empty($node)) {
644 return;
645 }
646 if ($is_new) {
647 $node->_subscriptions_is_new = TRUE;
648 }
649 else {
650 $node->_subscriptions_is_updated = TRUE;
651 }
652 return $node;
653 }
654
655 /**
656 * Custom function for loading comments.
657 *
658 * Function name stored in {subscriptions_queue}.load_func and called by
659 * subscriptions_mail().
660 *
661 * @param $cid
662 * Comment ID.
663 * @param $sqid
664 * Subscriptions queue ID.
665 *
666 * @return node as array().
667 */
668 function subscriptions_content_comment_load($cid, $sqid) {
669 $nid = db_result(db_query('SELECT nid FROM {comments} WHERE cid = %d', $cid));
670 if (empty($nid)) {
671 return;
672 }
673 $item = db_fetch_array(db_query('SELECT * FROM {subscriptions_queue} WHERE sqid = %d', $sqid));
674 if ($item['module'] != 'node' || $item['field'] != 'nid') {
675 // Only if we're processing a node/nid queue item should we cut off the comments at an update item, otherwise not:
676 $sqid = NULL;
677 }
678 $node = _subscriptions_content_load($nid, $sqid);
679 if (empty($node->_subscriptions_comments)) {
680 return;
681 }
682 return $node;
683 }
684
685 /**
686 * Returns a node if published, including any comments that are still queued, but
687 * limited by the given subscriptions queue ID.
688 */
689 function _subscriptions_content_load($nid, $comment_load_sqid) {
690 global $user;
691
692 $node = node_load($nid, NULL, TRUE);
693 // Note: we must not cache across users (access checking), and we take care
694 // not to process the same node more than once (except for multiple batches
695 // of comments), so we don't gain from caching nodes; on the contrary: we
696 // might run out of memory!
697
698 if (!empty($node) && module_exists('comment')) {
699 $published_comments_only = $limit_sqids = '';
700 if (!user_access('administer comments')) {
701 $published_comments_only = 'AND c.status = '. COMMENT_PUBLISHED;
702 }
703 if (!empty($comment_load_sqid)) {
704 // check for a later queued update notification (don't send comments past that one because it will go out as node/type with its own comments later!)
705 if ($cutoff_sqid = db_result(db_query_range("SELECT sqid FROM {subscriptions_queue} WHERE module = 'node' AND field = 'nid' AND value = '%s' AND uid = %d AND load_function = 'subscriptions_content_node_load' AND sqid > %d", array($nid, $user->uid, $comment_load_sqid), 0, 1))) {
706 $limit_sqids = 'AND q.sqid < '. (integer) $cutoff_sqid;
707 }
708 }
709 $sql = "
710 SELECT q.sqid AS _subscriptions_sqid, q.is_new AS _subscriptions_is_new, c.*
711 FROM {comments} c
712 INNER JOIN {subscriptions_queue} q ON ". ($GLOBALS['db_type'] == 'pgsql' ? 'CAST(' : '')
713 ."c.cid". ($GLOBALS['db_type'] == 'pgsql' ? ' AS VARCHAR)' : '')
714 ." = q.load_args AND q.uid = %d AND q.load_function = '%s'
715 WHERE c.nid = %d ". $published_comments_only .' '. $limit_sqids;
716 $sql = db_rewrite_sql($sql, 'c', 'cid');
717 $result = db_query($sql, $user->uid, 'subscriptions_content_comment_load', $nid);
718 $sqids = array();
719 while ($comment = db_fetch_object($result)) {
720 comment_invoke_comment($comment, 'view');
721 if ($comment && user_access('access comments') && !isset($node->_subscriptions_comments[$comment->cid])) {
722 $node->_subscriptions_comments[$comment->cid] = $comment;
723 }
724 $sqids[] = $comment->_subscriptions_sqid;
725 }
726 if ($sqids) {
727 db_query('DELETE FROM {subscriptions_queue} WHERE sqid IN ('. db_placeholders($sqids) .')', $sqids);
728 }
729 }
730 return (empty($node) ? NULL : $node);
731 }
732
733 /**
734 * Subscriptions page callback: List thread subscriptions.
735 */
736 function subscriptions_content_page_node($account, $form) {
737 return drupal_get_form('subscriptions_content_node_form', $account, $form);
738 }
739
740 /**
741 * Build the Thread subscriptions form at user/UID/subscriptions/node.
742 *
743 * @ingroup form
744 */
745 function subscriptions_content_node_form(&$form_state, $account, $form) {
746 $uid = $account->uid;
747 $tr = 't';
748 $subscriptions = array();
749 $sql = db_rewrite_sql("
750 SELECT n.nid, n.uid, s.send_interval, s.author_uid, s.send_comments, s.send_updates, n.title, n.status, n.changed, n.comment AS comment_count, ncs.last_comment_timestamp,
751 IF(s.send_comments + s.send_updates = 0, n.created, IF(s.send_comments + s.send_updates = 2, IF(n.changed > ncs.last_comment_timestamp, n.changed, ncs.last_comment_timestamp), IF(s.send_comments = 1, ncs.last_comment_timestamp, n.changed))) AS latest_activity
752 FROM {node} n
753 INNER JOIN {subscriptions} s ON ". ($GLOBALS['db_type'] == 'pgsql' ? 'CAST(' : '')
754 ."n.nid". ($GLOBALS['db_type'] == 'pgsql' ? ' AS VARCHAR)' : '')
755 ." = s.value
756 LEFT JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid
757 WHERE s.module = 'node' AND s.field = 'nid' AND s.recipient_uid = %d
758 ORDER BY latest_activity DESC");
759 $sql_count = db_rewrite_sql("
760 SELECT COUNT(n.nid)
761 FROM {node} n
762 INNER JOIN {subscriptions} s ON ". ($GLOBALS['db_type'] == 'pgsql' ? 'CAST(' : '')
763 ."n.nid". ($GLOBALS['db_type'] == 'pgsql' ? ' AS VARCHAR)' : '')
764 ." = s.value
765 WHERE s.module = 'node' AND s.field = 'nid' AND s.recipient_uid = %d");
766 $result = pager_query($sql, 50, 0, $sql_count, $uid);
767 while ($s = db_fetch_array($result)) {
768 $subscriptions[$s['nid']][$s['author_uid']] = $s;
769 }
770
771 // check whether we've commented:
772 $nids = array_keys($subscriptions);
773 $result = db_query("
774 SELECT nid FROM {comments}
775 WHERE
776 nid IN (
777 SELECT ". ($GLOBALS['db_type'] == 'pgsql' ? 'CAST(' : '')
778 ."value". ($GLOBALS['db_type'] == 'pgsql' ? ' AS INTEGER)' : '')
779 ." FROM {subscriptions} WHERE module = 'node' AND field = 'nid' AND recipient_uid = %d
780 )
781 AND uid = %d GROUP BY nid", $uid, $uid);
782 while ($c = db_fetch_array($result)) {
783 if (isset($subscriptions[$c['nid']])) {
784 foreach ($subscriptions[$c['nid']] as $author_uid => $subscription) {
785 $subscriptions[$c['nid']][$author_uid]['commented'] = TRUE;
786 }
787 }
788 }
789
790 $form[0] = array(
791 '#type' => 'item',
792 '#title' => '',
793 '#tree' => TRUE,
794 '#theme' => 'subscriptions_form_table',
795 );
796 $defaults = array();
797 foreach ($subscriptions as $nid => $bundle) {
798 foreach ($bundle as $author_uid => $subscription) {
799 $title = truncate_utf8($subscription['title'], 40);
800 if ($title != $subscription['title']) {
801 $title .= '...';
802 }
803 $title = l($title, 'node/'. $subscription['nid']);
804 if (!$subscription['status']) {
805 if (user_access('administer nodes')) {
806 $title = SUBSCRIPTIONS_UNAVAILABLE .'&nbsp;'. $title;
807 }
808 else {
809 continue;
810 }
811 }
812 $subscription['extra_info'] = t('@latest_activity, @authored, @commented', array(
813 '@latest_activity' => format_interval(time() - $subscription['latest_activity']),
814 '@authored' => ($subscription['uid'] == $uid ? $tr('Yes') : $tr('No')),
815 '@commented' => (!empty($subscription['commented']) ? $tr('Yes') : $tr('No')),
816 ));
817 subscriptions_form_helper($form[0], $defaults, $author_uid, $subscription['nid'], $title, $subscription);
818 }
819 }
820 unset($form[0]['author']);
821
822 if (count(element_children($form[0]))) {
823 $form[0]['extra_info']['#title'] = t('Latest activity, authored, commented');
824 $form[0]['defaults'] = array(
825 '#type' => 'value',
826 '#value' => $defaults,
827 );
828 subscriptions_form_column_filter($form[0], $uid);
829 $form['#tree'] = TRUE;
830 $form['uid'] = array('#type' => 'value', '#value' => $uid);
831 $form['access_key'] = array('#type' => 'value', '#value' => 'node');
832 $form['module'] = array('#type' => 'value', '#value' => 'node');
833 $form['field'] = array('#type' => 'value', '#value' => 'nid');
834 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 10);
835 $form['#submit'][] = 'subscriptions_page_form_submit';
836 $form['note'] = array('#type' => 'item', '#description' => '<div>'. t('Note: Deactivated subscriptions will be removed from the list.') .'</div>' );
837 $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
838 }
839 else {
840 $form = array(
841 array(
842 '#type' => 'item',
843 '#value' => t('There are no subscribed pages.'),
844 ),
845 );
846 }
847 return $form;
848 }
849
850 /**
851 * Subscriptions page callback: List content types subscriptions.
852 */
853 function subscriptions_content_page_type($account, $form) {
854 return drupal_get_form('subscriptions_content_type_form', $account, $form);
855 }
856
857 /**
858 * Build the Content Types subscriptions form at user/UID/subscriptions/type.
859 *
860 * @ingroup form
861 */
862 function subscriptions_content_type_form(&$form_state, $account, $form) {
863 $uid = (isset($account) ? $account->uid : (is_numeric(arg(5)) ? -arg(5) : -DRUPAL_AUTHENTICATED_RID));
864 $unlisteds = variable_get('subscriptions_unlisted_content_types', array());
865 $blockeds = variable_get('subscriptions_blocked_content_types', array());
866 $omits = array_merge($unlisteds, $blockeds);
867 $result = db_query(db_rewrite_sql("
868 SELECT s.value, s.send_interval, s.author_uid, s.send_comments, s.send_updates, nt.type, nt.name
869 FROM {subscriptions} s
870 INNER JOIN {node_type} nt ON s.value = nt.type
871 WHERE s.module = 'node' AND s.field = 'type' AND s.recipient_uid = %d
872 ORDER BY s.author_uid", 'nt', 'type'), $uid);
873 while ($s = db_fetch_array($result)) {
874 $subscriptions[$s['value']][$s['author_uid']] = $s;
875 }
876 $form[0] = array(
877 '#type' => 'item',
878 '#title' => '',
879 '#tree' => TRUE,
880 '#theme' => 'subscriptions_form_table',
881 );
882 $intervals = _subscriptions_send_intervals();
883 foreach (node_get_types() as $type) {
884 // add the active subscriptions
885 $type_name = check_plain($type->name);
886 if (in_array($type->type, $omits)) {
887 if (user_access('subscribe to all content types') || user_access('administer site configuration')) {
888 if (in_array($type->type, $blockeds)) {
889 $type_name = '<span class="error" title="'. t('This !content_type is blocked.', array('!content_type' => t('content type'))) .'">'. $type_name .'</span>&nbsp;'. SUBSCRIPTIONS_UNAVAILABLE;
890 }
891 else {
892 $type_name = $type_name .'&nbsp;'. SUBSCRIPTIONS_UNAVAILABLE;
893 }
894 }
895 else {
896 continue;
897 }
898 }
899 if (!isset($subscriptions[$type->type][-1])) {
900 // author-less item is missing -- add it here:
901 $subscriptions[$type->type][-1] = array(
902 'send_interval' => _subscriptions_get_setting('send_interval', ($uid < 0 ? $uid : $account)),
903 'send_comments' => _subscriptions_get_setting('send_comments', ($uid < 0 ? $uid : $account)),
904 'send_updates' => _subscriptions_get_setting('send_updates', ($uid < 0 ? $uid : $account)),
905 'author_uid' => FALSE,
906 );
907 }
908 foreach ($subscriptions[$type->type] as $author_uid => $subscription) {
909 subscriptions_form_helper($form[0], $defaults, $author_uid, $type->type, $type_name, $subscription);
910 }
911 }
912
913 if (isset($form[0]['checkboxes'])) {
914 $form[0]['defaults'] = array(
915 '#type' => 'value',
916 '#value' => $defaults,
917 );
918 subscriptions_form_column_filter($form[0], $uid);
919 $form['#tree'] = TRUE;
920 $form['uid'] = array('#type' => 'value', '#value' => $uid);
921 $form['access_key'] = array('#type' => 'value', '#value' => 'type');
922 $form['module'] = array('#type' => 'value', '#value' => 'node');
923 $form['field'] = array('#type' => 'value', '#value' => 'type');
924 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 10);
925 $form['#submit'][] = 'subscriptions_page_form_submit';
926 }
927 else {
928 $form['header']['#value'] = t('There are no available !subs_types.', array('!subs_types' => t('content types')));
929 unset($form['footer']);
930 }
931 return $form;
932 }
933
934 /**
935 * Implementation of hook_mailkeys().
936 *
937 * Provide mailkeys for mail_edit.
938 *
939 * @ingroup hooks
940 */
941 function subscriptions_content_mailkeys() {
942 $mailkeys['node-nid'] = t('Notifications for %Pages (update/comment) subscriptions', array('%Pages' => t('Pages/Threads')));
943 foreach (array_keys(node_get_types()) as $type) {
944 $mailkeys['node-type-'. $type] = t('Notifications for %type !content_type subscriptions', array('%type' => $type, '!content_type' => t('content type')));
945 }
946 return $mailkeys;
947 }
948
949 /**
950 * Implementation of hook_mail_edit_tokens_list().
951 *
952 * Provide replacable tokens for mail_edit.
953 *
954 * @ingroup hooks
955 */
956 function subscriptions_content_mail_edit_tokens_list($mailkey, $options = array()) {
957 $tr = 't';
958 $tokens = array();
959 switch ($mailkey) {
960 case 'comments':
961 $tokens += array(
962 '!comment_name' => t('The name of the comment author.'),
963 '!comment_title' => t('The title of the comment.'),
964 '!comment_text' => t('The body text of the comment.'),
965 '!comment_url' => t('The direct URL of the comment.'),
966 '!comment_is_new' => t('The type of comment notification: 1 = new comment, 0 = updated comment.'),
967 '!comment_is_published' => t('The comment publication state: 1 = published, 0 = unpublished.<br />(Unpublished comments are sent to users with the %administer_comments permission only.)', array('%administer_comments' => $tr('administer comments'))),
968 );
969 return $tokens;
970
971 default:
972 $tokens += array(
973 '!unsubscribe_url' => t('The user can unsubscribe by clicking this link.'),
974 '!sender_name' => t('The name of the sender (if the sender is visible).'),
975 '!sender_page' => t('The user page of the sender (if the sender is visible).'),
976 '!sender_has_contact_page' => t("The sender's contact setting: 1 = contact form enabled, 0 = disabled."),
977 '!sender_contact_page' => t('The contact page of the sender.'),
978 '!subs_type' => t("The type of the subscription, like '!thread' or '!category'.", array('!thread' => t('thread'), '!category' => $tr('category'))),
979 '!node_type' => t("The type of the node, like '!forum' or '!story'.", array('!forum' => 'forum', '!story' => 'story')),
980 '!title' => t('The title of the subscriptions item.'),
981 '!teaser' => t('An excerpt of the subscriptions item.'),
982 '!body' => t('The body of the subscriptions item.'),
983 );
984 if (isset($options['tokens'])) {
985 $tokens += $options['tokens'];
986 }
987
988 $tokens += module_invoke_all('subscriptions_tokens_list', $mailkey, array('tokens' => $tokens));
989
990 if ($mailkey != 'digest') {
991 $tokens['!full_node'] = t('The full node as it appears on the website (must be specifically enabled !here).', array('!here' => l(t('here'), 'admin/settings/subscriptions', array('fragment' => 'edit-subscriptions-generate-full-node'))));
992 }
993 $tokens += array(
994 '!url' => t('The URL of the item.'),
995 '!is_new' => t('The type of notification: 1 = new item, 0 = otherwise.'),
996 '!is_updated' => t('The type of notification: 1 = updated (possibly new and already updated) item, 0 = otherwise.'),
997 '!is_old' => t('The type of notification: 1 = neither new nor updated item, 0 = otherwise.'),
998 '!is_published' => t('The publication state: 1 = published, 0 = unpublished.<br />(Unpublished nodes are sent to users with the %administer_nodes permission only.)', array('%administer_nodes' => $tr('administer nodes'))),
999 '!has_files' => t('The presence of attached files or img_assist images: 1 = files are available in !files, 0 = no files.'),
1000 '!files' => t('The list of attached files, one per line, if any, otherwise empty.'),
1001 '!has_new_comments' => t('The comments state: 1 = comments are available in !comments, 0 = no comments.'),
1002 '!comments' => t('One or more comments if available, otherwise empty.') .'<br />',
1003 );
1004 if ($mailkey == 'node-nid' || $mailkey == 'digest') {
1005 $tokens['!comments'] = $tokens['!comments'] . t('The rendering of the comments is defined by the template below.');
1006 }
1007 else {
1008 $tokens['!comments'] = $tokens['!comments'] . t('The rendering of the comments is defined by the !link.', array(
1009 '!link' => '<a href="../subscriptions_content_node-nid/'. check_plain(arg(4)) .'#edit-comment-body">'. t('comment templates') .'</a>'));
1010 }
1011 }
1012 return $tokens;
1013 }
1014
1015 /**
1016 * Implementation of hook_mail_edit_text().
1017 *
1018 * Provide default template strings for mail_edit.
1019 *
1020 * @ingroup hooks
1021 */
1022 function subscriptions_content_mail_edit_text($mailkey, $language) {
1023 if (!($return = module_invoke('subscriptions_mail', 'subscriptions_mail_text', $mailkey, $language))) {
1024 $return = module_invoke_all('subscriptions_mail_text', $mailkey, $language);
1025 }
1026 return $return;
1027 }
1028
1029 /**
1030 * Implementation of hook_form_alter().
1031 *
1032 * Add the comment parts to the subscriptions_content_node-nid mail_edit page.
1033 *
1034 * @ingroup hooks
1035 * @ingroup form
1036 */
1037 function subscriptions_content_form_mail_edit_trans_alter(&$form, &$form_state) {
1038 $mailkey = 'subscriptions_content_node-nid';
1039 if ($form['id']['#value'] == $mailkey) {
1040 $tr = 't';
1041 $langcode = $form['language']['#value'];
1042
1043 $form['mail']['note'] = array(
1044 '#value' => '<div>'. t("Note: 'new' and 'update' notifications for %Pages subscriptions use the matching !content_type subscriptions template rather than this one, if allowed; this does not apply to 'comment'-only notifications.", array('%Pages' => t('Pages/Threads'), '!content_type' => t('content type'))) .'</div>',
1045 '#weight' => -1,
1046 );
1047
1048 $comment_body = subscriptions_mail_template_load