Mailinglist
 All Data Structures Files Functions Variables Pages
mailinglist_archive.module
Go to the documentation of this file.
1 <?php
2 
3 define('MAILINGLIST_ARCHIVE_VERSION', 1);
4 
15  return array(
16  'administer mailinglist archive' => array(
17  'title' => t('Administer Mailinglist Archives'),
18  ),
19  );
20 }
21 
27 
28  $lists = mailinglist_list_load_all(false);
29  foreach ($lists as $index => $list) {
30  if (isset($list->operations['archive']) && $list->operations['archive']) {
32  }
33  }
34 }
35 
41  $queue = array(
42  'mailinglist_archive_thread' => array(
43  'worker callback' => 'mailinglist_archive_thread_callback',
44  'time' => '1',
45  ),
46  );
47  return $queue;
48 }
49 
50 
62 function mailinglist_archive_ctools_plugin_directory($owner, $plugin_type) {
63  return 'plugins/' . $owner . '/' . $plugin_type;
64 }
65 
69 function mailinglist_archive_theme($existing, $type, $theme, $path) {
70  if($type != 'module') {
71  return array();
72  }
73  return array(
74  'mailinglist_archive_form' => array(
75  'render element' => 'element',
76  'file' => 'mailinglist_archive_theme.inc',
77  ),
78  'mailinglist_archive_form_header' => array(
79  'render element' => 'element',
80  'file' => 'mailinglist_archive_theme.inc',
81  ),
82  'mailinglist_summary' => array(
83  'render element' => 'element',
84  'file' => 'mailinglist_archive_theme.inc',
85  ),
86  'mailinglist_thread' => array(
87  'render element' => 'element',
88  'file' => 'mailinglist_archive_theme.inc',
89  ),
90  'mailinglist_message' => array(
91  'render element' => 'element',
92  'file' => 'mailinglist_archive_theme.inc',
93  ),
94  'mailinglist_body' => array(
95  'render element' => 'element',
96  'file' => 'mailinglist_archive_theme.inc',
97  ),
98  );
99 }
100 
105  return array(
106  'mailinglist_archive' => array(
107  'name' => 'Mailing List Archive Message',
108  'base' => 'mailinglist_message',
109  'description' => 'Message archived for a mailing ist',
110  'help' => '(opt) Help displayed when creating nodes',
111  'has_title' => TRUE, //(or FALSE) does type have a "title" FIELD.
112  'title_lable' => 'Subject',
113  'locked' => TRUE, //(or FALSE), can machine name be changed in admin interface
114  ),
115  );
116 }
117 
118 /*******************************************************************************
119  *
120  */
121 
125 function mailinglist_message_form($node, &$form_state) {
127  dpm($node);
128  return node_content_form($node, $form_state);
129 }
130 
136  dpm($node);
137 }
138 
142 function mailinglist_message_update($node) {
144  dpm($node);
145 }
146 
150 function mailinglist_message_delete($node) {
152  dpm($node);
153 }
154 
158 function mailinglist_message_load($nodes) {
160  dpm($node);
161 /*
162 $nodes is an array, fill in the elements with the node data based on the keys.
163 
164 Something like:
165  $result = db_query('SELECT nid, foo FROM {mytable} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
166  foreach ($result as $record) {
167  $nodes[$record->nid]->foo = $record->foo;
168  }
169 */
170 }
171 
172 
180  $list_name = $list->admin_name;
181  $list_schema = array();
182  $list_schema['mailinglist_archives__' . $list_name] = array(
183  'description' => 'Table for holding the text of the messages',
184  'fields' => array(
185  'mid' => array(
186  'type' => 'serial',
187  'unsigned' => TRUE,
188  'not null' => TRUE,
189  'description' => 'Primary Key for table',
190  ),
191  'msg_id' => array(
192  'type' => 'varchar',
193  'length' => '1024',
194  'description' => 'Message ID from message',
195  'not null' => TRUE,
196  ),
197  'reply_to' => array(
198  'type' => 'int',
199  'unsigned' => TRUE,
200  'description' => 'mid of message we are a reply to',
201  ),
202  'thread_id' => array(
203  'type' => 'int',
204  'unsigned' => TRUE,
205  'not null' => TRUE,
206  'description' => 'mid of first message of thread',
207  'default' => 0, // Actually = mid, but we can't tell sql that
208  ),
209  'time' => _mailinglist_datetime_schema('Time message was posted'),
210  'subject' => array(
211  'type' => 'varchar',
212  'length' => '2048',
213  'description' => 'Subject of Message',
214  'not null' => TRUE,
215  ),
216  'year' => array(
217  'type' => 'int',
218  'size' => 'small',
219  'not null' => TRUE,
220  ),
221  'month' => array(
222  'type' => 'int',
223  'size' => 'tiny',
224  'not null' => TRUE,
225  ),
226  'day' => array(
227  'type' => 'int',
228  'size' => 'tiny',
229  'not null' => TRUE,
230  ),
231  'hour' => array(
232  'type' => 'int',
233  'size' => 'tiny',
234  'not null' => TRUE,
235  ),
236  'author' => array(
237  'type' => 'int',
238  'unsigned' => TRUE,
239  'not null' => TRUE,
240  'description' => 'Index to Author\'s ID in mailinglist_names'
241  ),
242  'author_email' => array(
243  'type' => 'varchar',
244  'length' => 255,
245  'not null' => TRUE,
246  ),
247  'updated' => _mailinglist_datetime_schema(
248  array(
249  'description' => 'Time message was last updated (Debug)',
250  'not null' => TRUE,
251  'default' => '0000',
252  )
253  ),
254  'body' => array(
255  'type' => 'text',
256  ),
257  'headers' => array(
258  'type' => 'text',
259  'serialize' => TRUE,
260  ),
261  'raw_body' => array(
262  'type' => 'text',
263  ),
264  'status' => array(
265  'type' => 'int',
266  'description' => 'published status, 0 = published, 1 = moderation queue, 2=waiting reply, 3=rejected',
267  'not null' => TRUE,
268  'default' => 0,
269  ),
270  'version' => array(
271  'type' => 'int',
272  'description' => 'Version of the record (for development)',
273  'not null' => TRUE,
274  'default' => 0,
275  ),
276  // ?? digest flag, topic flags
277  ),
278  'primary key' => array('mid'),
279  'unique keys' => array(
280  'msg_id' => array(array('msg_id', 100)),
281  ),
282  'indexes' => array(
283  'time' => array('time'),
284  'thread' => array('thread_id', 'time'),
285  'subject' => array(array('subject', 255), 'time'),
286  'author' => array('author', 'time'),
287  'author_email' => array('author_email'),
288  'hour' => array('hour'),
289  'reply_to' => array('reply_to', 'time'),
290  'time_parsed' => array('year', 'month', 'day'),
291  'updated' => array('updated', 'time', 'version'),
292  ),
293  'foreign keys' => array(
294  'reply_to' => array(
295  'table' => 'mailinglist_archives__' . $list_name,
296  'columns' => array('reply_to' => 'mid'),
297  ),
298  'thread_id' => array(
299  'table' => 'mailinglist_archives__' . $list_name,
300  'columns' => array('thread_id' => 'mid'),
301  ),
302  'author' => array(
303  'table' => 'mailinglist_names',
304  'columns' => array('author' => 'key'),
305  ),
306  ),
307  );
308  $list_schema['mailinglist_archives_references__' . $list_name] = array(
309  'description' => 'Table to track the references of a message',
310  'fields' => array(
311  'mid' => array(
312  'type' => 'serial',
313  'unsigned' => TRUE,
314  'not null' => TRUE,
315  'description' => 'mid of message these are references to',
316  ),
317  'idx' => array(
318  'type' => 'int',
319  'unsigned' => TRUE,
320  'not null' => TRUE,
321  'description' => 'Sequence number for which ref this is for the meesage'
322  ),
323  'ref_mid' => array(
324  'type' => 'int',
325  'unsigned' => TRUE,
326  'description' => 'mid of message we are a reply to',
327  ),
328  'ref_msg_id' => array(
329  'type' => 'varchar',
330  'length' => '2048',
331  'not null' => TRUE,
332  'description' => 'Message ID of message we are a reply to',
333  ),
334  ),
335  'primary key' => array('mid', 'idx'),
336  'indexes' => array(
337  'ref_mid' => array('ref_mid'),
338  'ref_msg_id' => array(array('ref_msg_id', 40)),
339  ),
340  'foreign keys' => array(
341  'mid' => array(
342  'table' => 'mailinglist_archives__' . $list_name,
343  'columns' => array('mid' => 'mid'),
344  ),
345  'ref_mid' => array(
346  'table' => 'mailinglist_archives__' . $list_name,
347  'columns' => array('ref_mid' => 'mid'),
348  ),
349  'ref_msg_id' => array(
350  'table' => 'mailinglist_archives__' . $list_name,
351  'columns' => array('ref_msg_id' => 'msg_id'),
352  ),
353  ),
354  );
355  $list_schema['mailinglist_archives_thread__' . $list_name] = array(
356  'description' => 'Table for holding information about threads',
357  'fields' => array(
358  'thread_id' => array(
359  'type' => 'int',
360  'unsigned' => TRUE,
361  'not null' => TRUE,
362  'description' => 'mid of first message of thread',
363  ),
364  'start_time' => _mailinglist_datetime_schema(array(
365  'description' => 'Time thread was started',
366  'not null' => TRUE,
367  )),
368  'last_time' => _mailinglist_datetime_schema(array(
369  'description' => 'Time thread was last posted to',
370  'not null' => TRUE,
371  )),
372  ),
373  'primary key' => array('thread_id'),
374  'indexes' => array(
375  'times' => array('start_time', 'last_time'),
376  ),
377  'foreign keys' => array(
378  'mid' => array(
379  'table' => 'mailinglist_archives__' . $list_name,
380  'columns' => array('thread_id' => 'thread_id'),
381  ),
382  ),
383  );
384  // $list_schema['mailinglist_archives_topics__ . $list_name]
385 
386  $list->schema($list_schema, 'archive');
387  return $list_schema;
388 }
389 
407 function mailinglist_archive_message_add($msg, $list) {
408  $listname = $list -> admin_name;
409  $table = 'mailinglist_archives__' . $listname;
410  $table_ref = 'mailinglist_archives_references__' . $listname;
411  $msg_id = $msg->header('Message-ID');
412  $from = $msg->headerEmail('From');
413  $subject = $msg->header('Subject');
414  $time = $msg->headerNoComments('Date');
415  $refs = $msg->header('References');
416  $refrply = $msg->headerNoComments('In-Reply-To');
417  $status = $msg->header('XX-Status');
418 
419  if (!is_array($refs)) {
420  if (empty($refs)) {
421  if (empty($refrply)) {
422  $refs = array();
423  } else {
424  $refs = array($refrply);
425  }
426  } else {
427  $refs = explode(' ', $refs);
428  }
429  }
430 
431  // $transaction = db_transaction();
432  // See if message exists.
433  $sql = 'SELECT * FROM {' . $table .
434  '} WHERE msg_id = :msgid';
435  $result = db_query($sql, array(':msgid' => $msg_id)) -> fetchAssoc();
436  dpm($result, 'Old Version');
437  $incr = empty($result);
438 
439  $nameid = mailinglist_email_name($from['email'], $from['name'], $incr);
440  $headers = serialize($msg->headers());
441  $body = $msg->body();
442  $raw_body = $msg->raw();
444  $time = str_replace(array("US/Mountain"), array("America/Denver"), $time);
445  $ts = strtotime($time);
446  if($ts < strtotime('1980-01-01')) {
447  $list->log('Bad Date: ' . $time, $list::LOG_ERROR);
448  }
449  $info = array (
450  'msg_id' => $msg_id,
451  'reply_to' => 0, // will be filled in later if needed
452  'thread_id' => 0, // will be filled in later
453  'time' => _mailinglist_datetime_format($ts),
454  'year' => (int)date('Y', $ts),
455  'month' => (int)date('m', $ts),
456  'day' => (int)date('d', $ts),
457  'hour' => (int)date('h', $ts),
458  'subject' => substr($subject, 0, 255),
459  'author' => $nameid,
460  'author_email' => $from['email'],
461  'body' => $body,
462  'headers' => $headers,
463  'raw_body' => $raw_body,
464  'status' => (int)$status,
465  'updated' => _mailinglist_datetime_format(time()),
466  'version' => MAILINGLIST_ARCHIVE_VERSION,
467  );
468  try {
469  if (empty($result)) {
470  dpm($info, 'New Record');
471  // New Record
472  $mid = db_insert($table) ->fields($info) -> execute();
473  $info['thread_id'] = $mid;
474  } else {
475  // Updating record
476  $info['status'] = min($info['status'], (int)$result['status']);
477  $mid = $result['mid'];
478  $info['mid'] = $mid;
479  if(($info['thread_id'] == 0) or ($info['reply_to'] == 0)) {
480  $info['thread_id'] = $mid;
481  }
482  }
483  dpm($info, 'Update Record');
484  db_update($table) -> fields($info) -> condition('mid', $mid) -> execute();
485 
486 
487 
488  // Build up Reference Table
489  foreach ($refs as $idx => $ref) {
490  // See if $ref is a known msg_id
491  $sql = 'SELECT mid FROM {' . $table . '} WHERE msg_id = :msgid';
492  $result = db_query($sql, array(':msgid' => $ref)) -> fetchAssoc();
493  if ($result) {
494  $ref_mid = $result['mid'];
495  } else {
496  $ref_mid = NULL;
497  }
498 
499  $info_ref = array(
500  'mid' => $mid,
501  'idx' => $idx,
502  'ref_mid' => $ref_mid,
503  'ref_msg_id' => $ref,
504  );
505  dpm($info_ref, 'Reference');
506  $sql = 'SELECT * FROM {' . $table_ref . '} WHERE mid = :mid AND idx = :idx';
507  $result = db_query($sql, array(':mid' => $mid, ':idx' => $idx)) ->fetchAssoc();
508  if ($result) {
509  db_update($table_ref) -> fields($info_ref) -> condition('mid', $mid) ->
510  condition('idx', $idx) -> execute();
511  } else {
512  db_insert($table_ref) -> fields($info_ref) -> execute();
513  }
514  }
515 
516  mailinglist_archive_message_ref_id($list, $mid, $msg_id);
518  } catch (Exception $e) {
519  watchdog('mailinglist', 'Exception in mailinglist_archive_message_add: @e ',
520  array('@e' => print_r($e, true)), WATCHDOG_ERROR);
521  return;
522  }
523 }
524 
533 function mailinglist_archive_message_link($list, $mid) {
534  $listname = $list -> admin_name;
535  $table = 'mailinglist_archives__' . $listname;
536  $table_ref = 'mailinglist_archives_references__' . $listname;
537 
538  $reply_to = 0;
539  $thread_id = $mid;
540 
541  // Find lowest index item that we have a message to it.
542  $sql = 'SELECT ref_mid
543  FROM {' . $table_ref . '} as r, {' . $table . '} as m
544  WHERE r.mid = :mid AND r.ref_mid = m.mid and m.status = 0
545  ORDER BY r.idx ASC LIMIT 1';
546  $result = db_query($sql, array(':mid' => $mid)) -> fetchAssoc();
547  dpm($result, 'Link Info');
548  if ($result) {
549  $reply_to = $result['ref_mid'];
550  $sql = 'SELECT * FROM {' . $table . '} WHERE mid = :mid';
551  $result = db_query($sql, array(':mid' => $reply_to)) -> fetchAssoc();
552  $thread_id = $result['thread_id'];
553  } else {
554  $reply_to = 0;
555  $thread_id = $mid;
556  }
557  $info = array('reply_to' => $reply_to, 'thread_id' => $thread_id);
558  db_update($table) -> fields($info) -> condition('mid', $mid) -> execute();
559  dpm($info, 'Link');
560 
561  $sql = 'SELECT MIN( `time` ) AS start_time, MAX( `time` ) AS last_time
562  FROM {mailinglist_archives__' . $listname . '}
563  WHERE thread_id =:tid
564  GROUP BY thread_id';
565  $result = db_query($sql, array(':tid' => $thread_id)) -> fetchAssoc();
566  dpm($result, 'Threading');
567 
568  db_merge('mailinglist_archives_thread__' . $listname)
569  -> key(array('thread_id' => $thread_id))
570  -> fields($result)
571  -> execute();
572 
573  return;
574 }
575 
580 function mailinglist_archive_message_ref_id($list, $mid, $msgid) {
581  $listname = $list -> admin_name;
582  $table_ref = 'mailinglist_archives_references__' . $listname;
583 
584  // Find all archive_references that reference this msgid
585  $sql = 'SELECT * FROM {' . $table_ref . '} WHERE ref_msg_id = :msgid';
586  $result = db_query($sql, array(':msgid' => $msgid));
587  while($row = $result -> fetchAssoc()) {
588  // Does it currently point to the right mid?
589  if ($row['ref_mid'] != $mid) {
590  // No. Update reference and message it is a reference for.
591  $ref_mid = $row['mid'];
592  $ref_idx = $row['idx'];
593  $info_ref = array(
594  'ref_mid' => $mid,
595  );
596  db_update($table_ref) -> fields($info_ref) -> condition('mid', $ref_mid) ->
597  condition('idx', $ref_idx) -> execute();
598  mailinglist_archive_message_link($list, $ref_mid);
599  }
600  }
601 }
602 
606 function mailinglist_archive_summary_list($list, $vars, $cond = '', $order = "", $limit = "") {
607  $listname = $list->admin_name;
608  $tablename = 'mailinglist_archives__' . $listname;
609  $base = variable_get('mailinglist_base', 'mailinglist');
610 
611  if(!is_array($vars)) {
612  if(is_numeric($vars) && $cond == '') {
613  $cond = $vars;
614  }
615  $vars = array();
616  }
617 
618  if(is_numeric($cond)){
619  $vars[':mid'] = $cond;
620  $cond = 'a.mid = :mid';
621  }
622 
623  if(empty($order)) {
624  $order = 'ORDER BY a.time ASC';
625  }
626 
627  if(empty($limit)) {
628  $limit = 100;
629  }
630  if(is_numeric($limit)) {
631  $limit = "LIMIT $limit";
632  }
633 
634  $sql = 'Select * FROM
635  {' . $tablename . '} AS a
636  LEFT JOIN {mailinglist_names} AS n ON a.author = n.name_key';
637  if(!empty($cond)) {
638  $sql .= ' WHERE ' . $cond;
639  }
640  $sql .= ' ' . $order . ' ' . $limit;
641 // dpm($vars, $sql);
642  $data = array(
643  '#theme' => 'mailinglist_thread',
644  '#type' => 'item',
645  '#level' => 0,
646  'items' => array(),
647  );
648 
649  $result = db_query($sql, $vars);
650 
651  while($row = $result -> fetchAssoc()) {
652  $data['items'][] = array(
653  '#theme' => 'mailinglist_summary',
654  '#type' => 'item',
655  '#date' => _mailinglist_datetime_ts($row['time']),
656  'subject' => array('#markup' => $row['subject']),
657  'email' => array('#markup' => $row['email']),
658  'name' => array('#markup' => $row['name']),
659  'url' => array('#item' => "$base/$listname/archive/msg/" . $row['mid']),
660 // 'mid' => array('#markup' => $row['mid'], '#printed' => TRUE),
661 // 'reply_to' => array('#markup' => $row['reply_to'], '#printed' => TRUE),
662  '#mid' => $row['mid'],
663  '#reply_to' => $row['reply_to'],
664 
665  );
666  }
667  return $data;
668 }
669 
670 function mailinglist_archive_reply_tree($list, $msgid, $n = 0) {
671  $replies = mailinglist_archive_summary_list($list, array(':mid' => $msgid),
672  'a.reply_to = :mid', 'ORDER BY a.time ASC');
673  $replies['#level'] = $n;
674 if($n < 5) {
675  foreach($replies['items'] as $key => &$item) {
676  $item['reply_to'] = mailinglist_archive_reply_tree($list, $item['#mid'], $n+1);
677  }
678 }
679 //dpm($replies, 'Reply Tree');
680  return $replies;
681 }
682 
686 function mailinglist_archive_message_get($list, $msgid) {
687  $tablename = 'mailinglist_archives__' . $list->admin_name;
688  $sql = 'SELECT * FROM {' . $tablename. '} AS a,
689  {mailinglist_names} as n
690  WHERE a.mid = :mid AND a.author = n.name_key';
691  $result = db_query($sql, array(':mid' => $msgid)) -> fetchAssoc();
692  return $result;
693 }
694 
699  if ($msg->header('XX-Archive')) {
700  mailinglist_archive_message_add($msg, $list);
701  }
702 }
703 
707 function _mailinglist_archive_scan($list) {
708  $queue = DrupalQueue::get('mailinglist_message');
709  if($queue->numberOfItems() > 0) {
710  // Don't repair if importing, or behind
711  return;
712  }
713 
714  $listname = $list->admin_name;
715 
716  $sql = 'SELECT * FROM {mailinglist_archives__' . $listname . '}
717  ORDER BY updated ASC, time ASC LIMIT 10';
718  $result = db_query($sql, array());
719  while($row = $result -> fetchAssoc()) {
720  dpm($row, 'Refresh Scan');
721  $msg = $row['raw_body'];
722 
723  $msg = new MailinglistMessage($msg);
724 
725  $from = $msg->header('From');
726  if (is_array($from)) {
727  $from = $from[0];
728  }
729  // Fix up obfuscated from line
730  if (strpos($from, '@') == FALSE) {
731  // obfuscated from line from archives
732  $from = str_replace(' at ', '@', $from);
733  $msg->setHeader('From', $from);
734  }
735 
736  // resumbit message
737  $msg->setHeader('XX-List', $list->admin_name);
738  $msg->setHeader('XX-Archive', '1');
739  $msg->setHeader('XX-Status', $row['status']);
740  $list->processMessage($msg);
741  }
742 }
743 
748 }
mailinglist_archive_message_ref_id($list, $mid, $msgid)
mailinglist_archive_message_ref_id().
mailinglist_archive_theme($existing, $type, $theme, $path)
Implements hook_theme().
mailinglist_message_insert($node)
Implements hook_insert().
mailinglist_archive_message_add($msg, $list)
mailinglist_archive_message_add().
Class defining the contents of an e-mail message.
mailinglist_archive_permission()
Implements hook_permission().
mailinglist_archive_cron()
Implements hook_cron().
mailinglist_archive_cron_queue_info()
Implements hook_cron_queue_info().
mailinglist_message_delete($node)
Implements hook_delete().
mailinglist_message_load($nodes)
Implements hook_load().
_mailinglist_archives_list_schema($list)
_mailinnglist)archives_list_schema()
mailinglist_archive_message_link($list, $mid)
mailinglist_archive_message_link().
mailinglist_archive_ctools_plugin_directory($owner, $plugin_type)
Implements hook_ctools_plugin_directory().
mailinglist_message_form($node, &$form_state)
Implements hook_form().
_mailinglist_datetime_schema($desc)
Return the schema for a 'datetime' field.
_mailinglist_archive_scan($list)
Perform incremental scan of archive for problems.
mailinglist_email_name($email, $name, $count=0)
Lookup Email <-> Name combination.
mailinglist_archive_reply_tree($list, $msgid, $n=0)
mailinglist_archive_message_get($list, $msgid)
Get a full message by Msg ID.
mailinglist_archive_summary_list($list, $vars, $cond= '', $order="", $limit="")
Return list of message summaries.
mailinglist_archive_thread_callback($thread)
const MAILINGLIST_ARCHIVE_VERSION
mailinglist_message_update($node)
Implements hook_update().
mailinglist_list_load_all($show_disabled=TRUE)
Load all mailinglists.
mailinglist_archive_mailinglist_message($msg, $list)
Implements hook_mailinglist_message().
_mailinglist_datetime_format($ts)
Convert a 'timestamp to a 'datetime'.
mailinglist_archive_node_info()
Implments hook_node_info().
_mailinglist_datetime_ts($time)
Convert a 'datetime' field into a timestamp.