Browse Source

Began rework of options page. Also added 3 plugins (filters, translate, and squirrelspell) into CVS for maintenance with the Squirrelmail core. Please note that these plugins are NOT going to be INTEGRATED with the core, just maintained alongside it here in CVS and packaged as a part of the official Squirrelmail release when we release 1.2.

thomppj 24 years ago
parent
commit
1f4a32d08c
44 changed files with 3844 additions and 204 deletions
  1. 63 0
      plugins/filters/CHANGES
  2. 45 0
      plugins/filters/README
  3. 372 0
      plugins/filters/filters.php
  4. 349 0
      plugins/filters/options.php
  5. 85 0
      plugins/filters/setup.php
  6. 85 0
      plugins/filters/sqimap_read_data.php
  7. 19 0
      plugins/squirrelspell/INSTALL
  8. 30 0
      plugins/squirrelspell/doc/CRYPTO
  9. 72 0
      plugins/squirrelspell/doc/ChangeLog
  10. 14 0
      plugins/squirrelspell/doc/PRIVACY
  11. 52 0
      plugins/squirrelspell/doc/README
  12. 15 0
      plugins/squirrelspell/doc/UPGRADING
  13. 3 0
      plugins/squirrelspell/doc/index.php
  14. 3 0
      plugins/squirrelspell/index.php
  15. 3 0
      plugins/squirrelspell/js/WHATISTHIS
  16. 221 0
      plugins/squirrelspell/js/check_me.js
  17. 17 0
      plugins/squirrelspell/js/crypto_settings.js
  18. 21 0
      plugins/squirrelspell/js/decrypt_error.js
  19. 3 0
      plugins/squirrelspell/js/index.php
  20. 13 0
      plugins/squirrelspell/js/init.js
  21. 3 0
      plugins/squirrelspell/modules/WHATISTHIS
  22. 250 0
      plugins/squirrelspell/modules/check_me.mod.php
  23. 36 0
      plugins/squirrelspell/modules/crypto.mod.php
  24. 54 0
      plugins/squirrelspell/modules/crypto_badkey.mod.php
  25. 57 0
      plugins/squirrelspell/modules/edit_dic.mod.php
  26. 53 0
      plugins/squirrelspell/modules/enc_setup.mod.php
  27. 44 0
      plugins/squirrelspell/modules/forget_me.mod.php
  28. 43 0
      plugins/squirrelspell/modules/forget_me_not.mod.php
  29. 3 0
      plugins/squirrelspell/modules/index.php
  30. 44 0
      plugins/squirrelspell/modules/init.mod.php
  31. 53 0
      plugins/squirrelspell/modules/lang_change.mod.php
  32. 30 0
      plugins/squirrelspell/modules/lang_setup.mod.php
  33. 27 0
      plugins/squirrelspell/modules/options_main.mod.php
  34. 73 0
      plugins/squirrelspell/setup.php
  35. 88 0
      plugins/squirrelspell/sqspell_config.dist
  36. 88 0
      plugins/squirrelspell/sqspell_config.php
  37. 311 0
      plugins/squirrelspell/sqspell_functions.php
  38. 36 0
      plugins/squirrelspell/sqspell_interface.php
  39. 35 0
      plugins/squirrelspell/sqspell_options.php
  40. 16 0
      plugins/translate/INSTALL
  41. 73 0
      plugins/translate/README
  42. 144 0
      plugins/translate/options.php
  43. 416 0
      plugins/translate/setup.php
  44. 382 204
      src/options.php

+ 63 - 0
plugins/filters/CHANGES

@@ -0,0 +1,63 @@
+Changes since 0.8.3
+-------------------
+Just changed include() calls to require_once() calls.
+
+Changes since 0.8.2
+-------------------
+Added many new FREE anti-spam databases to lookup from.
+Removed ORBS since they're off the air.
+
+Changes since 0.8.1
+-------------------
+Added a SpamFilters_DNScache[] array that is useful for 2 reasons:
+
+1. You can put in IPs in the cache that override the SPAMfilter's DNS
+   checking routines -- either to force 'em to NOT filter email coming from
+   a specific IP (ie. for those pesky customers who can't figure out how to
+   make Exchange not be an open relay) or to force 'em to ALWAYS filter email
+   coming from a specific IP (ie. for those pesky sites you KNOW are SPAM
+   sources or relays but that aren't added to any of the DNS databases)
+
+2. Before the SPAMfilters do a DNS query, they check to see if the IP in
+   question is already in the cache.  If not and the query is performed, the
+   result is put in the DNS cache.  This makes the SPAM filters a LOT faster
+   when you get lots of email from various mailing lists (all coming from a
+   very small number of IPs).  The SPAM filters don't have to do a DNS query
+   on every message coming from the squirrelmail-plugins mailing list -- only
+   one the first time through to confirm the list server isn't in any of the
+   DNS databases!  :-)
+
+Changes since 0.8
+-------------------
+Just fixed the Bad or malformed FETCH error that occurred when the INBOX was
+empty.  The spamfilter plugin did a FETCH query from MsgNum 1 to * and since
+there WAS no Msg Num 1 the IMAP server would respond with an error.  Now I
+check the number of messages before calling spam_filters or user_filters
+
+Changes since 0.7
+--------------------
+Tyler made TONS of changes to incorporate the fixes I'd done and posted
+about, as well as to fix the problem with number of unread messages not
+showing up in the folder panel.  He also added some code to scan the headers
+for specific IPs in order to scan only IPs on the previous hop in the
+header. 
+
+This didn't work well for me (the IP of my gateway wasn't reported on the
+Received from ... by ... line, and so RSS and DUL still had tons of false
+hits.  I munged it so instead it uses a single string provided at 
+setup/install time to find the right line in the header to find the IPs to
+look for in the various databases.  (see SpamFilters_YourHop in setup.php)
+This seems to work pretty well for me -- faster enough to turn on all the
+databases and zero (so far) false hits! (grin)
+
+I also found a bug in sqimap_read_data() in functions/imap_general.php.
+After much discussion with Tyler, I rewrote it and posted it to
+squirrelmail-devel but it hasn't been accepted/tested/blessed yet
+NOTE: THIS NEW VERSION OF sqimap_read_data() IS REQUIRED BY FILTERS 0.8
+so you'll have to edit functions/imap_general.php and replace the function
+with the contents of sqimap_read_data.php.
+
+As soon as either my version of sqimap_read_data is officially part of the
+SM 1.1.2 CVS or until a new working version is posted, this is the only way
+to guarantee filters 0.8 will work.  
+

+ 45 - 0
plugins/filters/README

@@ -0,0 +1,45 @@
+Filters 0.8.3
+
+IMPORTANT: I've noticed at least one version of PHP that has bugs in the
+checkdnsrr() function that the SPAM filtering code RELIES ON.  In my case,
+the PHP server that comes with Mandrake 8.1 has this problem -- checkdnsrr()
+NEVER finds the inaddr records, even the ones that really exist. (sigh)
+
+NOTE!!! As of the time of this writing, there is a bug in sqimap_read_data()
+in functions/imap_general.php.  I rewrote it (see sqimap_read_data.php) so
+if the SPAM filters aren't filtering, make a backup copy of
+functions/imap_general.php, remove the sqimap_read_data() function in there
+and replace it with the contents of sqimap_read_data.php.  Hopefully, either
+my replacement will be blessed by squirrelmail-devel or some other version
+will come out soon.  Now back to your regularly scheduled README...  (grin)
+
+This is a poor alternative to procmail or Elm's filter programs.  This is a
+pathetic replacement for good RBL mail scanning when you get the mail.  This
+is more for systems that can't/won't offer that kind of functionality and
+you still require it.
+
+This is slow.  Yep.  Slow.
+
+
+To configure, you should just take a peek at setup.php and set
+$SpamFilters_YourHop to some string if you want to avoid tons of false
+hits on the RSS and DUL and ORBS databases.  It should also speed up the
+scan somewhat.  
+
+If you do not want to enable spam filters for all users, edit setup.php and
+set the $AllowSpamFilters to false.  Spam filters can take TONS of time, so
+if you don't want your users to complain and ask you tons of questions, this
+is a quick and easy method.
+
+If you use UW and if you encounter strange errors while using this plugin on 
+your system, edit setup.php and set $UseSeparateImapConnection to true.  This
+may not solve the problem.  One problem it might fix is if you run UW 2001
+and if you don't see the number of unread messages in your left-hand folder
+pane, or if you see timeouts or IMAP server error messages.  Turning on this
+feature may slow down the filters a bit more since it has to open a new
+connection.
+
+Lastly, if there are some IPs that you want to refuse email from or some IPs
+you want to accept email from REGARDLESS of what the DNS databases say, you
+can put in overrides in the SpamFilters_DNScache[] array.  See the comments
+in setup.php for more info on this.

+ 372 - 0
plugins/filters/filters.php

@@ -0,0 +1,372 @@
+<?php
+   /*
+    *  Message and Spam Filter Plugin 
+    *  By Luke Ehresman <luke@squirrelmail.org>
+    *     Tyler Akins
+    *     Brent Bice
+    *  (c) 2000 (GNU GPL - see ../../COPYING)
+    *
+    *  This plugin filters your inbox into different folders based upon given
+    *  criteria.  It is most useful for people who are subscibed to mailing lists
+    *  to help organize their messages.  The argument stands that filtering is
+    *  not the place of the client, which is why this has been made a plugin for
+    *  SquirrelMail.  You may be better off using products such as Sieve or
+    *  Procmail to do your filtering so it happens even when SquirrelMail isn't
+    *  running.
+    *
+    *  If you need help with this, or see improvements that can be made, please
+    *  email me directly at the address above.  I definately welcome suggestions
+    *  and comments.  This plugin, as is the case with all SquirrelMail plugins,
+    *  is not directly supported by the developers.  Please come to me off the
+    *  mailing list if you have trouble with it.
+    *
+    *  Also view plugins/README.plugins for more information.
+    *
+    */
+    
+   function start_filters() {
+      global $username, $key, $imapServerAddress, $imapPort, $imap,
+         $imap_general, $filters, $imap_stream, $imapConnection, 
+	 $UseSeparateImapConnection, $AllowSpamFilters;
+
+      // Detect if we have already connected to IMAP or not.
+      // Also check if we are forced to use a separate IMAP connection
+      if ((!isset($imap_stream) && !isset($imapConnection)) ||
+          $UseSeparateImapConnection) {
+         $stream = sqimap_login($username, $key, $imapServerAddress, 
+	    $imapPort, 10);
+         $previously_connected = false;
+      } elseif (isset($imapConnection)) {
+         $stream = $imapConnection;
+         $previously_connected = true;
+      } else {
+         $previously_connected = true;
+	 $stream = $imap_stream;
+      }
+
+      if (sqimap_get_num_messages($stream, "INBOX") > 0) {
+         // Filter spam from inbox before we sort them into folders
+         if ($AllowSpamFilters)
+            spam_filters($stream);
+	 
+         // Sort into folders
+         user_filters($stream);
+      }
+      
+      if (!$previously_connected)
+         sqimap_logout($stream);
+   }
+
+
+   function user_filters($imap_stream) {
+      $filters = load_filters();
+      if (! $filters) return;
+      
+      sqimap_mailbox_select($imap_stream, 'INBOX');
+      
+      // For every rule
+      for ($i=0; $i < count($filters); $i++) {
+         // If it is the "combo" rule
+         if ($filters[$i]["where"] == "To or Cc") {
+            /*
+             *  If it's "TO OR CC", we have to do two searches, one for TO
+             *  and the other for CC.
+             */
+	    filter_search_and_delete($imap_stream, 'TO',
+	       $filters[$i]['what'], $filters[$i]['folder']);
+	    filter_search_and_delete($imap_stream, 'CC',
+	       $filters[$i]['what'], $filters[$i]['folder']);
+         } else {
+            /*
+             *  If it's a normal TO, CC, SUBJECT, or FROM, then handle it 
+	     *  normally.
+             */
+	    filter_search_and_delete($imap_stream, $filters[$i]['where'],
+	       $filters[$i]['what'], $filters[$i]['folder']);
+         }
+      }
+      // Clean out the mailbox whether or not auto_expunge is on
+      // That way it looks like it was redirected properly
+      sqimap_mailbox_expunge($imap_stream, 'INBOX');
+   }
+   
+   function filter_search_and_delete($imap, $where, $what, $where_to) {
+      fputs ($imap, 'a001 SEARCH ALL ' . $where . ' "' . addslashes($what) . 
+         "\"\r\n");
+      $read = sqimap_read_data ($imap, 'a001', true, $response, $message);
+      
+      // This may have problems with EIMS due to it being goofy
+      
+      for ($r=0; $r < count($read) && 
+                 substr($read[$r], 0, 8) != '* SEARCH'; $r++) {}
+      if ($response == 'OK') {
+         $ids = explode(' ', $read[$r]);
+	 if (sqimap_mailbox_exists($imap, $where_to)) {
+            for ($j=2; $j < count($ids); $j++) {
+   	       $id = trim($ids[$j]);
+               sqimap_messages_copy ($imap, $id, $id, $where_to);
+               sqimap_messages_flag ($imap, $id, $id, 'Deleted');
+            }
+         }
+      }
+   }
+
+   // These are the spam filters
+   function spam_filters($imap_stream) {
+      global $data_dir, $username;
+      global $SpamFilters_YourHop;
+      global $SpamFilters_DNScache;
+
+      $filters_spam_scan = getPref($data_dir, $username, "filters_spam_scan");
+      $filters_spam_folder = getPref($data_dir, $username, "filters_spam_folder");
+      $filters = load_spam_filters();
+      
+      $run = 0;
+      
+      foreach ($filters as $Key=> $Value) {
+         if ($Value['enabled'])
+            $run ++;
+      }
+      
+      // short-circuit
+      if ($run == 0) {
+          return;
+      }
+      
+      sqimap_mailbox_select($imap_stream, 'INBOX');
+      
+      // Ask for a big list of all "Received" headers in the inbox with 
+      // flags for each message.  Kinda big.
+      fputs($imap_stream, 'A3999 FETCH 1:* (FLAGS BODY.PEEK[HEADER.FIELDS ' .
+       "(RECEIVED)])\r\n");
+      
+      $read = sqimap_read_data ($imap_stream, 'A3999', true, $response, $message);
+      
+      if ($response != 'OK')
+          return;
+
+      $i = 0;
+      while ($i < count($read)) {
+          // EIMS will give funky results
+          $Chunks = explode(' ', $read[$i]);
+          if ($Chunks[0] != '*') {
+              $i ++;
+              continue;
+          }
+          $MsgNum = $Chunks[1];
+
+          $IPs = array();
+          $i ++;
+          $IsSpam = 0;
+          $Scan = 1;
+          
+	  // Check for normal IMAP servers
+          if ($filters_spam_scan == 'new') {
+              if (is_int(strpos($Chunks[4], '\Seen'))) {
+                  $Scan = 0;
+              }
+          }
+	  
+	  // Look through all of the Received headers for IP addresses
+	  // Stop when I get ")" on a line
+	  // Stop if I get "*" on a line (don't advance)
+          // and above all, stop if $i is bigger than the total # of lines
+          while (($i < count($read)) &&
+                 ($read[$i][0] != ')' && $read[$i][0] != '*' &&
+	          $read[$i][0] != "\n") && (! $IsSpam))
+	  {
+              // Check to see if this line is the right "Received from" line
+              // to check
+              if (is_int(strpos($read[$i], $SpamFilters_YourHop))) {
+
+   	         // short-circuit and skip work if we don't scan this one
+                 if ($Scan) {
+                     $read[$i] = ereg_replace('[^0-9\.]', ' ', $read[$i]);
+                     $elements = explode(' ', $read[$i]);
+                     foreach ($elements as $value) {
+   		      if ($value != '' &&
+                          ereg('[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}',
+   			     $value, $regs)) {
+		          $Chunks = explode('.', $value);
+                          if ("$SpamFilters_DNScache[$value]" == "") {
+                             $SpamFilters_DNScache[$value] =
+                                filters_spam_check_site($Chunks[0], $Chunks[1],
+		                      $Chunks[2], $Chunks[3], $filters);
+                         }
+                         if ($SpamFilters_DNScache[$value]) {
+		            $IsSpam ++;
+                            break;  // no sense in checking more IPs
+                         }
+                      }
+                     }
+                 }
+              }
+              $i ++;
+          }
+	  
+	  // Lookie!  It's spam!  Yum!
+          if ($IsSpam) {
+              if (sqimap_mailbox_exists ($imap_stream, $filters_spam_folder)) {
+                  sqimap_messages_copy ($imap_stream, $MsgNum, $MsgNum, 
+		     $filters_spam_folder);
+                  sqimap_messages_flag ($imap_stream, $MsgNum, $MsgNum, 
+		     'Deleted');
+              }
+          }
+      }
+      
+      sqimap_mailbox_expunge($imap_stream, 'INBOX');
+   }   
+
+  
+   // Does the loop through each enabled filter for the specified IP address.
+   // IP format:  $a.$b.$c.$d
+   function filters_spam_check_site($a, $b, $c, $d, &$filters) {
+      foreach ($filters as $key => $value) {
+          if ($filters[$key]['enabled']) {
+              if ($filters[$key]['dns']) {
+                  if (checkdnsrr("$d.$c.$b.$a." . $filters[$key]['dns'],
+                     'ANY')) {
+                      return 1;
+                  }
+              }
+          }
+      }
+      return 0;
+   }
+   
+   function load_filters() {
+      global $data_dir, $username;
+      $filters = array();
+      for ($i=0; $fltr = getPref($data_dir, $username, 'filter' . $i); $i++) {
+         $ary = explode(',', $fltr);
+         $filters[$i]['where'] = $ary[0];
+         $filters[$i]['what'] = $ary[1];
+         $filters[$i]['folder'] = $ary[2];
+      }
+      return $filters;
+   }
+
+   function load_spam_filters() {
+      global $data_dir, $username;
+      
+      $filters['MAPS RBL']['prefname'] = 'filters_spam_maps_rbl';
+      $filters['MAPS RBL']['name'] = 'MAPS Realtime Blackhole List';
+      $filters['MAPS RBL']['link'] = 'http://www.mail-abuse.org/rbl/';
+      $filters['MAPS RBL']['dns'] = 'blackholes.mail-abuse.org';
+      $filters['MAPS RBL']['comment'] = 
+'COMMERCIAL - This list contains servers that are verified spam senders.
+It is a pretty reliable list to scan spam from.';
+      
+      $filters['MAPS RSS']['prefname'] = 'filters_spam_maps_rss';
+      $filters['MAPS RSS']['name'] = 'MAPS Relay Spam Stopper';
+      $filters['MAPS RSS']['link'] = 'http://www.mail-abuse.org/rss/';
+      $filters['MAPS RSS']['dns'] = 'relays.mail-abuse.org';
+      $filters['MAPS RSS']['comment'] =
+'COMMERCIAL - Servers that are configured (or misconfigured) to allow spam to
+be relayed through their system will be banned with this.  Another good one to
+use.';
+
+      $filters['MAPS DUL']['prefname'] = 'filters_spam_maps_dul';
+      $filters['MAPS DUL']['name'] = 'MAPS Dial-Up List';
+      $filters['MAPS DUL']['link'] = 'http://www.mail-abuse.org/dul/';
+      $filters['MAPS DUL']['dns'] = 'dialups.mail-abuse.org';
+      $filters['MAPS DUL']['comment'] =
+'COMMERCIAL - Dial-up users are often filtered out since they should use their
+ISP\'s mail servers to send mail.  Spammers typically get a dial-up account
+and send spam directly from there.';
+
+      $filters['MAPS RBLplus']['prefname'] = 'filters_spam_maps_rblplus';
+      $filters['MAPS RBLplus']['name'] = 'MAPS RBL+ List';
+      $filters['MAPS RBLplus']['link'] = 'http://www.mail-abuse.org/';
+      $filters['MAPS RBLplus']['dns'] = 'rbl-plus.mail-abuse.org';
+      $filters['MAPS RBLplus']['comment'] =
+'COMMERCIAL - RBL+ is a combination of RSS, DUL, and RBL.';
+
+      $filters['Osirusoft']['prefname'] = 'filters_spam_maps_osirusoft';
+      $filters['Osirusoft']['name'] = 'Osirusoft List';
+      $filters['Osirusoft']['link'] = 'http://relays.osirusoft.com/';
+      $filters['Osirusoft']['dns'] = 'relays.osirusoft.com';
+      $filters['Osirusoft']['comment'] =
+'FREE - Osirusoft - Very thorough, but also rejects replies from many
+ISP\'s abuse@domain.name email messages for some reason.';
+
+      $filters['ORDB']['prefname'] = 'filters_spam_ordb';
+      $filters['ORDB']['name'] = 'Open Relay Database List';
+      $filters['ORDB']['link'] = 'http://www.ordb.org/';
+      $filters['ORDB']['dns'] = 'relays.ordb.org';
+      $filters['ORDB']['comment'] =
+'FREE - ORDB was born when ORBS went off the air. It seems to have fewer false
+positives than ORBS did though.';
+      
+      $filters['ORBZ']['prefname'] = 'filters_spam_orbz';
+      $filters['ORBZ']['name'] = 'ORBZ List';
+      $filters['ORBZ']['link'] = 'http://www.orbz.org/';
+      $filters['ORBZ']['dns'] = 'inputs.orbz.org';
+      $filters['ORBZ']['comment'] =
+'FREE - Another ORBS replacement (just the INPUTS database used here).';
+      
+      $filters['Five-Ten']['prefname'] = 'filters_spam_fiveten';
+      $filters['Five-Ten']['name'] = 'Five-Ten-sg.com Lists';
+      $filters['Five-Ten']['link'] = 'http://www.five-ten-sg.com/blackhole.php';
+      $filters['Five-Ten']['dns'] = 'blackholes.five-ten-sg.com';
+      $filters['Five-Ten']['comment'] =
+'FREE - Five-Ten-sg.com has SPAM source, OpenRelay, and and Dialup IPs.';
+      
+      $filters['Dorkslayers']['prefname'] = 'filters_spam_dorks';
+      $filters['Dorkslayers']['name'] = 'Dorkslayers Lists';
+      $filters['Dorkslayers']['link'] = 'http://www.dorkslayers.com';
+      $filters['Dorkslayers']['dns'] = 'orbs.dorkslayers.com';
+      $filters['Dorkslayers']['comment'] =
+'FREE - Dorkslayers appears to include only really bad open relays outside
+the US to avoid being sued. Interestingly enough, their website recommends
+you NOT use their service.';
+      
+      $filters['ORBL']['prefname'] = 'filters_spam_orbl';
+      $filters['ORBL']['name'] = 'ORBL Lists';
+      $filters['ORBL']['link'] = 'http://www.orbl.org';
+      $filters['ORBL']['dns'] = 'or.orbl.org';
+      $filters['ORBL']['comment'] =
+'FREE - ORBL is another ORBS spinoff formed after ORBS shut down. May be
+SLOOOOOOW!';
+      
+      $filters['ORBZ-UK']['prefname'] = 'filters_spam_orbzuk';
+      $filters['ORBZ-UK']['name'] = 'ORBZ-UK Lists';
+      $filters['ORBZ-UK']['link'] = 'http://orbz.gst-group.co.uk';
+      $filters['ORBZ-UK']['dns'] = 'orbz.gst-group.co.uk';
+      $filters['ORBZ-UK']['comment'] =
+'FREE - orbz.gst-group.co.uk lists not only open relays, but also mailservers
+that refuse or bounce email addressed to postmaster@<theirdomain>.';
+      
+      foreach ($filters as $Key => $Value) {
+          $filters[$Key]['enabled'] = getPref($data_dir, $username,
+              $filters[$Key]['prefname']);
+      }
+      
+      return $filters;
+   }
+
+   function remove_filter ($id) {
+      global $data_dir, $username;
+      
+      while ($nextFilter = getPref($data_dir, $username, 'filter' . 
+         ($id + 1))) {
+         setPref($data_dir, $username, 'filter' . $id, $nextFilter);
+	 $id ++;
+      }
+      
+      removePref($data_dir, $username, 'filter' . $id);
+   }
+   
+   function filter_swap($id1, $id2) {
+      global $data_dir, $username;
+      
+      $FirstFilter = getPref($data_dir, $username, 'filter' . $id1);
+      $SecondFilter = getPref($data_dir, $username, 'filter' . $id2);
+      
+      if ($FirstFilter && $SecondFilter) {
+         setPref($data_dir, $username, 'filter' . $id2, $FirstFilter);
+         setPref($data_dir, $username, 'filter' . $id1, $SecondFilter);
+      }
+   }
+?>

+ 349 - 0
plugins/filters/options.php

@@ -0,0 +1,349 @@
+<?php
+   /*
+    *  Message and Spam Filter Plugin 
+    *  By Luke Ehresman <luke@squirrelmail.org>
+    *     Tyler Akins
+    *     Brent Bice
+    *  (c) 2000 (GNU GPL - see ../../COPYING)
+    *
+    *  This plugin filters your inbox into different folders based upon given
+    *  criteria.  It is most useful for people who are subscibed to mailing lists
+    *  to help organize their messages.  The argument stands that filtering is
+    *  not the place of the client, which is why this has been made a plugin for
+    *  SquirrelMail.  You may be better off using products such as Sieve or
+    *  Procmail to do your filtering so it happens even when SquirrelMail isn't
+    *  running.
+    *
+    *  If you need help with this, or see improvements that can be made, please
+    *  email me directly at the address above.  I definately welcome suggestions
+    *  and comments.  This plugin, as is the case with all SquirrelMail plugins,
+    *  is not directly supported by the developers.  Please come to me off the
+    *  mailing list if you have trouble with it.
+    *
+    *  Also view plugins/README.plugins for more information.
+    *
+    */
+   chdir ("..");
+   require_once('../src/validate.php');
+   require_once ("../functions/page_header.php");
+   require_once ("../functions/imap.php");
+   require_once ("../src/load_prefs.php");
+
+   global $AllowSpamFilters;
+
+   displayPageHeader($color, "None");   
+
+   if (isset($filter_submit)) {
+      if (!isset($theid)) $theid = 0;
+      $filter_what = ereg_replace(",", " ", $filter_what);
+      $filter_what = str_replace("\\\\", "\\", $filter_what);
+      $filter_what = str_replace("\\\"", "\"", $filter_what);
+      $filter_what = str_replace("\"", "&quot;", $filter_what);
+
+      setPref($data_dir, $username, "filter".$theid, $filter_where.",".$filter_what.",".$filter_folder);
+      $filters[$theid]["where"] = $filter_where;
+      $filters[$theid]["what"] = $filter_what;
+      $filters[$theid]["folder"] = $filter_folder;
+   } elseif (isset($spam_submit) && $AllowSpamFilters) {
+      $spam_filters = load_spam_filters();
+      setPref($data_dir, $username, 'filters_spam_folder', $filters_spam_folder_set);
+      setPref($data_dir, $username, 'filters_spam_scan', $filters_spam_scan_set);
+      foreach ($spam_filters as $Key => $Value)
+      {
+          $input = $spam_filters[$Key]['prefname'] . '_set';
+          setPref($data_dir, $username, $spam_filters[$Key]['prefname'],
+              $$input);
+      }
+   } elseif (isset($action) && $action == "delete") {
+      remove_filter($theid);
+   } elseif (isset($action) && $action == "move_up") {
+      filter_swap($theid, $theid - 1);
+   } elseif (isset($action) && $action == "move_down") {
+      filter_swap($theid, $theid + 1);
+   }
+
+   if ($AllowSpamFilters) {
+      $filters_spam_folder = getPref($data_dir, $username, 'filters_spam_folder');
+      $filters_spam_scan = getPref($data_dir, $username, 'filters_spam_scan');
+   }
+   $filters = load_filters();
+
+   ?>
+      <br>
+      <table width=95% align=center border=0 cellpadding=2 cellspacing=0><tr><td bgcolor="<?php echo $color[0] ?>">
+         <center><b><?php echo _("Options") ?> - Message Filtering</b></center>
+      </td></tr></table>
+      <br><center>[<a href="options.php?action=add">New</a>] - [<a href="../../src/options.php">Done</a>]</center><br>
+      <table border=0 cellpadding=3 cellspacing=0 align=center>
+         <?php
+            for ($i=0; $i < count($filters); $i++) {
+               if ($i % 2 == 0) $clr = $color[0];
+               else $clr = $color[9];
+
+               $fdr = ($folder_prefix)?str_replace($folder_prefix, "", $filters[$i]["folder"]):$filters[$i]["folder"];
+
+?>
+<tr bgcolor="<?PHP echo $clr ?>"><td><small>
+[<a href="options.php?theid=<?PHP echo $i ?>&action=edit">Edit</a>]
+</small></td><td><small>
+[<a href="options.php?theid=<?PHP echo $i ?>&action=delete">Delete</a>]
+</small></td><td align=center><small>
+[<?PHP if (isset($filters[$i + 1])) {
+?><a href="options.php?theid=<?PHP echo $i ?>&action=move_down">Down</a><?PHP 
+if ($i > 0) echo ' | ';
+}
+if ($i > 0) {
+?><a href="options.php?theid=<?PHP echo $i ?>&action=move_up">Up</a><?PHP 
+} ?>]</small></td><td>
+- If <b><?PHP echo $filters[$i]['where'] ?></b> contains <b><?PHP
+echo $filters[$i]['what'] ?></b> then move to <b><?PHP echo $fdr ?></b>
+</td></tr>
+<?PHP
+
+            }
+         ?>
+      </table>
+      
+      <table width=80% align=center border=0 cellpadding=2 cellspacing=0">
+        <tr><td>&nbsp</td></tr>
+      </table>
+      
+      <?PHP if ($AllowSpamFilters) { ?>
+      
+      <table width=95% align=center border=0 cellpadding=2 cellspacing=0 bgcolor="<?php echo $color[0] ?>">
+        <tr><th align=center>Spam Filtering</th></tr>
+      </table>
+      <?PHP if (! isset($action) || $action != 'spam') { ?>
+      <p align=center>[<a href="options.php?action=spam">Edit</a>]<br>
+      Spam is sent to <b><?PHP 
+         if ($filters_spam_folder) 
+         {
+            echo $filters_spam_folder;
+         }
+         else
+         {
+            echo '[<i>not set yet</i>]';
+         }
+      ?></b><br>Spam scan is limited to <b><?PHP
+         if ($filters_spam_scan == 'new')
+         {
+            echo 'New Messages Only';
+         }
+         else
+         {
+            echo 'All Messages';
+         }
+      ?></b></p>
+      
+      <table border=0 cellpadding=3 cellspacing=0 align=center bgcolor="<?PHP echo $color[0] ?>">
+        <?PHP
+        
+          $spam_filters = load_spam_filters();
+          
+          foreach ($spam_filters as $Key => $Value)
+          {
+              echo '<tr><th align=center>';
+              
+              if ($spam_filters[$Key]['enabled'])
+              {
+                  echo 'ON';
+              }
+              else
+              {
+                  echo 'OFF';
+              }
+              
+              echo '</th><td>&nbsp;-&nbsp;</td><td>';
+              
+              if ($spam_filters[$Key]['link'])
+              {
+                  echo '<a href="';
+                  echo $spam_filters[$Key]['link'];
+                  echo '" target="_blank">';
+              }
+              
+              echo $spam_filters[$Key]['name'];
+              if ($spam_filters[$Key]['link'])
+              {
+                  echo '</a>';
+              }
+              echo "</td></tr>\n";
+          }
+          
+        ?>
+      </table>
+   <?php
+         }
+      }
+      
+      if (isset($action) && ($action == "add" || $action == "edit")) {
+         $imapConnection = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0);
+         $boxes = sqimap_mailbox_list($imapConnection);
+         sqimap_logout($imapConnection);
+         if (!isset($theid))
+            $theid = count($filters);
+
+         ?>
+            <center>
+            <form action="options.php" method=post>
+            <br><table cellpadding=2 cellspacing=0 border=0>
+               <tr>
+                  <td>
+                    &nbsp;
+                  </td>
+                  <td>
+                     <select name=filter_where>
+                        <?php
+			   if (! isset($filters[$theid]['where'])) $L = false;
+			   else $L = true;
+                           if ($L && $filters[$theid]["where"] == "From") echo "<option value=\"From\" selected> From\n";
+                           else                                     echo "<option value=\"From\"> From\n";
+
+                           if ($L && $filters[$theid]["where"] == "To")   echo "<option value=\"To\" selected> To\n";
+                           else                                     echo "<option value=\"To\"> To\n";
+
+                           if ($L && $filters[$theid]["where"] == "Cc")   echo "<option value=\"Cc\" selected> Cc\n";
+                           else                                     echo "<option value=\"Cc\"> Cc\n";
+
+                           if ($L && $filters[$theid]["where"] == "To or Cc")   echo "<option value=\"To or Cc\" selected> To or Cc\n";
+                           else                                     echo "<option value=\"To or Cc\"> To or Cc\n";
+
+                           if ($L && $filters[$theid]["where"] == "Subject")   echo "<option value=\"Subject\" selected> Subject\n";
+                           else                                     echo "<option value=\"Subject\"> Subject\n";
+                        ?>
+                     </select>
+                  </td>
+               </tr>
+               <tr>
+                  <td align=right>
+                     Contains:
+                  </td>
+                  <td>
+                     <input type=text size=32 name=filter_what value="<?php
+if (isset($filters[$theid]['what'])) echo $filters[$theid]["what"]; ?>">
+                  </td>
+               </tr>
+               <tr>
+                  <td>
+                     Move to:
+                  </td>
+                  <td>
+                     <tt>
+                     <select name=filter_folder>
+      <?php
+      for ($i = 0; $i < count($boxes); $i++) {
+         if (! in_array('noselect', $boxes[$i]['flags'])) {
+            $box = $boxes[$i]["unformatted"];
+            $box2 = str_replace(' ', '&nbsp;', $boxes[$i]["formatted"]);
+            if (isset($filters[$theid]['folder']) && 
+	        $filters[$theid]["folder"] == $box)
+               echo "         <OPTION VALUE=\"$box\" SELECTED>$box2\n";
+            else
+               echo "         <OPTION VALUE=\"$box\">$box2\n";
+         }       
+      }
+      ?>
+                     </tt>
+                     </select>
+                  </td>
+               </tr>
+            </table>   
+            <input type=submit name=filter_submit value=Submit>
+            <input type=hidden name=theid value=<?php echo $theid ?>>
+            </form>
+            </center>
+         <?php
+      }
+      else if (isset($action) && $action == 'spam' && $AllowSpamFilters)
+      {
+         $imapConnection = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0);
+         $boxes = sqimap_mailbox_list($imapConnection);
+         sqimap_logout($imapConnection);
+         for ($i = 0; $i < count($boxes) && $filters_spam_folder == ''; $i++) {
+            if ($boxes[$i]["flags"][0] != "noselect" && $boxes[$i]["flags"][1] != "noselect" && $boxes[$i]["flags"][2] != "noselect") {
+               $filters_spam_folder = $boxes[$i]['unformatted'];
+            }       
+         }
+         
+         ?><form method=post action="options.php">
+         <center>
+         <table width=85% cellpadding=2 cellspacing=0 border=0>
+           <tr>
+             <th align=right nowrap>Move spam to:</th>
+             <td><select name="filters_spam_folder_set">
+         <?PHP
+            for ($i = 0; $i < count($boxes); $i++) {
+               if (! in_array('noselect', $boxes[$i]['flags'])) {
+                  $box = $boxes[$i]["unformatted"];
+                  $box2 = str_replace(' ', '&nbsp;', $boxes[$i]["formatted"]);
+                  if ($filters_spam_folder == $box)
+                     echo "<OPTION VALUE=\"$box\" SELECTED>$box2</OPTION>\n";
+                  else
+                     echo "<OPTION VALUE=\"$box\">$box2</OPTION>\n";
+               }       
+            }
+         ?>
+               </select>
+             </td>
+           </tr>
+           <tr><td></td><td>Moving spam directly to the trash may not be a good idea at first,
+             since messages from friends and mailing lists might accidentally be marked as spam.
+             Whatever folder you set this to, make sure that it gets cleaned out periodically,
+             so that you don't have an excessively large mailbox hanging around.
+             </td></tr>
+           <tr>
+             <th align=right nowrap>What to Scan:</th>
+             <td><select name="filters_spam_scan_set">
+               <option value=''<?PHP
+                   if ($filters_spam_scan == '') echo ' SELECTED';
+               ?>>All messages</option>
+               <option value='new'<?PHP
+                   if ($filters_spam_scan == 'new') echo ' SELECTED';
+               ?>>Only unread messages</option>
+             </select>
+             </td>
+           </tr>
+           <tr>
+             <td></td><td>The more messages you scan, the longer it takes.  I would suggest
+             that you scan only new messages.  If you make a change to your filters, I
+             would set it to scan all messages, then go view my INBOX, then come back and
+             set it to scan only new messages.  That way, your new spam filters will be
+             applied and you'll scan even the spam you read with the new filters.</td>
+           </tr>
+         <?PHP
+           $spam_filters = load_spam_filters();
+           
+           foreach ($spam_filters as $Key => $Value)
+           {
+               echo "<tr><th align=right nowrap>$Key</th>\n";
+               echo '<td><input type=checkbox name="';
+               echo $spam_filters[$Key]['prefname'];
+               echo '_set"';
+               if ($spam_filters[$Key]['enabled'])
+                   echo ' CHECKED';
+               echo '> - ';
+               if ($spam_filters[$Key]['link'])
+               {
+                   echo '<a href="';
+                   echo $spam_filters[$Key]['link'];
+                   echo '" target="_blank">';
+               }
+               echo $spam_filters[$Key]['name'];
+               if ($spam_filters[$Key]['link'])
+               {
+                   echo '</a>';
+               }
+               echo '</td></tr><tr><td></td><td>';
+               echo $spam_filters[$Key]['comment'];
+               echo "</td></tr>\n";
+           }
+         ?>
+           <tr><td colspan=2 align=center><input type=submit name="spam_submit" value="Save"></td></tr>
+           </table>
+           </center>
+           </form>
+         <?PHP
+         
+         sqimap_logout($imapConnection);
+      }
+?>

+ 85 - 0
plugins/filters/setup.php

@@ -0,0 +1,85 @@
+<?php
+   /*
+    *  Message and Spam Filter Plugin 
+    *  By Luke Ehresman <luke@squirrelmail.org>
+    *     Tyler Akins
+    *     Brent Bice
+    *  (c) 2000 (GNU GPL - see ../../COPYING)
+    *
+    *  This plugin filters your inbox into different folders based upon given
+    *  criteria.  It is most useful for people who are subscibed to mailing lists
+    *  to help organize their messages.  The argument stands that filtering is
+    *  not the place of the client, which is why this has been made a plugin for
+    *  SquirrelMail.  You may be better off using products such as Sieve or
+    *  Procmail to do your filtering so it happens even when SquirrelMail isn't
+    *  running.
+    *
+    *  If you need help with this, or see improvements that can be made, please
+    *  email me directly at the address above.  I definately welcome suggestions
+    *  and comments.  This plugin, as is the case with all SquirrelMail plugins,
+    *  is not directly supported by the developers.  Please come to me off the
+    *  mailing list if you have trouble with it.
+    *
+    *  Also view plugins/README.plugins for more information.
+    *
+    */
+
+   // Set this to true if you have problems -- check the README file
+   // Note:  This doesn't work all of the time (No idea why)
+   //        Seems to be related to UW
+   global $UseSeparateImapConnection;
+   $UseSeparateImapConnection = false;
+   
+   // Set this to false if you do not want the user to be able to enable
+   // spam filters
+   global $AllowSpamFilters;
+   $AllowSpamFilters = true;
+   
+   // Set this to a string containing something unique to the line in the
+   // header you want me to find IPs to scan the databases with.  For example,
+   // All the email coming IN from the internet to my site has a line in
+   // the header that looks like (all on one line):
+   // Received: [from usw-sf-list1.sourceforge.net (usw-sf-fw2.sourceforge.net
+   //    [216.136.171.252]) by firewall.persistence.com (SYSADMIN-antispam
+   //     0.2) with
+   // Since this line indicates the FIRST hop the email takes into my network,
+   // I set my SpamFilters_YourHop to 'by firewall.persistence.com' but any
+   // case-sensitive string will do.  You can set it to something found on
+   // every line in the header (like ' ') if you want to scan all IPs in
+   // the header (lots of false alarms here tho).
+
+   global $SpamFilters_YourHop;
+   $SpamFilters_YourHop = 'by firewall.persistence.com';
+
+   // A cache of IPs we've already checked or are known bad boys or good boys
+   // ie. $SpamFilters_DNScache["210.54.220.18"] = true; 
+   // would tell filters to not even bother doing the DNS queries for that
+   // IP and any email coming from it are SPAM - false would mean that any
+   // email coming from it would NOT be SPAM
+   global $SpamFilters_DNScache;
+
+   require_once ("../plugins/filters/filters.php");
+
+   function squirrelmail_plugin_init_filters() {
+      global $squirrelmail_plugin_hooks;
+      global $mailbox, $imap_stream, $imapConnection;
+
+      $squirrelmail_plugin_hooks["left_main_before"]["filters"] = "start_filters";
+      if ($mailbox == "INBOX")
+         $squirrelmail_plugin_hooks["right_main_after_header"]["filters"] = "start_filters";
+      $squirrelmail_plugin_hooks["options_register"]["filters"] = "squirrelmail_plugin_register";
+   }
+
+   function squirrelmail_plugin_register() {
+      global $optionpages;
+
+      $optionpages[] = array(
+         'name' => 'Message Filters',
+         'url'  => '../plugins/filters/options.php',
+         'desc' => 'Filtering enables messages with different criteria to
+                    be automatically filtered into different folders for
+                    easier organization.',
+         'js'   => false
+      );
+   }
+?>

+ 85 - 0
plugins/filters/sqimap_read_data.php

@@ -0,0 +1,85 @@
+   /******************************************************************************
+    **  Reads the output from the IMAP stream.  If handle_errors is set to true,
+    **  this will also handle all errors that are received.  If it is not set,
+    **  the errors will be sent back through $response and $message
+    ******************************************************************************/
+   function sqimap_read_data ($imap_stream, $pre, $handle_errors, &$response, &$message) {
+      global $color, $squirrelmail_language, $imap_general_debug;
+
+      $data = array();
+      $size = 0;
+
+      do {
+         $read = fgets($imap_stream, 9096);
+         if (ereg("^$pre (OK|BAD|NO)(.*)$", $read, $regs)) {
+            break;  // found end of reply
+         }
+
+         // Continue if needed for this single line
+         while (strpos($read, "\n") === false) {
+            $read .= fgets($imap_stream, 9096);
+         }
+
+         $data[] = $read;
+
+         if (ereg("^\\* [0-9]+ FETCH.*\\{([0-9]+)\\}", $read, $regs)) {
+            $size = $regs[1];
+            if ($imap_general_debug) {
+               echo "<small><tt><font color=\"#CC0000\">Size is $size</font></tt></small><br>\n";
+            }
+
+            $total_size = 0;
+            do {
+               $read = fgets($imap_stream, 9096);
+               if ($imap_general_debug) {
+                  echo "<small><tt><font color=\"#CC0000\">$read</font></tt></small><br>\n";
+                  flush();
+               }
+               $data[] = $read;
+               $total_size += strlen($read);
+            } while ($total_size < $size);
+
+            $size = 0;
+         }
+         // For debugging purposes
+         if ($imap_general_debug) {
+            echo "<small><tt><font color=\"#CC0000\">$read</font></tt></small><br>\n";
+            flush();
+         }
+      } while (true);
+
+      $response = $regs[1];
+      $message = trim($regs[2]);
+
+      if ($imap_general_debug) echo '--<br>';
+
+      if ($handle_errors == false)
+          return $data;
+ 
+      if ($response == 'NO') {
+         // ignore this error from m$ exchange, it is not fatal (aka bug)
+         if (strstr($message, 'command resulted in') === false) {
+            set_up_language($squirrelmail_language);
+            echo "<br><b><font color=$color[2]>\n";
+            echo _("ERROR : Could not complete request.");
+            echo "</b><br>\n";
+            echo _("Reason Given: ");
+            echo $message . "</font><br>\n";
+            exit;
+         }
+      } else if ($response == 'BAD') {
+         set_up_language($squirrelmail_language);
+         echo "<br><b><font color=$color[2]>\n";
+         echo _("ERROR : Bad or malformed request.");
+         echo "</b><br>\n";
+         echo _("Server responded: ");
+         echo $message . "</font><br>\n";
+         exit;
+      }
+
+      return $data;
+   }
+
+
+
+

+ 19 - 0
plugins/squirrelspell/INSTALL

@@ -0,0 +1,19 @@
+SquirrelSpell-v0.3.1
+---------------------
+
+Untar SquirrelSpell into your squirrelmail/plugins directory. Move
+sqspell_config.dist to sqspell_config.php if this is a fresh install. If
+upgrading, just untar over your old installation.
+
+Modify the sqspell_config.php file making sure you have ispell or aspell
+available on your system and located in PHP's path. The squirrelspell
+doesn't check for that and if it is not available, you're just going to
+get a "No errors found"  message every time. :) Quite pleasing, but not
+very useful.
+
+Read files in "doc" directory -- they explain some features.
+
+Enable the plugin either by hand or by running the configure script from
+your squirrelmail install directory.
+
+Enjoy and report bugs. ;)

+ 30 - 0
plugins/squirrelspell/doc/CRYPTO

@@ -0,0 +1,30 @@
+CRYPTOGRAPHY SUPPORT IN SQUIRRELSPELL
+--------------------------------------
+
+Starting with version v0.3 SquirrelSpell is capable of working with encrypted
+user dictionaries. However, this option is only available when PHP
+is compiled with support for MCRYPT. This is relatively easy -- to enable
+MCRYPT support, follow instructions at:
+
+http://www.php.net/manual/en/ref.mcrypt.php
+
+NOTE: You will need libmcrypt version 2.4.x or above for SquirrelSpell
+to work.
+
+HOW IT'S DONE
+--------------
+SquirrelSpell encrypts the dictionary with the user's mailbox password, 
+thus making the encryption/decryption process transparent to the user. 
+The algorythm used for encryption is Blowfish, but you may manually override 
+it in the code if you so wish.
+
+The only shortcoming this approach has -- when mailbox password is changed, 
+SquirrelSpell asks the user to enter the old password in order to re-encrypt
+the file with the new key. If the user doesn't remember the password, then
+the file is lost, unless you want to brute-force it open.
+
+The encryption is off by default and users are warned about remembering
+their passwords before they enable encryption of their personal dictionary.
+
+I haven't tested the overhead. If anyone has any benchmarks -- you are
+welcome to share them.

+ 72 - 0
plugins/squirrelspell/doc/ChangeLog

@@ -0,0 +1,72 @@
+SQUIRRELSPELL
+
+v0.3.5
+-------
+- Making it work with 1.1.1 broke it under 1.0.6. Decided not to support
+  developmental versions after this release.
+
+v0.3.4
+-------
+- Changes to unbreak it in 1.1.1. :)
+
+v0.3.3
+-------
+- Apparently, magic quotes wasn't a bug, but something introduced in 1.0.6,
+  so I took out all magic-quotes escaping routines, since it's done
+  automatically now by validate.php.
+
+v0.3.2
+-------
+- Rolled back changes in v0.3.1
+- Workaround for an odd bug with PHP's magic_quotes_gpc
+- Changed trim to chop so the newline-trimming function doesn't trim
+  leading spaces.
+- Changed SOUP_NAZI to only deny Opera-4 versions
+- Moved SQSPELL_VERSION to sqspell_functions.php for easier
+  upgrades.
+
+v0.3.1
+-------
+Changes to make it work with 1.0.5.
+
+v0.3
+-----
+Added vlink and alink settings, plus fixed some colors.
+
+v0.3b
+------
+- Major code re-organization. 
+- Moved modules into separate directory.
+- Moved most JavaScript out of the main code into separate .js files
+- Created generic GUI-wrappers for most interface screens.
+- Added support for multiple international dictionaries.
+- Added MCRYPT support for encrypting the user dictionaries.
+- No longer checks lines starting with ">" (reply).
+- No longer checks anything past the "--" on a single line (signature).
+- SquirrelSpell options are now on the main OPTIONS page, not on the
+  personal options page.
+
+v0.2.1
+------
+Added a SoupNazi function. :)) Checks for bad browsers which are known not to
+work with SquirrelSpell due to their odd JavaScript.
+
+v0.2pl1
+-------
+Fixed the Magic Quotes problems.
+
+v0.2
+-----
+Added user dictionaries.
+
+v0.1.1
+-------
+Added support for aspell
+
+v0.1pl1
+--------
+Fixed Magic Quotes errors.
+
+version v0.1
+-------------
+Initial release.

+ 14 - 0
plugins/squirrelspell/doc/PRIVACY

@@ -0,0 +1,14 @@
+PRIVACY CONCERNS WHEN USING SQUIRRELSPELL:
+-------------------------------------------
+
+Beginning with version v0.2 SquirrelSpell saves personal dictionary on the
+server. This has a potential of a serious privacy issue, therefore you
+should configure your system to disallow web access to the directory where
+your user dictionaries are stored. By default they are stored in your
+$data_dir which you provided in your Squirrelmail config. This is the best
+option, but you should read the SquirrelMail FAQ's and Readme's on how to
+secure that directory.
+
+Also, see the CRYPTO file for instructions on how to enable encryption
+of user dictionaries. This is done in order to further enhance the
+privacy of your users.

+ 52 - 0
plugins/squirrelspell/doc/README

@@ -0,0 +1,52 @@
+SquirrelSpell
+--------------
+
+SquirrelSpell is a JavaScript-powered spellchecker written to work with
+SquirrelMail versions 0.5 and higher.
+
+LICENSE: 
+--------- 
+This is free software released under GNU GPL license and comes with no
+warranty of any kind. You may modify, borrow, or redistribute code as long
+as it doesn't violate the GNU GPL license. You can read more about this
+license at http://www.gnu.org/
+
+FEATURES:
+----------
+SquirrelSpell works with UN*X's ISPELL or ASPELL libraries and
+SquirrelMail version 0.5 and higher. No PHP recompilation required,
+unless you wish to enable MCRYPT support.
+
+* SpellChecker:
+ISPELL or ASPELL. It all depends on them. Read configuration parameters in
+sqspell_config.php. Starting with version v0.3 supports multiple international
+dictionaries.
+
+* User Dictionary:
+SquirrelSpell adds words to the user dictionary. You may edit your
+dictionary under options->personal options->Edit my dictionary.
+
+* Encryption:
+Starting with version v0.3 SquirrelSpell is capable of working with
+encrypted user dictionaries. See doc/CRYPTO for information on how to
+enable this feature.
+
+* i18n and l10n:
+SquirrelSpell supports any international dictionaries provided by ispell
+or aspell. However, since there isn't a translation interface available
+for SquirrelMail plugins, all messages produced by SquirrelSpell will be
+in English.
+
+AUTHOR:
+--------
+Konstantin Riabitsev, http://www.mricon.com/
+
+SUPPORT:
+---------
+Send suppot questions and bug reports to the plugins mailing list:
+squirrelmail-plugins@lists.sourceforge.net. When reporting a bug
+don't forget to mention your browser version, SquirrelMail and
+SquirrelSpell versions, as well as any other useful info.
+
+ENJOY. :)
+---------

+ 15 - 0
plugins/squirrelspell/doc/UPGRADING

@@ -0,0 +1,15 @@
+From version v0.2 to version v0.3
+----------------------------------
+
+The user dictionaries will be converted to v0.3 format. Once they are
+converted, you can't downgrade back to v0.2. If this scares you, backup all
+*.words files in your $data_dir somewhere safe.
+
+Files are renamed around. config.php is now sqspell_config.php. 
+
+When you are setting up SQSPELL_DEFAULT_APP in the sqspell_config, keep in 
+mind that this has to reflect whichever dictionary you used in version 0.2.
+Say, if you used "ispell -d german", you will need to specify German as 
+your SQSPELL_DEFAULT_APP so user dictionaries can be upgraded successfully.
+Otherwise wrong words will end up in a wrong dictionary.
+

+ 3 - 0
plugins/squirrelspell/doc/index.php

@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>

+ 3 - 0
plugins/squirrelspell/index.php

@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>

+ 3 - 0
plugins/squirrelspell/js/WHATISTHIS

@@ -0,0 +1,3 @@
+squirrelspell/js
+
+These are javascript files used by SquirrelSpell.

+ 221 - 0
plugins/squirrelspell/js/check_me.js

@@ -0,0 +1,221 @@
+/**
+   CHECK_ME.JS
+   ------------
+   This JavaScript app is the driving power of the SquirrelSpell's
+   main spellchecker window. Hope you have as much pain figuring
+   it out as it took to write. ;))
+   								**/
+
+var CurrentError=0;
+var CurrentLocation=0;
+
+var CurrentLine;
+var CurrentSymbol;
+var ChangesMade=false;
+
+function populateSqspellForm(){
+  // this function loads error data into the form.
+  CurrentWord=Word=misses[CurrentError];
+  WordLocations = locations[CurrentError].split(", ");
+  CurrentLoc = WordLocations[CurrentLocation];
+  if(CurrentLocation==WordLocations.length-1) {
+    CurrentLocation=0;
+  } else {
+    CurrentLocation++;
+  }
+       
+  tmp = CurrentLoc.split(":");
+  CurrentLine=parseInt(tmp[0]);
+  CurrentSymbol=parseInt(tmp[1]);
+  document.forms[0].sqspell_error.value=Word;
+  LineValue=sqspell_lines[CurrentLine];
+  StartWith=0;
+  NewLineValue="";
+  if (CurrentSymbol > 40){
+    StartWith=CurrentSymbol-40;
+    NewLineValue = "...";
+  }
+  EndWith=LineValue.length;
+  EndLine="";
+  if (EndWith > CurrentSymbol + 40){
+    EndWith=CurrentSymbol+40;
+    EndLine="...";
+  }
+  NewLineValue+=LineValue.substring(StartWith, CurrentSymbol) + "*" + Word + "*" + LineValue.substring(CurrentSymbol + Word.length, EndWith) + EndLine;
+  document.forms[0].sqspell_line_area.value=NewLineValue;
+       
+  if (suggestions[CurrentError]){
+    WordSuggestions = suggestions[CurrentError].split(", ");
+    for (i=0; i<WordSuggestions.length; i++){
+      document.forms[0].sqspell_suggestion.options[i] = new Option(WordSuggestions[i], WordSuggestions[i]);
+    }
+  } else {
+    document.forms[0].sqspell_suggestion.options[0] = new Option("No Suggestions", "_NONE");
+    document.forms[0].sqspell_oruse.value=Word;
+    document.forms[0].sqspell_oruse.focus();
+    document.forms[0].sqspell_oruse.select();
+  }
+       
+  document.forms[0].sqspell_suggestion.selectedIndex=0;
+  if (!document.forms[0].sqspell_oruse.value)
+    document.forms[0].sqspell_oruse.value=document.forms[0].sqspell_suggestion.options[document.forms[0].sqspell_suggestion.selectedIndex].value;
+  occursTimes = WordLocations.length;
+  if (CurrentLocation) occursTimes += CurrentLocation-1;
+  document.forms[0].sqspell_likethis.value=occursTimes;
+}
+
+function updateLine(lLine, lSymbol, lWord, lNewWord){
+  // This function updates the line with new word value
+  sqspell_lines[lLine] = sqspell_lines[lLine].substring(0, lSymbol) + lNewWord + sqspell_lines[lLine].substring(lSymbol+lWord.length, sqspell_lines[lLine].length);
+  if (lWord.length != lNewWord.length)
+    updateSymbol(lLine, lSymbol, lNewWord.length-lWord.length);
+  if (!ChangesMade) ChangesMade=true;
+}
+     
+function sqspellRemember(){
+  // This function adds the word to the field in the form to be later
+  // submitted and added to the user dictionary.
+  CurrentWord = misses[CurrentError] + "%";
+  document.forms[0].words.value += CurrentWord;
+  sqspellIgnoreAll();
+}
+
+     
+function sqspellChange(){
+  // Called when pressed the "Change" button
+  CurrentWord = misses[CurrentError];
+  NewWord=document.forms[0].sqspell_oruse.value;
+  updateLine(CurrentLine, CurrentSymbol, CurrentWord, NewWord);
+  proceed();
+}
+
+function sqspellChangeAll(){
+  // Called when pressed the "Change All" button
+  allLoc = locations[CurrentError].split(", ");
+  if (allLoc.length==1) {
+    // There's no need to "change all", only one occurance.
+    sqspellChange();
+    return;
+  }
+       
+  NewWord=document.forms[0].sqspell_oruse.value;
+  CurrentWord = misses[CurrentError];
+  for (z=CurrentLocation-1; z<allLoc.length; z++){
+    tmp = allLoc[z].split(":");
+    lLine = parseInt(tmp[0]);  lSymbol = parseInt(tmp[1]);
+    updateLine(lLine, lSymbol, CurrentWord, NewWord);
+    // Load it again to reflect the changes in symbol data
+    allLoc = locations[CurrentError].split(", ");
+  }
+       
+  CurrentLocation=0;
+  proceed();
+}
+
+function sqspellIgnore(){
+  // Only here for consistency. Called when pressed the "Ignore" button
+  proceed();
+}
+
+function sqspellIgnoreAll(){
+  // Called when pressed the "Ignore All" button
+  CurrentLocation=0;
+  proceed();
+}
+
+function clearSqspellForm(){
+  // Clears the options in selectbox "sqspell_suggestions"
+  for (i=0; i<document.forms[0].sqspell_suggestion.length; i++){
+    document.forms[0].sqspell_suggestion.options[i]=null;
+  }
+       
+  // Now, I've been instructed by the Netscape Developer docs to call
+  // history.go(0) to refresh the page after I've changed the options.
+  // However, that brings so many pains with it that I just decided not
+  // to do it. It works like it is in Netscape 4.x. If there are problems
+  // in earlier versions of Netscape, then oh well. I'm not THAT anxious
+  // to have it working on all browsers... ;)
+
+  document.forms[0].sqspell_oruse.value="";
+}
+
+function proceed(){
+  // Goes on to the next error if any, or finishes.
+  if (!CurrentLocation) CurrentError++;
+  if (misses[CurrentError]){
+    clearSqspellForm();
+    populateSqspellForm();
+  } else {
+    if (ChangesMade || document.forms[0].words.value){
+      if (confirm("SpellCheck complete. Commit Changes?"))
+	sqspellCommitChanges();
+	else self.close();
+    } else {
+      confirm ("No changes were made.");
+      self.close();
+    }
+  }
+}
+
+function updateSymbol(lLine, lSymbol, difference){
+  // Now, I will admit that this is not the best way to do stuff,
+  // However that's the solution I've come up with.
+  // This function updates the symbol locations after there have been
+  // word length changes in the lines. Otherwise SquirrelSpell barfs all
+  // over your message... ;)
+  //
+  // If you are wondering why I didn't use two-dimensional arrays instead,
+  // well, sometimes there will be a long line with an error close to the
+  // end of it, so the coordinates would be something like 2,98 and 
+  // some Javascript implementations will create 98 empty members of an 
+  // array just to have a filled number 98. This is too resource-wasteful 
+  // and I have decided to go with the below solution instead. It takes 
+  // a little more processing, but it saves a lot on memory.
+       
+  for (i=0; i<misses.length; i++){
+    if(locations[i].indexOf(lLine + ":") >= 0){
+      allLoc = locations[i].split(", ");
+      for (j=0; j<allLoc.length; j++){
+	if (allLoc[j].indexOf(lLine+":")==0){
+	  tmp = allLoc[j].split(":");
+	  tmp[0] = parseInt(tmp[0]); tmp[1] = parseInt(tmp[1]);
+	  if (tmp[1] > lSymbol){
+	    tmp[1] = tmp[1] + difference;
+	    allLoc[j] = tmp.join(":");
+	  }
+	}
+      }
+      locations[i] = allLoc.join(", ");
+    }
+  }
+}
+
+function sqspellCommitChanges(){
+  // Write the changes back into the compose form
+  if (navigator.appName.indexOf("Microsoft")==0){
+    // MSIE doesn't have array.shift()
+    newSubject = sqspell_lines[0];
+    newBody = "";
+    for (i=1; i<sqspell_lines.length; i++){
+      if (i!=1) newBody+="\r\n";
+      newBody += sqspell_lines[i];
+    }
+  } else {
+    newSubject = sqspell_lines.shift();
+    newBody = sqspell_lines.join("\n");
+  }
+
+  opener.document.forms[0].subject.value=newSubject;
+  opener.document.forms[0].body.value=newBody;
+       
+  // See if any words were added to the dictionary.
+  if (document.forms[0].words.value){
+    // yeppers
+    document.forms[0].sqspell_line_area.value="Now saving your personal dictionary... Please wait.";
+    // pass focus to the parent so we can do background save.
+    window.opener.focus();
+    document.forms[0].submit();
+  } else {
+     self.close();
+  }
+}

+ 17 - 0
plugins/squirrelspell/js/crypto_settings.js

@@ -0,0 +1,17 @@
+/**
+   CRYPTO_SETTINGS.JS
+   -------------------
+   Some client-side checks. Nothing fancy.
+   								**/
+
+function checkMe(){
+ if (!document.forms[0].action.checked){
+	alert ("Please make a selection first.");
+	return false;
+ }
+ if (document.forms[0].action.value=="encrypt")
+ 	cmsg="This will encrypt your personal dictionary and store it in an encrypted format. Proceed?";
+ if (document.forms[0].action.value=="decrypt")
+ 	cmsg="This will decrypt your personal dictionary and store it in a clear-text format. Proceed?";
+ return confirm(cmsg);
+}

+ 21 - 0
plugins/squirrelspell/js/decrypt_error.js

@@ -0,0 +1,21 @@
+/**
+   DECRYPT_ERROR.JS
+   -----------------
+   Some client-side form-checks. Trivial stuff.
+   								**/
+
+function AYS(){
+  if (document.forms[0].delete_words.checked && document.forms[0].old_key.value){
+    alert ("You can either delete your dictionary or type in the old password. Not both.");
+    return false;
+  }
+
+  if (!document.forms[0].delete_words.checked && !document.forms[0].old_key.value){
+    alert("First make a choice.");
+    return false;
+  }
+  if (document.forms[0].delete_words.checked)
+    return confirm("This will delete your personal dictionary file. Proceed?");
+  return true;
+}
+

+ 3 - 0
plugins/squirrelspell/js/index.php

@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>

+ 13 - 0
plugins/squirrelspell/js/init.js

@@ -0,0 +1,13 @@
+/**
+   INIT.JS
+   -------
+   Grabs the text from the SquirrelMail field and submits it to
+   the squirrelspell.
+   								**/
+function sqspell_init(flag){
+  // flag tells the function whether to automatically submit the form, or
+  // wait for user input. True submits the form, while False doesn't.
+  textToSpell = opener.document.forms[0].subject.value + "\n" + opener.document.forms[0].body.value;
+  document.forms[0].sqspell_text.value = textToSpell;
+  if (flag) document.forms[0].submit();
+}

+ 3 - 0
plugins/squirrelspell/modules/WHATISTHIS

@@ -0,0 +1,3 @@
+squirrelspell/modules
+
+This is where the loadable modules for SquirrelSpell are.

+ 250 - 0
plugins/squirrelspell/modules/check_me.mod.php

@@ -0,0 +1,250 @@
+<?php
+
+ /**
+    CHECK_ME.MOD.PHP
+    -----------------
+    This module is the main workhorse of SquirrelSpell. It submits
+    the message to the spell-checker, parses the output, and loads
+    the interface window.
+								**/
+// Declaring globals for E_ALL.
+global $sqspell_text, $SQSPELL_APP, $sqspell_use_app, $attachment_dir,
+       $username, $SQSPELL_EREG, $color; 
+
+ // Now we explode the lines for three reasons:
+ // 1) So we can ignore lines starting with ">" (reply's)
+ // 2) So we can stop processing when we get to "--" on a single line,
+ //    which means that the signature is starting
+ // 3) So we can add an extra space at the beginning of each line. This way
+ //    ispell/aspell don't treat these as command characters.
+ $sqspell_raw_lines = explode("\n", $sqspell_text);
+ for ($i=0; $i<sizeof($sqspell_raw_lines); $i++){
+   if (trim($sqspell_raw_lines[$i]) == "--") break;
+   if(substr($sqspell_raw_lines[$i], 0, 1) != ">") 
+    $sqspell_new_lines[$i] = " " . $sqspell_raw_lines[$i];
+    else $sqspell_new_lines[$i] = "";
+ }
+ $sqspell_new_text=implode("\n", $sqspell_new_lines);
+
+ // Define the command used to spellcheck the document.
+ $sqspell_command=$SQSPELL_APP[$sqspell_use_app];
+ // For the simplicity's sake we'll put all text into a file
+ // in attachment_dir directory, then cat it and pipe it to sqspell_command.
+ // There are other ways to do it, including popen(), but it's unidirectional
+ // and no fun at all.
+ // NOTE: This will probably change in future releases of squirrelspell
+ // for privacy reasons.
+ //
+ $floc = "$attachment_dir/$username" . "_sqspell_data.txt";
+ $fp=fopen($floc, "w");
+ fwrite($fp, $sqspell_new_text);
+ fclose($fp);
+ exec("cat $floc | $sqspell_command", $sqspell_output);
+ unlink($floc);
+
+ // Load the user dictionary.
+ $words=sqspell_getLang(sqspell_getWords(), $sqspell_use_app);
+ // define some variables.
+ $current_line=0;
+ $missed_words=Array();
+ $misses = Array();
+ $locations = Array();
+ $errors=0;
+ // Now we process the output of sqspell_command (ispell or aspell
+ // in ispell compatibility mode, whichever).
+ for ($i=0; $i<sizeof($sqspell_output); $i++){
+  switch (substr($sqspell_output[$i], 0, 1)){
+  case "":
+    // Ispell adds empty lines when an end of line is reached
+    $current_line++;
+    break;
+
+  case "&":
+    // This means there's a misspelled word and a few suggestions.
+    list($left, $right) = explode(": ", $sqspell_output[$i]);
+    $tmparray = explode(" ", $left);
+    $sqspell_word=$tmparray[1];
+    // Check if the word is in user dictionary.
+    if (!$SQSPELL_EREG("\n$sqspell_word\n", $words)){ 
+     $sqspell_symb=intval($tmparray[3])-1;
+     if (!$misses[$sqspell_word]) {
+	$misses[$sqspell_word] = $right;
+	$missed_words[$errors] = $sqspell_word;
+	$errors++;
+     }
+     if ($locations[$sqspell_word]) $locations[$sqspell_word] .= ", ";
+     $locations[$sqspell_word] .= "$current_line:$sqspell_symb";
+    }
+    break;
+
+  case "#":
+    // This means a misspelled word and no suggestions.
+    $tmparray = explode(" ", $sqspell_output[$i]);
+    $sqspell_word=$tmparray[1];
+    // Check if the word is in user dictionary.
+    if (!$SQSPELL_EREG("\n$sqspell_word\n", $words)){
+     $sqspell_symb=intval($tmparray[2])-1;
+     if (!$misses[$sqspell_word]) {
+	$misses[$sqspell_word] = "_NONE";
+	$missed_words[$errors] = $sqspell_word;
+	$errors++;
+     }
+     if ($locations[$sqspell_word]) $locations[$sqspell_word] .= ", ";
+     $locations[$sqspell_word] .= "$current_line:$sqspell_symb";
+    }
+    break;
+  }
+ }
+
+ if ($errors){ 
+  // So, there are errors
+  // This is the only place where the generic GUI-wrapper is not
+  // called, but generated right here. This is due to the complexity
+  // of the output.
+  ?>
+  <html>
+  <head>
+   <title>SquirrelSpell Results</title>
+   <script type="text/javascript">
+     // Load the spelling errors into JavaScript arrays
+     <!--
+     <?php
+     $sqspell_lines = explode("\n", $sqspell_text);
+     echo "// All lines of the message
+     var sqspell_lines=new Array();\n";
+     for ($i=0; $i<sizeof($sqspell_lines); $i++){
+       echo "sqspell_lines[$i] = \"" . chop(addslashes($sqspell_lines[$i])) . "\";\n";
+     }
+
+     echo "\n\n";
+     echo "// Misses are all misspelled words
+     var misses=new Array();\n";
+     for ($i=0; $i<sizeof($missed_words); $i++){
+       echo "misses[$i] = \"" . $missed_words[$i] . "\";\n";
+     }
+
+     echo "\n\n";
+     echo "// Suggestions are (guess what!) suggestions for misspellings
+     var suggestions = new Array();\n";
+     $i=0;
+     while (list($word, $value) = each($misses)){
+       if ($value=="_NONE") $value="";
+       echo "suggestions[$i] = \"$value\";\n";
+       $i++;
+     }
+
+     echo "\n\n";
+     echo "// Locations are where those misspellings are located, line:symbol
+     var locations= new Array();\n";
+     $i=0;
+     while (list($word, $value) = each($locations)){
+       echo "locations[$i] = \"$value\";\n";
+       $i++;
+     }
+     ?>
+     // Why isn't there a booger fairy?
+     //-->
+   </script>
+   <script src="js/check_me.js" type="text/javascript"></script>
+   </head>
+  <?php
+  printf('<body bgcolor="%s" text="%s" link="%s" alink="%s" vlink="%s" onload="populateSqspellForm()">', $color[4], $color[8], $color[7], $color[7], $color[7]);
+   ?>
+   <table width="100%" border="0" cellpadding="2">
+   <tr><td bgcolor="<?php echo $color[9] ?>" align="center"><b>Found <?php 
+   	echo $errors ?> errors</b></td></tr>
+   <tr><td><hr></td></tr>
+   <tr><td>
+   <form method="post">
+   <input type="hidden" name="MOD" value="forget_me_not">
+   <input type="hidden" name="words" value="">
+   <input type="hidden" name="sqspell_use_app" value="<?php echo $sqspell_use_app ?>">
+   <table border="0" width="100%">
+    <tr align="center">
+     <td colspan="4">
+      <span style="background-color:<?php echo $color[9] ?>">Line with an error:</span><br>
+      <textarea name="sqspell_line_area" cols="50" rows="3" wrap="hard" onfocus="this.blur()"></textarea>
+     </td>
+    </tr>
+    <tr valign="middle">
+     <td align="right" width="25%">
+      <span style="background-color: <?php echo $color[9] ?>">Error:</span> 
+     </td>
+     <td align="left" width="25%">
+      <input name="sqspell_error" size="10" value="" onfocus="this.blur()">
+     </td>
+     <td align="right" width="25%">
+      <span style="background-color: <?php echo $color[9] ?>">Suggestions:</span> 
+     </td>
+     <td align="left" width="25%">
+      <select name="sqspell_suggestion" onchange="if (this.options[this.selectedIndex].value != '_NONE') document.forms[0].sqspell_oruse.value=this.options[this.selectedIndex].value">
+       <option>Suggestions</option>
+      </select>
+     </td>
+    </tr>
+    <tr>
+     <td align="right">
+      <span style="background-color: <?php echo $color[9] ?>">Change to:</span> 
+     </td>
+     <td align="left">
+      <input name="sqspell_oruse" size="15" value=""
+        onfocus="if(!this.value) this.value=document.forms[0].sqspell_error.value">
+     </td>
+     <td align="right">
+      <span style="background-color: <?php echo $color[9] ?>">Occurs times:</span> 
+     </td>
+     <td align="left">
+      <input name="sqspell_likethis" size=3 value=""
+	onfocus="this.blur()">
+     </td>
+    </tr>
+   </td></tr>
+   <tr><td colspan="4"><hr></td></tr>
+    <tr>
+     <td colspan="4">
+      <table border="0" cellpadding="0" cellspacing="3" width="100%">
+       <tr align="center" bgcolor="<?php echo $color[9] ?>">
+        <td>
+         <a href="javascript:sqspellChange()"
+	  title="Change this word">Change</a>
+        </td>
+        <td>
+         <a href="javascript:sqspellChangeAll()"
+	  title="Change ALL occurances of this word">Change All</a>
+        </td>
+        <td>
+         <a href="javascript:sqspellIgnore()"
+	  title="Ignore this word">Ignore</a>
+        </td>
+        <td>
+         <a href="javascript:sqspellIgnoreAll()"
+	  title="Ignore ALL occurances of this word">Ignore All</a>
+        </td>
+	<td>
+	 <a href="javascript:sqspellRemember()" 
+	  title="Add this word to your personal dictionary">Add to Dic</a>
+	</td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+    <tr><td colspan="4"><hr></td></tr>
+    <tr>
+     <td colspan="4" align="center" bgcolor="<?php echo $color[9] ?>">
+      <input type="button" value="  Close and Commit  " onclick="if (confirm('The spellcheck is not finished. Really close and commit changes?')) sqspellCommitChanges()"> 
+      <input type="button" value="  Close and Cancel  " onclick="if (confirm('The spellcheck is not finished. Really close and discard changes?')) self.close()">
+     </td>
+    </tr>
+   </table>
+   </form>
+   </td></tr>
+  </table>
+  </body>
+  </html>
+  <?php
+ } else {
+   // AREN'T YOU SUCH A KNOW-IT-ALL!
+   $msg="<form onsubmit=\"return false\"><div align=\"center\"><input type=\"submit\" value=\"  Close  \" onclick=\"self.close()\"></div></form>";
+   sqspell_makeWindow(null, "No errors found", null, $msg);
+ }
+?>

+ 36 - 0
plugins/squirrelspell/modules/crypto.mod.php

@@ -0,0 +1,36 @@
+<?php
+
+/**
+   CRYPTO.MOD.PHP
+   --------------
+   This module handles the encryption/decryption of the user dictionary
+   if the user so chooses from the options page.
+   								**/
+// Declaring globals for E_ALL
+global $action, $SQSPELL_CRYPTO;
+switch ($action){
+ case "encrypt":
+  // Let's encrypt the file.
+  $words=sqspell_getWords();
+  // flip the flag.
+  $SQSPELL_CRYPTO=true;
+  sqspell_writeWords($words);
+  $msg="<p>Your personal dictionary has been <strong>encrypted</strong> and is now stored in an <strong>encrypted format</strong>.</p>";
+ break;
+
+ case "decrypt":
+  // Decrypt the file and save plain text.
+  $words=sqspell_getWords();
+  // flip the flag.
+  $SQSPELL_CRYPTO=false;
+  sqspell_writeWords($words);
+  $msg="<p>Your personal dictionary has been <strong>decrypted</strong> and is now stored as <strong>clear text</strong>.</p>";
+ break;
+ 
+ case "":
+  // Wait, this shouldn't happen! :)
+  $msg = "<p>No action requested.</p>";
+ break;
+}
+ sqspell_makePage("Personal Dictionary Crypto Settings", null, $msg);
+?>

+ 54 - 0
plugins/squirrelspell/modules/crypto_badkey.mod.php

@@ -0,0 +1,54 @@
+<?php
+/**
+   CRYPTO_BADKEY.MOD.PHP
+   ---------------------
+   This module tries to decrypt the user dictionary with a newly provided
+   old password, or erases the file if everything else fails. :(
+   								**/
+ // Just for fidian! :)
+ global $delete_words, $SCRIPT_NAME, $old_key;
+ if ($delete_words=="ON"){
+  // All attemts to decrypt the file were futile. Erase the bastard and
+  // hope this never happens again.
+  sqspell_deleteWords(); 
+  // See where we were called from -- pop-up window or options page
+  // and call whichever wrapper is appropriate.
+  if (strstr($SCRIPT_NAME, "sqspell_options")){
+   $msg="<p>Your personal dictionary was erased.</p>";
+   sqspell_makePage("Dictionary Erased", null, $msg);
+  } else {
+   $msg = "<p>Your personal dictionary was erased. Please close this window and
+   click \"Check Spelling\" button again to start your spellcheck over.</p>
+   <p align=\"center\"><form>
+   <input type=\"button\" value=\"  Close this Window \" onclick=\"self.close()\">
+   </form></p>";
+   sqspell_makeWindow(null, "Dictionary Erased", null, $msg);
+  }
+  exit;
+ }
+
+ if ($old_key){
+  // User provided another key to try and decrypt the dictionary.
+  // call sqspell_getWords. If this key fails, the function will
+  // handle it.
+  $words=sqspell_getWords();
+  // It worked! Pinky, you're a genius!
+  // Write it back this time encrypted with a new key.
+  sqspell_writeWords($words);
+  // See where we are and call a necessary GUI-wrapper.
+  if (strstr($SCRIPT_NAME, "sqspell_options")){
+   $msg="<p>Your personal dictionary was re-encrypted successfully. Now
+   return to the &quot;SpellChecker options&quot; menu and make your selection
+   again.</p>";
+   sqspell_makePage("Successful Re-encryption", null, $msg);
+  } else {
+   $msg = "<p>Your personal dictionary was re-encrypted successfully. Please
+   close this window and click \"Check Spelling\" button again to start your
+   spellcheck over.</p>
+   <form><p align=\"center\"><input type=\"button\" value=\"  Close this Window  \"
+   onclick=\"self.close()\"></p></form>";
+   sqspell_makeWindow(null, "Dictionary re-encrypted", null, $msg);
+  }
+  exit;
+ }
+?>

+ 57 - 0
plugins/squirrelspell/modules/edit_dic.mod.php

@@ -0,0 +1,57 @@
+<?php
+/**
+   EDIT_DIC.MOD.PHP
+   ----------------
+   This module displays the words in your dictionary for editing.
+								**/
+ // fidian, you owe me a pack of Guinness! :)
+ global $color;
+ $words=sqspell_getWords();
+ if (!$words){
+  // Agt. Smith: "You're empty."
+  // Neo: "So are you."
+  sqspell_makePage("Personal Dictionary", null, "<p>No words in your personal dictionary.</p>");
+ } else {
+  // We're loaded with booty.
+  $pre_msg = "<p>Please check any words you wish to delete from your dictionary.</p>\n";
+  $pre_msg .= "<table border=\"0\" width=\"95%\" align=\"center\">\n";
+  $langs=sqspell_getSettings($words);
+  for ($i=0; $i<sizeof($langs); $i++){
+   $lang_words = sqspell_getLang($words, $langs[$i]);
+   if ($lang_words){
+    // No words in this dictionary.
+    if (!$msg) $msg = $pre_msg;
+    $msg .= "<tr bgcolor=\"$color[0]\" align=\"center\"><th>$langs[$i] dictionary</th></tr>
+    <tr><td align=\"center\"> 
+     <form method=\"post\">
+     <input type=\"hidden\" name=\"MOD\" value=\"forget_me\">
+     <input type=\"hidden\" name=\"sqspell_use_app\" value=\"$langs[$i]\">
+     <table border=\"0\" width=\"95%\" align=\"center\">
+      <tr>
+       <td valign=\"top\">\n";
+        $words_ary=explode("\n", $lang_words);
+        array_pop($words_ary);
+        array_shift($words_ary);
+	// Do some fancy stuff to separate the words into three columns.
+        for ($j=0; $j<sizeof($words_ary); $j++){
+         if ($j==intval(sizeof($words_ary)/3) || $j==intval(sizeof($words_ary)/3*2))
+   		$msg .= "</td><td valign=\"top\">\n";
+         $msg .= "<input type=\"checkbox\" name=\"words_ary[]\" value=\"$words_ary[$j]\"> $words_ary[$j]<br>";
+        }
+       $msg .= "</td>
+      </tr>
+     </table>
+    </td></tr>
+    <tr bgcolor=\"$color[0]\" align=\"center\"><td>
+     <input type=\"submit\" value=\"Delete checked words\"></form>
+    </td></tr><tr><td><hr>
+    </td></tr>\n";
+   }
+  }
+  // Check if all dictionaries were empty.
+  if (!$msg)
+   $msg = "<p>No words in your dictionary.</p>";
+   else $msg .= "</table>";
+  sqspell_makePage("Edit your Personal Dictionary", null, $msg);
+ }
+?>

+ 53 - 0
plugins/squirrelspell/modules/enc_setup.mod.php

@@ -0,0 +1,53 @@
+<?php
+/**
+   ENC_SETUP.MOD.PHP
+   -----------------
+   This module shows the user a nice invitation to encrypt or decypt
+   his/her personal dictionary and explains the caveats of such a decision.
+   								**/
+// Something for our friends with E_ALL for error_reporting:
+global $SQSPELL_CRYPTO; 
+
+$words=sqspell_getWords();
+if ($SQSPELL_CRYPTO){
+ // Current format is encrypted.
+ $msg = "<p>Your personal dictionary is <strong>currently encrypted</strong>. This 
+ helps protect your privacy in case the web-mail system gets compromized and your 
+ personal dictionary ends up stolen. It is currently encrypted with the password
+ you use to access your mailbox, making it hard for anyone to see what is stored
+ in your personal dictionary.</p>
+ <p><strong>ATTENTION:</strong> If you forget your password, your personal dictionary
+ will become unaccessible, since it can no longer be decrypted.
+ If you change your mailbox password, SquirrelSpell will recognize it and prompt you for
+ your old password in order to re-encrypt the dictionary with a new key.</p>
+ <form method=\"post\" onsubmit=\"return checkMe()\">
+  <input type=\"hidden\" name=\"MOD\" value=\"crypto\">
+  <p align=\"center\"><input type=\"checkbox\" name=\"action\" value=\"decrypt\"> Please decrypt my personal
+  dictionary and store it in a clear-text format.</p>
+  <p align=\"center\"><input type=\"submit\" value=\" Change crypto settings \"></p>
+ </form>
+ ";
+} else {
+ // current format is clear text.
+ $msg = "<p>Your personal dictionary is <strong>currently not encrypted</strong>.
+ You may wish to encrypt your personal dictionary to protect your privacy in case
+ the webmail system gets compromized and your personal dictionary file gets stolen.
+ When encrypted, the file's contents look garbled and are hard to decrypt without
+ knowing the correct key (which is your mailbox password).</p>
+ <strong>ATTENTION:</strong> If you decide to encrypt your personal dictionary,
+ you must remember that it gets &quot;hashed&quot; with your mailbox password. If
+ you forget your mailbox password and the administrator changes it to a new value,
+ your personal dictionary will become useless and will have to be created anew.
+ However, if you or your system administrator change your mailbox password but you
+ still have the old password at hand, you will be able to enter the old key to
+ re-encrypt the dictionary with the new value.</p>
+ <form method=\"post\" onsubmit=\"return checkMe()\">
+  <input type=\"hidden\" name=\"MOD\" value=\"crypto\">
+  <p align=\"center\"><input type=\"checkbox\" name=\"action\" value=\"encrypt\"> Please encrypt my personal
+  dictionary and store it in an encrypted format.</p>
+  <p align=\"center\"><input type=\"submit\" value=\" Change crypto settings \"></p>
+ </form>
+ ";
+}
+ sqspell_makePage("Personal Dictionary Crypto Settings", "crypto_settings.js", $msg);
+?>

+ 44 - 0
plugins/squirrelspell/modules/forget_me.mod.php

@@ -0,0 +1,44 @@
+<?php
+/**
+   FORGET_ME.MOD.PHP
+   ------------------
+   This module deletes the words from the user dictionary. Called
+   after EDIT_DIC module.
+   								**/
+ // Make it two packs of Guinness and a bag of pistachios, fidian. :)
+ global $words_ary, $sqspell_use_app, $SQSPELL_VERSION;
+ if (sizeof($words_ary)){
+  // something needs to be deleted.
+  $words=sqspell_getWords();
+  $lang_words = sqspell_getLang($words, $sqspell_use_app);
+  $msg = "<p>Deleting the following entries from <strong>$sqspell_use_app</strong> dictionary:</p>
+  <ul>\n";
+  for ($i=0; $i<sizeof($words_ary); $i++){
+    // remove word by word...
+    $lang_words=str_replace("$words_ary[$i]\n", "", $lang_words);
+    $msg .= "<li>$words_ary[$i]</li>\n";
+  }
+  $new_words_ary=split("\n", $lang_words);
+  // Wipe this lang, if only 2 members in array (no words left).
+  if (sizeof($new_words_ary)<=2) $lang_words="";
+  $new_lang_words = $lang_words;
+  // process the stuff and write the dic back.
+  $langs=sqspell_getSettings($words);
+  $words_dic = "# SquirrelSpell User Dictionary $SQSPELL_VERSION\n# Last Revision: " . date("Y-m-d") . "\n# LANG: " . join(", ", $langs) . "\n";
+  for ($i=0; $i<sizeof($langs); $i++){
+   if ($langs[$i]==$sqspell_use_app)
+     $lang_words = $new_lang_words;
+     else $lang_words = sqspell_getLang($words, $langs[$i]);
+   if ($lang_words) $words_dic .= $lang_words;
+  }
+  $words_dic .= "# End\n";
+  sqspell_writeWords($words_dic);
+  $msg .= "</ul>
+  <p>All done!</p>\n";
+  sqspell_makePage("Personal Dictionary Updated", null, $msg);
+ } else {
+  // Click on some words first, Einstein!
+  sqspell_makePage("Personal Dictionary", null, "<p>No changes requested.</p>");
+ }
+?>
+

+ 43 - 0
plugins/squirrelspell/modules/forget_me_not.mod.php

@@ -0,0 +1,43 @@
+<?php 
+/**
+   FORGET_ME_NOT.MOD.PHP
+   ----------------------
+   This module saves the added words into the user dictionary. Called
+   after CHECK_ME module.
+   								**/
+ // For our friends with E_ALL.
+ global $words, $SQSPELL_VERSION, $SQSPELL_APP_DEFFAULT, $sqspell_use_app;
+ 
+ $new_words = ereg_replace("%", "\n", $words);
+ 
+ // Load the user dictionary.
+ $words=sqspell_getWords();
+ 
+ if (!$words){
+  // First time.
+  $words_dic="# SquirrelSpell User Dictionary $SQSPELL_VERSION\n# Last Revision: " . date("Y-m-d") . "\n# LANG: $SQSPELL_APP_DEFAULT\n# $SQSPELL_APP_DEFAULT\n";
+  $words_dic .= $new_words . "# End\n";
+ } else {
+  // Do some fancy stuff in order to save the dictionary and not mangle the
+  // rest.
+  $langs=sqspell_getSettings($words);
+  $words_dic = "# SquirrelSpell User Dictionary $SQSPELL_VERSION\n# Last Revision: " . date("Y-m-d") . "\n# LANG: " . join(", ", $langs) . "\n";
+  for ($i=0; $i<sizeof($langs); $i++){
+   $lang_words=sqspell_getLang($words, $langs[$i]);
+   if ($langs[$i]==$sqspell_use_app){
+    if (!$lang_words) $lang_words="# $langs[$i]\n";
+    $lang_words .= $new_words;
+   }
+   $words_dic .= $lang_words;
+  }
+  $words_dic .= "# End\n";
+ }
+ 
+ // Write out the file
+ sqspell_writeWords($words_dic);
+ // display the splash screen, then close it automatically after 2 sec.
+ $onload="setTimeout('self.close()', 2000)";
+ $msg="<form onsubmit=\"return false\"><div align=\"center\"><input type=\"submit\" value=\"  Close  \" onclick=\"self.close()\"></div></form>";
+ sqspell_makeWindow($onload, "Personal Dictionary Updated", null, $msg);
+?>
+

+ 3 - 0
plugins/squirrelspell/modules/index.php

@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>

+ 44 - 0
plugins/squirrelspell/modules/init.mod.php

@@ -0,0 +1,44 @@
+<?php
+/**
+   INIT.MOD.PHP
+   -------------
+   Initial loading of the popup window interface.
+   								**/
+
+ // See if we need to give user the option of choosing which dictionary 
+ // he wants to use to spellcheck his message.
+ $langs=sqspell_getSettings(null);
+ $msg="<form method=\"post\">
+   <input type=\"hidden\" name=\"MOD\" value=\"check_me\">
+   <input type=\"hidden\" name=\"sqspell_text\">
+   <p align=\"center\">";
+ if (sizeof($langs)==1){ 
+  // only one dictionary defined by the users. Submit the form
+  // automatically.
+  $onload="sqspell_init(true)";
+  $msg .= "Please wait, communicating with the server...</p>
+     <input type=\"hidden\" name=\"sqspell_use_app\" value=\"$langs[0]\">
+  ";
+ } else {
+  // more than one dictionary. Let the user choose the dictionary first
+  // then manually submit the form.
+  $onload="sqspell_init(false)";
+  $msg .= "Please choose which dictionary you would like to use to spellcheck this
+     message:</p>
+     <p align=\"center\">
+      <select name=\"sqspell_use_app\">
+  ";
+  for ($i=0; $i<sizeof($langs); $i++){
+   $msg .= "<option";
+   if (!$i) $msg .= " selected";
+   $msg .= ">$langs[$i]</option>\n";
+  }
+   
+  $msg .= " </select>
+      <input type=\"submit\" value=\"Go\">
+     </p>
+  ";
+ }
+ $msg .="</form>\n";
+ sqspell_makeWindow($onload, "SquirrelSpell Initiating", "init.js", $msg);
+?> 

+ 53 - 0
plugins/squirrelspell/modules/lang_change.mod.php

@@ -0,0 +1,53 @@
+<?php
+/**
+   LANG_CHANGE.MOD.PHP
+   --------------------
+   This module changes the international dictionaries selection
+   for the user. Called after LANG_SETUP module.
+   								**/
+ // For poor wretched souls with E_ALL.
+ global $use_langs, $lang_default, $SQSPELL_APP_DEFAULT;
+ 
+ $words = sqspell_getWords();
+ if (!$words) $words = sqspell_makeDummy();
+ $langs = sqspell_getSettings($words);
+ if (sizeof($use_langs)){
+  // See if the user clicked any options on the previous page.
+  if (sizeof($use_langs)>1){
+   // See if s/he wants more than one dictionary.
+   if ($use_langs[0]!=$lang_default){
+    // See if we need to juggle the order of the dictionaries
+    // to make the default dictionary first in line.
+    if (in_array($lang_default, $use_langs)){
+     // see if the user was dumb and chose a default dictionary
+     // to be something other than the ones he selected.
+     $hold = array_shift($use_langs);
+     $lang_string = join(", ", $use_langs);
+     $lang_string = str_replace("$lang_default", "$hold", $lang_string);
+     $lang_string = $lang_default . ", " . $lang_string;
+    } else {
+     // Yes, he is dumb.
+     $lang_string = join(", ", $use_langs);
+    }
+   } else {
+    // No need to juggle the order -- preferred is already first.
+    $lang_string = join(", ", $use_langs);
+   }
+  } else {
+   // Just one dictionary, please.
+   $lang_string = $use_langs[0];
+  }
+  $msg = "<p>Settings adjusted to: <strong>$lang_string</strong> with 
+  <strong>$lang_default</strong> as default dictionary.</p>";
+ } else {
+  // No dictionaries selected. Use system default.
+  $msg = "<p>Using <strong>$SQSPELL_APP_DEFAULT</strong> dictionary (system default)
+  for spellcheck.</p>";
+  $lang_string = $SQSPELL_APP_DEFAULT;
+ }
+ $old_lang_string = join(", ", $langs);
+ $words = str_replace("# LANG: $old_lang_string", "# LANG: $lang_string", $words);
+ // write it down where the sun don't shine.
+ sqspell_writeWords($words);
+ sqspell_makePage("International Dictionaries Preferences Updated", null, $msg);
+?>

+ 30 - 0
plugins/squirrelspell/modules/lang_setup.mod.php

@@ -0,0 +1,30 @@
+<?php
+/**
+   LANG_SETUP.MOD.PHP
+   ------------------
+   This module displays available dictionaries to the user and lets
+   him/her choose which ones s/he wants to check messages with.
+   								**/
+ // Making sure Sqspell doesn't barf when working with E_ALL
+ global $SQSPELL_APP;
+ 
+ $msg = "<p>Please check any available international dictionaries which you would like 
+  to use when spellchecking:</p>
+  <form method=\"post\">
+  <input type=\"hidden\" name=\"MOD\" value=\"lang_change\">
+  <blockquote><p>
+ ";
+ $langs = sqspell_getSettings(null);
+ $add = "<p>Make this dictionary my default selection: <select name=\"lang_default\">\n";
+ while (list($avail_lang, $junk) = each($SQSPELL_APP)){
+  $msg .= "<input type=\"checkbox\" name=\"use_langs[]\" value=\"$avail_lang\"";
+  if (in_array($avail_lang, $langs)) $msg .= " checked";
+  $msg .= ">$avail_lang<br>\n";
+  $add .= "<option";
+  if ($avail_lang==$langs[0]) $add .= " selected";
+  $add .= ">$avail_lang</option>\n";
+ }
+ $msg .= "</p>\n" . $add . "</select>\n";
+ $msg .= "</p></blockquote><p><input type=\"submit\" value=\" Make these changes \"></p>";
+ sqspell_makePage("Add International Dictionaries", null, $msg); 
+?>

+ 27 - 0
plugins/squirrelspell/modules/options_main.mod.php

@@ -0,0 +1,27 @@
+<?php
+/**
+   OPTIONS_MAIN.MOD.PHP
+   ---------------------
+   Default page called when accessing SquirrelSpell's options.
+   								**/
+ // E_ALL: protection behind 3000 miles.
+ global $SQSPELL_APP;
+
+ $msg = "<p>Please choose which options you wish to set up:</p>
+ <ul>
+  <li><a href=\"sqspell_options.php?MOD=edit_dic\">Edit your personal dictionary</a></li>
+ ";
+ // See if more than one dictionary is defined system-wide.
+ // If so, let the user choose his preferred ones.
+ if (sizeof($SQSPELL_APP)>1)
+ 	$msg .= "<li><a href=\"sqspell_options.php?MOD=lang_setup\">Set up international dictionaries</a></li>\n";
+ // See if MCRYPT is available.
+ // If so, let the user choose whether s/he wants to encrypt the
+ // personal dictionary file.
+ if (function_exists("mcrypt_generic"))
+ 	$msg .= "<li><a href=\"sqspell_options.php?MOD=enc_setup\">Encrypt or decrypt your personal dictionary</a></li>\n";
+	else $msg .= "<li>Encrypt or decrypt your personal dictionary <em>(not available)</em></li>\n";
+ $msg .= "</ul>\n";
+ sqspell_makePage("SquirrelSpell Options Menu", null, $msg);
+
+?>

+ 73 - 0
plugins/squirrelspell/setup.php

@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * SETUP.PHP
+ * ---------
+ * This is a standard Squirrelmail-1.2 API for plugins.
+ */
+
+/**
+ * This function checks whether the user's USER_AGENT is known to
+ * be broken. If so, returns true and the plugin is invisible to the
+ * offending browser.
+ */
+function soupNazi(){
+    global $HTTP_USER_AGENT;
+    require ('../plugins/squirrelspell/sqspell_config.php');
+    $soup_nazi = false;
+
+    $soup_menu = explode(',', $SQSPELL_SOUP_NAZI);
+    for ($i = 0; $i < sizeof($soup_menu); $i++) {
+        if (stristr($HTTP_USER_AGENT, trim($soup_menu[$i]))) {
+            $soup_nazi=true;
+        }
+    }
+    return $soup_nazi;
+}
+
+function squirrelmail_plugin_init_squirrelspell() {
+    /* Standard initialization API. */
+    global $squirrelmail_plugin_hooks;
+
+    $squirrelmail_plugin_hooks["compose_button_row"]["squirrelspell"] = "squirrelspell_setup";
+    $squirrelmail_plugin_hooks["options_register"]["squirrelspell"] = "squirrelspell_options";
+    $squirrelmail_plugin_hooks["options_link_and_description"]["squirrelspell"] = "squirrelspell_options";
+}
+
+function squirrelspell_options() {
+   // Gets added to the user's OPTIONS page.
+   global $optionpages;
+
+   if (soupNazi()) {
+       return;
+   }
+
+   /* Register Squirrelspell with the $optionpages array. */
+   $optionpages[] = array(
+       'name' => 'SpellChecker Options',
+       'url'  => '../plugins/squirrelspell/sqspell_options.php',
+       'desc' => 'Here you may set up how your personal dictionary is stored,
+                  edit it, or choose which languages should be available to
+                  you when spell-checking.',
+       'js'   => true
+    );
+}
+
+function squirrelspell_setup() {
+   /* Gets added to the COMPOSE buttons row. */
+   if (soupNazi()) {
+       return;
+   }
+
+?>
+    <script type="text/javascript">
+    <!--
+        // using document.write to hide this functionality from people
+        // with JavaScript turned off.
+        document.write("<input type=\"button\" value=\"Check Spelling\" onclick=\"window.open('../plugins/squirrelspell/sqspell_interface.php', 'sqspell', 'status=yes,width=550,height=370,resizable=yes')\">");
+    //-->
+    </script>
+<?php
+}
+
+?>

+ 88 - 0
plugins/squirrelspell/sqspell_config.dist

@@ -0,0 +1,88 @@
+<?php
+/** SquirrelSpell Configuration file. **/
+
+// Just for poor wretched souls with E_ALL. :)
+global $username, $data_dir;
+
+/** 
+   SPELL-CHECKING APPLICATIONS:
+   ----------------------------
+   This feature was added/changed in 0.3. Use this array to set up
+   which dictionaries are available to users. If you only have 
+   English spellchecker on your system, then let this line be:
+
+   $SQSPELL_APP = array("English" => "ispell -a");
+
+   or
+
+   $SQSPELL_APP = array("English" => "/usr/local/bin/aspell -a");
+
+   Sometimes you have to specify full path for PHP to find it.
+   Aspell is a better spell-checker than Ispell, so you're encouraged
+   to use it.
+
+   If you want to have more than one dictionary available to users,
+   configure the array to look something like this:
+
+   $SQSPELL_APP = array(
+			"English" => "aspell -a",
+                        "Russian" => "ispell -d russian -a",
+			...
+			"Swahili" => "ispell -d swahili -a"
+		       );
+   
+   Watch the commas, making sure there isn't one after your last
+   dictionary declaration. Also, make sure all these dictionaries
+   are available on your system before you specify them here.
+   
+   Whatever your setting is, don't omit the "-a" flag.
+
+								**/
+$SQSPELL_APP = array("English" => "ispell -a");
+
+/**
+   DEFAULT DICTIONARY
+   -------------------
+   Even if you're only running one dictionary, still specify which one 
+   is the default. Watch the case -- it has to be exactly as in array
+   you declared in $SQSPELL_APP.	
+   								**/
+$SQSPELL_APP_DEFAULT="English";
+
+/**
+   USER DICTIONARY:
+   -----------------
+   $SQSPELL_WORDS_FILE is a location and mask of a user dictionary file. 
+   The default setting should be OK for most everyone. Read PRIVACY and
+   CRYPTO in the "doc" directory.
+								**/
+$SQSPELL_WORDS_FILE = "$data_dir/$username.words";
+
+/**
+   CASE SENSITIVITY:
+   ------------------
+   Use $SQSPELL_EREG="ereg" for case-sensitive matching of user 
+   dictionary, or $SQSPELL_EREG="eregi" for case-insensitive 
+   matching. It is advised to use case-sensitive matching.
+   								**/
+$SQSPELL_EREG="ereg";
+
+/**
+   SOUP NAZI (AVOIDING BAD BROWSERS)
+   -------------------------------------
+   Since some browsers choke on JavaScript, here is an option to disable the
+   browsers with known problems. All you do is add some part of an USER_AGENT 
+   string of an offending browser which you want to disable and users will not
+   know about this plugin. E.g. browsers with "Mozilla/4.61 (Macintosh, I, PPC)"
+   USER_AGENT string will get weeded out if you provide "Macintosh" in the 
+   config string.
+  
+   Mozilla/2 -- You're kidding, right?
+   Mozilla/3 -- known not to work
+   Opera -- known not to work
+   Macintosh -- Netscape 4.x on Macintosh chokes for some reason. 
+                Adding until resolved.
+								**/
+$SQSPELL_SOUP_NAZI = "Mozilla/3, Mozilla/2, Opera 4, Opera/4, Macintosh";
+
+?>

+ 88 - 0
plugins/squirrelspell/sqspell_config.php

@@ -0,0 +1,88 @@
+<?php
+/** SquirrelSpell Configuration file. **/
+
+// Just for poor wretched souls with E_ALL. :)
+global $username, $data_dir;
+
+/** 
+   SPELL-CHECKING APPLICATIONS:
+   ----------------------------
+   This feature was added/changed in 0.3. Use this array to set up
+   which dictionaries are available to users. If you only have 
+   English spellchecker on your system, then let this line be:
+
+   $SQSPELL_APP = array("English" => "ispell -a");
+
+   or
+
+   $SQSPELL_APP = array("English" => "/usr/local/bin/aspell -a");
+
+   Sometimes you have to specify full path for PHP to find it.
+   Aspell is a better spell-checker than Ispell, so you're encouraged
+   to use it.
+
+   If you want to have more than one dictionary available to users,
+   configure the array to look something like this:
+
+   $SQSPELL_APP = array(
+			"English" => "aspell -a",
+                        "Russian" => "ispell -d russian -a",
+			...
+			"Swahili" => "ispell -d swahili -a"
+		       );
+   
+   Watch the commas, making sure there isn't one after your last
+   dictionary declaration. Also, make sure all these dictionaries
+   are available on your system before you specify them here.
+   
+   Whatever your setting is, don't omit the "-a" flag.
+
+								**/
+$SQSPELL_APP = array("English" => "ispell -a");
+
+/**
+   DEFAULT DICTIONARY
+   -------------------
+   Even if you're only running one dictionary, still specify which one 
+   is the default. Watch the case -- it has to be exactly as in array
+   you declared in $SQSPELL_APP.	
+   								**/
+$SQSPELL_APP_DEFAULT="English";
+
+/**
+   USER DICTIONARY:
+   -----------------
+   $SQSPELL_WORDS_FILE is a location and mask of a user dictionary file. 
+   The default setting should be OK for most everyone. Read PRIVACY and
+   CRYPTO in the "doc" directory.
+								**/
+$SQSPELL_WORDS_FILE = "$data_dir/$username.words";
+
+/**
+   CASE SENSITIVITY:
+   ------------------
+   Use $SQSPELL_EREG="ereg" for case-sensitive matching of user 
+   dictionary, or $SQSPELL_EREG="eregi" for case-insensitive 
+   matching. It is advised to use case-sensitive matching.
+   								**/
+$SQSPELL_EREG="ereg";
+
+/**
+   SOUP NAZI (AVOIDING BAD BROWSERS)
+   -------------------------------------
+   Since some browsers choke on JavaScript, here is an option to disable the
+   browsers with known problems. All you do is add some part of an USER_AGENT 
+   string of an offending browser which you want to disable and users will not
+   know about this plugin. E.g. browsers with "Mozilla/4.61 (Macintosh, I, PPC)"
+   USER_AGENT string will get weeded out if you provide "Macintosh" in the 
+   config string.
+  
+   Mozilla/2 -- You're kidding, right?
+   Mozilla/3 -- known not to work
+   Opera -- known not to work
+   Macintosh -- Netscape 4.x on Macintosh chokes for some reason. 
+                Adding until resolved.
+								**/
+$SQSPELL_SOUP_NAZI = "Mozilla/3, Mozilla/2, Opera 4, Opera/4, Macintosh";
+
+?>

+ 311 - 0
plugins/squirrelspell/sqspell_functions.php

@@ -0,0 +1,311 @@
+<?php
+/**
+   SQSPELL_FUNCTIONS.PHP
+   --------------
+   All SquirrelSpell-wide functions are in this file.
+   								**/
+								
+function sqspell_makePage($title, $scriptsrc, $body){
+ //
+ // GUI wrap-around for the OPTIONS page.
+ //
+ global $color, $SQSPELL_VERSION, $MOD;
+ displayPageHeader($color, "None");
+ ?>
+ &nbsp;<br>
+ <?php if($scriptsrc) { ?>
+  <script type="text/javascript" src="js/<?php echo $scriptsrc ?>"></script>
+ <?php } ?>
+ <table width="95%" align="center" border="0" cellpadding="2" cellspacing="0">
+  <tr>
+   <td bgcolor="<?php echo $color[9] ?>" align="center">
+      <strong><?php echo $title ?></strong>
+   </td>
+  </tr>
+  <tr><td><hr></td></tr>
+  <tr><td>
+   <?php echo $body ?>
+  </td></tr>
+  <?php if ($MOD!="options_main"){ 
+   // Generate a nice return-to-main link.
+   ?>
+   <tr><td><hr></td></tr>
+   <tr><td align="center"><a href="sqspell_options.php">Back to &quot;SpellChecker Options&quot; page</a></td></tr>
+  <?php } ?>
+  <tr><td><hr></td></tr>
+  <tr>
+   <td bgcolor="<?php echo $color[9] ?>" align="center">
+      SquirrelSpell <?php echo $SQSPELL_VERSION ?>
+   </td>
+  </tr>
+ </table>
+ <?php
+}
+
+function sqspell_makeWindow($onload, $title, $scriptsrc, $body){
+ //
+ // GUI wrap-around for the pop-up window interface.
+ //
+ global $color, $SQSPELL_VERSION;
+ ?>
+ <html>
+  <head>
+   <title><?php echo $title ?></title>
+   <?php if ($scriptsrc){ ?>
+    <script type="text/javascript" src="js/<?php echo $scriptsrc ?>"></script>
+   <?php } ?>
+  </head>
+  <body text="<?php echo $color[8] ?>" 
+        bgcolor="<?php echo $color[4] ?>" 
+	link="<?php echo $color[7] ?>" 
+	vlink="<?php echo $color[7] ?>" 
+	alink="<?php echo $color[7] ?>"<?php
+	if ($onload) echo " onload=\"$onload\""; ?>>
+   <table width="100%" border="0" cellpadding="2">
+    <tr>
+     <td bgcolor="<?php echo $color[9] ?>" align="center">
+      <strong><?php echo $title ?></strong>
+     </td>
+    </tr>
+    <tr>
+     <td><hr></td>
+    </tr>
+    <tr>
+     <td>
+      <?php echo $body ?>
+     </td>
+    </tr>
+    <tr>
+     <td><hr></td>
+    </tr>
+    <tr>
+     <td bgcolor="<?php echo $color[9] ?>" align="center">
+      SquirrelSpell <?php echo $SQSPELL_VERSION ?>
+     </td>
+    </tr>
+   </table>
+  </body>
+ </html>
+ <?php
+}
+
+function sqspell_crypto($mode, $ckey, $input){
+ //
+ // This function does the encryption and decryption of the user
+ // dictionary. It is only available when PHP is compiled
+ // --with-mcrypt. See doc/CRYPTO for more information.
+ //
+ if (!function_exists(mcrypt_generic)) return "PANIC";
+ $td = mcrypt_module_open(MCRYPT_Blowfish, "", MCRYPT_MODE_ECB, "");
+ $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
+ mcrypt_generic_init($td, $ckey, $iv);
+ switch ($mode){
+  case "encrypt":
+   $crypto = mcrypt_generic($td, $input);
+  break;
+  case "decrypt":
+   $crypto = mdecrypt_generic($td, $input);
+   // See if it decrypted successfully. If so, it should contain
+   // the string "# SquirrelSpell".
+   if (!strstr($crypto, "# SquirrelSpell")) $crypto="PANIC";
+  break;
+ }
+ mcrypt_generic_end ($td);
+ return $crypto;
+}
+
+function sqspell_upgradeWordsFile($words_string){
+ //
+ // This function transparently upgrades the 0.2 dictionary format to 
+ // 0.3, since user-defined languages have been added in 0.3 and
+ // the new format keeps user dictionaries selection in the file.
+ //
+ global $SQSPELL_APP_DEFAULT, $SQSPELL_VERSION;
+ 
+ // Define just one dictionary for this user -- the default.
+ // If the user wants more, s/he can set them up in personal
+ // preferences. See doc/UPGRADING for more info.
+ $new_words_string=substr_replace($words_string, "# SquirrelSpell User Dictionary $SQSPELL_VERSION\n# Last Revision: " . date("Y-m-d") . "\n# LANG: $SQSPELL_APP_DEFAULT\n# $SQSPELL_APP_DEFAULT", 0, strpos($words_string, "\n")) . "# End\n";
+ sqspell_writeWords($new_words_string);
+ return $new_words_string;
+}
+
+function sqspell_getSettings($words){
+ //
+ // Right now it just returns an array with the dictionaries 
+ // available to the user for spell-checking. It will probably
+ // do more in the future, as features are added.
+ //
+ global $SQSPELL_APP, $SQSPELL_APP_DEFAULT;
+ if (sizeof($SQSPELL_APP) > 1){
+  // OK, so there are more than one dictionary option.
+  // Now load the user prefs.
+  if(!$words) $words=sqspell_getWords();
+  if ($words){
+   // find which dictionaries user wants to use
+   preg_match("/# LANG: (.*)/i", $words, $matches);
+   $langs=explode(", ", $matches[1]);
+  } else {
+   // User doesn't have a personal dictionary. Set him up with
+   // a default setting.
+   $langs[0]=$SQSPELL_APP_DEFAULT;
+  }
+ } else {
+  // There is only one dictionary defined system-wide.
+  $langs[0]=$SQSPELL_APP_DEFAULT;
+ }
+ return $langs;
+}
+
+function sqspell_getLang($words, $lang){
+ //
+ // Returns words of a specific user dictionary.
+ //
+ $start=strpos($words, "# $lang\n");
+ if (!$start) return "";
+ $end=strpos($words, "#", $start+1);
+ $lang_words = substr($words, $start, $end-$start);
+ return $lang_words;
+}
+ 
+function sqspell_getWords(){
+ //
+ // This baby operates the user dictionary. If the format is clear-text,
+ // then it just reads the file and returns it. However, if the file is
+ // encrypted, then it decrypts it, checks whether the decryption was 
+ // successful, troubleshoots if not, then returns the clear-text dictionary
+ // to the app.
+ //
+ global $SQSPELL_WORDS_FILE, $SQSPELL_CRYPTO;
+ $words="";
+ if (file_exists($SQSPELL_WORDS_FILE)){
+  // Gobble it up.
+  $fp=fopen($SQSPELL_WORDS_FILE, "r");
+  $words=fread($fp, filesize($SQSPELL_WORDS_FILE));
+  fclose($fp);
+ }
+ // Check if this is an encrypted file by looking for
+ // the string "# SquirrelSpell" in it.
+ if ($words && !strstr($words, "# SquirrelSpell")){
+  // This file is encrypted or mangled. Try to decrypt it.
+  // If fails, raise hell.
+  global $key, $onetimepad, $old_key;
+  if ($old_key) {
+   // an override in case user is trying to decrypt a dictionary
+   // with his old password
+   $clear_key=$old_key;
+  } else {
+   // get user's password (the key).
+   $clear_key = OneTimePadDecrypt($key, $onetimepad);
+  }
+  // decrypt
+  $words=sqspell_crypto("decrypt", $clear_key, $words);
+  if ($words=="PANIC"){
+   // AAAAAAAAAAAH!!!!! OK, ok, breathe!
+   // Let's hope the decryption failed because the user changed his
+   // password. Bring up the option to key in the old password
+   // or wipe the file and start over if everything else fails.
+   $msg="<p>
+    <strong>ATTENTION:</strong><br>
+    SquirrelSpell was unable to decrypt your personal dictionary. This is most likely
+    due to the fact that you have changed your mailbox password. In order to proceed,
+    you will have to supply your old password so that SquirrelSpell can decrypt your
+    personal dictionary. It will be re-encrypted with your new password after this.<br>
+    If you haven't encrypted your dictionary, then it got mangled and is no longer
+    valid. You will have to delete it and start anew. This is also true if you don't
+    remember your old password -- without it, the encrypted data is no longer 
+    accessible.</p>
+    <blockquote>
+    <form method=\"post\" onsubmit=\"return AYS()\">
+     <input type=\"hidden\" name=\"MOD\" value=\"crypto_badkey\">
+     <p><input type=\"checkbox\" name=\"delete_words\" value=\"ON\"> Delete my dictionary and start a new one<br>
+     Decrypt my dictionary with my old password: <input name=\"old_key\" size=\"10\"></p>
+    </blockquote>
+     <p align=\"center\"><input type=\"submit\" value=\"Proceed &gt;&gt;\"></p>
+    </form>
+   ";
+   // See if this happened in the pop-up window or when accessing
+   // the SpellChecker options page. 
+   global $SCRIPT_NAME;
+   if (strstr($SCRIPT_NAME, "sqspell_options"))
+   	sqspell_makePage("Error Decrypting Dictionary", "decrypt_error.js", $msg);
+   else sqspell_makeWindow(null, "Error Decrypting Dictionary", "decrypt_error.js", $msg); 
+   exit;
+  } else {
+   // OK! Phew. Set the encryption flag to true so we can later on 
+   // encrypt it again before saving to HDD.
+   $SQSPELL_CRYPTO=true;
+  }
+ } else {
+  // No encryption is used. Set $SQSPELL_CRYPTO to false, in case we have to
+  // save the dictionary later.
+  $SQSPELL_CRYPTO=false;
+ }
+ // Check if we need to upgrade the dictionary from version 0.2.x
+ if (strstr($words, "Dictionary v0.2")) $words=sqspell_upgradeWordsFile($words);
+ return $words;
+}
+
+function sqspell_writeWords($words){
+ //
+ // Writes user dictionary into the $username.words file, then changes mask
+ // to 0600. If encryption is needed -- does that, too.
+ //
+ global $SQSPELL_WORDS_FILE, $SQSPELL_CRYPTO;
+ // if $words is empty, create a template entry.
+ if (!$words) $words=sqspell_makeDummy();
+ if ($SQSPELL_CRYPTO){
+  // User wants to encrypt the file. So be it.
+  // get his password to use as a key.
+  global $key, $onetimepad;
+  $clear_key=OneTimePadDecrypt($key, $onetimepad);
+  // Try encrypting it. If fails, scream bloody hell.
+  $save_words = sqspell_crypto("encrypt", $clear_key, $words);
+  if ($save_words=="PANIC"){
+   // AAAAAAAAH! I'm not handling this yet, since obviously
+   // the admin of the site forgot to compile the MCRYPT support in.
+   // I will add a handler for this case later, when I can come up
+   // with some work-around... Right now, do nothing. Let the Admin's
+   // head hurt.. ;)))
+  }
+ } else {
+  $save_words = $words;
+ }
+ $fp=fopen($SQSPELL_WORDS_FILE, "w");
+ fwrite($fp, $save_words);
+ fclose($fp);
+ chmod($SQSPELL_WORDS_FILE, 0600);
+}
+
+function sqspell_deleteWords(){
+ //
+ // so I open the door to my enemies,
+ // and I ask can we wipe the slate clean,
+ // but they tell me to please go...
+ // uhm... Well, this just erases the user dictionary file.
+ //
+ global $SQSPELL_WORDS_FILE;
+ if (file_exists($SQSPELL_WORDS_FILE)) unlink($SQSPELL_WORDS_FILE);
+}
+
+function sqspell_makeDummy(){
+ //
+ // Creates an empty user dictionary for the sake of saving prefs or
+ // whatever.
+ //
+ global $SQSPELL_VERSION, $SQSPELL_APP_DEFAULT;
+ $words="# SquirrelSpell User Dictionary $SQSPELL_VERSION\n# Last Revision: " . date("Y-m-d") . "\n# LANG: $SQSPELL_APP_DEFAULT\n# End\n"; 
+ return $words;
+}
+
+/** 
+   VERSION:
+   ---------
+   SquirrelSpell version. Don't modify, since it identifies the format
+   of the user dictionary files and messing with this can do ugly 
+   stuff. :)
+   								**/
+$SQSPELL_VERSION="v0.3.5";
+
+
+?>

+ 36 - 0
plugins/squirrelspell/sqspell_interface.php

@@ -0,0 +1,36 @@
+<?php
+
+/**
+   SQSPELL_INTERFACE.PHP
+   ----------------------
+   This is a main wrapper for the pop-up window interface of
+   SquirrelSpell.
+  								**/
+	
+// Set up a couple of non-negotiable constants. Don't change these,
+// the setuppable stuff is in sqspell_config.php
+$SQSPELL_DIR="squirrelspell";
+$SQSPELL_CRYPTO=false;
+
+// Load the necessary stuff.
+chdir("..");
+include("../src/validate.php");
+include("../src/load_prefs.php");
+include ("$SQSPELL_DIR/sqspell_config.php");
+require ("$SQSPELL_DIR/sqspell_functions.php");
+
+// Now load the necessary module from the modules dir.
+//
+if (!$MOD) $MOD="init";
+
+// see if someone is attempting to be nasty by trying to get out of the
+// modules directory, although it probably wouldn't do them any good,
+// since every module has to end with .mod.php. Still, they deserve
+// to be warned. ;)
+if (strstr($MOD, ".") || strstr($MOD, "/") || strstr($MOD, "%")){ 
+	echo "SECURITY BREACH ON DECK 5! CMDR TUVOK AND SECURITY TEAM REQUESTED.";
+        exit;
+}
+// fetch the module now.
+include ("$SQSPELL_DIR/modules/$MOD.mod.php");
+?>

+ 35 - 0
plugins/squirrelspell/sqspell_options.php

@@ -0,0 +1,35 @@
+<?php
+/**
+   SQSPELL_OPTIONS.PHP
+   --------------------
+   Main wrapper for the options interface.
+   								**/
+// Set a couple of constants. Don't change these, the setuppable stuff is
+// in sqspell_config.php
+$SQSPELL_DIR="squirrelspell";
+$SQSPELL_CRYPTO=false;
+
+// Load some necessary stuff.
+chdir("..");
+include("../src/validate.php");
+include("../src/load_prefs.php");
+include("../functions/strings.php");
+include("../functions/page_header.php");
+include ("$SQSPELL_DIR/sqspell_config.php");
+require ("$SQSPELL_DIR/sqspell_functions.php");
+
+// Access the module needed
+//
+if (!$MOD) $MOD="options_main";
+
+// see if someone is attempting to be nasty by trying to get out of the
+// modules directory, although it probably wouldn't do them any good,
+// since every module has to end with .mod.php. Still, they deserve
+// to be warned. ;)
+if (strstr($MOD, ".") || strstr($MOD, "/") || strstr($MOD, "%")){
+	echo "SECURITY BREACH ON DECK 5! CMDR TUVOK AND SECURITY TEAM REQUESTED.";
+        exit;
+}
+// load the stuff already.
+include ("$SQSPELL_DIR/modules/$MOD.mod.php");
+?>

+ 16 - 0
plugins/translate/INSTALL

@@ -0,0 +1,16 @@
+Installing Plugins
+==================
+Simply untar the file in the plugins directory, and make sure it is
+in its own directory, and that the name of the directory is the name
+of the plugin.  Example below uses "plug_demo" as the name of the 
+plugin:
+
+  $ cd plugins
+  $ tar -zxvf /usr/archives/plug_demo.tar.gz
+
+Then go to your config directory and run conf.pl.  Choose option
+8 and add the plugin.  Save and exit, then that should be all
+if the plugin was made correctly.  :)
+
+  $ cd ../config
+  $ ./conf.pl

+ 73 - 0
plugins/translate/README

@@ -0,0 +1,73 @@
+Message Translation -- Version 1.4
+
+If you have ever received a mail message from someone who is not in
+your country, you probably noticed that they used a different language
+than you. If you don't know their language, a translator might help you
+out.
+
+
+Features
+========
+
+* Multiple translation servers
+* Uses your language preference for setting up default translation options
+* You pick where you see the translation box
+* You decide which translation buttons you see
+
+
+Description
+===========
+
+This plugin lets you select, on a per-user basis, the translator you want
+to use.  It defaults to None.  It has different servers you can use, and
+it lists their strengths and limits with each one.  Also, there are
+multiple servers just in case one changes their interface or goes down.
+
+Since SquirrelMail is designed to have multiple translations of the text,
+this plugin takes your preference and will use it for selecting what to
+translate the text into.
+
+
+Future Work
+===========
+
+* Grab translation directly from server (need to send POST request directly)
+* Translate your outgoing message
+
+
+Servers
+=======
+
+Babelfish = babelfish.altavista.com
+Go.com = translator.go.com
+Dictionary.com = www.dictionary.com/translate
+InterTran = tranexp.com
+GPLTrans = www.translator.cx
+
+
+Installation
+============
+
+As with other plugins, just uncompress the archive in the plugins
+directory, go back to the main directory, run configure and add the plugin.
+
+Questions/comments/flames/etc can be sent to the Squirrelmail Plugins list
+
+
+Changes
+=======
+1.3 -> 1.4
+  * Modified to use new option page hook.
+    Paul Joseph Thompson <captbunzo@squirrelmail.org>
+
+1.2 -> 1.3
+  * Stupid bugfix.  :-)
+  
+1.1 -> 1.2
+  * HTML changed to look better
+  
+1.0 -> 1.1
+  * Added more servers
+  * Added better language handling
+  * You have to pick a server, but you can choose not to have the box
+  * Added better support for direction of translation

+ 144 - 0
plugins/translate/options.php

@@ -0,0 +1,144 @@
+<?php
+   /**
+    **  options.php
+    **
+    **  Pick your translator to translate the body of incoming mail messages
+    **
+    **/
+
+   chdir("..");
+
+   session_start();
+
+   if (!isset($config_php))
+      include("../config/config.php");
+   if (!isset($strings_php))
+      include("../functions/strings.php");
+   if (!isset($page_header_php))
+      include("../functions/page_header.php");
+   if (!isset($display_messages_php))
+      include("../functions/display_messages.php");
+   if (!isset($imap_php))
+      include("../functions/imap.php");
+   if (!isset($array_php))
+      include("../functions/array.php");
+   if (!isset($i18n_php))
+      include("../functions/i18n.php");
+
+
+   include("../src/load_prefs.php");
+   displayPageHeader($color, "None");
+
+  $translate_server = getPref($data_dir, $username, "translate_server");
+  if ($translate_server == '') 
+    $translate_server = 'babelfish';
+  $translate_location = getPref($data_dir, $username, "translate_location");
+  if ($translate_location == '')
+    $translate_location = 'center';
+  $translate_show_read = getPref($data_dir, $username, 'translate_show_read');
+  $translate_show_send = getPref($data_dir, $username, 'translate_show_send');
+  $translate_same_window = getPref($data_dir, $username, 'translate_same_window');
+
+   function ShowOption($Var, $value, $Desc)
+   {
+       $Var = 'translate_' . $Var;
+       
+       global $$Var;
+       
+       echo '<option value="' . $value . '"';
+       if ($$Var == $value)
+       {
+           echo ' SELECTED';
+       }
+       echo '>' . $Desc . "</option>\n";
+   }
+       
+
+?>
+   <br>
+   <table width=95% align=center border=0 cellpadding=2 cellspacing=0><tr><td bgcolor="<?php echo $color[0] ?>">
+      <center><b><?php echo _("Options") ?> - Translator</b></center>
+   </td></tr></table>
+
+   <p>Your server options are as follows:</p>
+   
+   <ul>
+   
+   <li><b>Babelfish</b> -
+       13 language pairs,
+       maximum of 1000 characters translated,
+       powered by Systran
+       [ <a href="http://babelfish.altavista.com/" 
+       target="_blank">Babelfish</a> ]</li>
+
+   <li><b>Go.com</b> -
+       10 language pairs,
+       maximum of 25 kilobytes translated,
+       powered by Systran
+       [ <a href="http://translator.go.com/"
+       target="_blank">Translator.Go.com</a> ]</li>
+
+   <li><b>Dictionary.com</b> -
+       12 language pairs,
+       no known limits,
+       powered by Systran
+       [ <a href="http://www.dictionary.com/translate"
+       target="_blank">Dictionary.com</a> ]</li>
+       
+   <li><b>InterTran</b> -
+       767 language pairs,
+       no known limits,
+       powered by Translation Experts's InterTran
+       [ <a href="http://www.tranexp.com/"
+       target="_blank">Translation Experts</a> ]</li>
+       
+   <li><b>GPLTrans</b> -
+       8 language pairs,
+       no known limits,
+       powered by GPLTrans (free, open source)
+       [ <a href="http://www.translator.cx/"
+       target="_blank">GPLTrans</a> ]</li>
+
+   </ul>
+   
+   <p>You also decide if you want the translation box displayed, 
+   and where it will be located.</p>
+
+   <form action="../../src/options.php" method=post>
+   <table border=0 cellpadding=0 cellspacing=2>
+   <tr><td align=right nowrap>Select your translator:</td>
+       <td><select name="translate_translate_server">
+<?PHP
+    ShowOption('server', 'babelfish', 'Babelfish');
+    ShowOption('server', 'go', 'Go.com');
+    ShowOption('server', 'dictionary', 'Dictionary.com');
+    ShowOption('server', 'intertran', 'Intertran');
+    ShowOption('server', 'gpltrans', 'GPLTrans');
+?>       </select>
+       </td></tr>
+   <tr><td align=right nowrap valign="top">When reading:</td>
+   <td><input type=checkbox name="translate_translate_show_read"<?PHP
+   if ($translate_show_read) 
+     echo " CHECKED";
+   ?>> - Show translation box
+   <select name="translate_translate_location">
+<?PHP
+    ShowOption('location', 'left', 'to the left');
+    ShowOption('location', 'center', 'in the center');
+    ShowOption('location', 'right', 'to the right');
+?>    </select><br>
+   <input type=checkbox name="translate_translate_same_window"<?PHP
+   if ($translate_same_window)
+     echo " CHECKED";
+   ?>> - Translate inside the SquirrelMail frames</td></tr>
+   <tr><td align=right nowrap>When composing:</td>
+   <td><input type=checkbox name="translate_translate_show_send"<?PHP
+   if ($translate_show_send)
+     echo " CHECKED";
+   ?>> - Not yet functional, currently does nothing</td></tr>
+   <tr><td></td><td>
+       <input type="submit" value="Submit" name="submit_translate">
+       </td></tr>
+   </table>
+   </form>
+</body></html>

+ 416 - 0
plugins/translate/setup.php

@@ -0,0 +1,416 @@
+<?php
+
+/* Easy plugin that sends the body of the message to a new browser
+window using the specified translator.  It can also translate your
+outgoing message if you send it to someone in a different country. 
+
+  Languages from i18n, incorporated in the auto-language selection:
+    en - English
+    no - Norwegian (Bokm&aring;l)
+    no_NO_ny - Norwegian (Nynorsk)
+    de - Deutsch
+    ru - Russian KOI8-R
+    pl - Polish
+    sv - Swedish
+    nl - Dutch
+    pt_BR - Portuguese (Brazil)
+    fr - French
+    it - Italian
+    cs - Czech
+    es - Spanish
+    ko - Korean
+*/
+
+
+/* Initialize the translation plugin */
+function squirrelmail_plugin_init_translate() {
+  global $squirrelmail_plugin_hooks;
+
+  $squirrelmail_plugin_hooks['read_body_bottom']['translate'] = 'translate_read_form';
+  $squirrelmail_plugin_hooks['options_register']['translate'] = 'translate_opt';
+  $squirrelmail_plugin_hooks['options_save']['translate'] = 'translate_sav';
+  $squirrelmail_plugin_hooks['loading_prefs']['translate'] = 'translate_pref';
+  $squirrelmail_plugin_hooks['compose_button_row']['translate'] = 'translate_button';
+}
+
+
+/* Show the translation for a message you're reading */
+function translate_read_form() {
+    global $color, $translate_server;
+    global $body, $translate_dir;
+    global $translate_show_read;
+
+    if (!$translate_show_read) {
+        return;
+    }
+    
+    $translate_dir = 'to';
+            
+    $new_body = $body;
+    $pos = strpos($new_body,
+            '">Download this as a file</A></CENTER><BR></SMALL>');
+    if (is_int($pos)) {
+        $new_body = substr($new_body, 0, $pos);
+    }
+                     
+    $trans = get_html_translation_table('HTMLENTITIES');
+    $trans[' '] = '&nbsp;';
+    $trans = array_flip($trans);
+    $new_body = strtr($new_body, $trans);
+
+    $new_body = urldecode($new_body);
+    $new_body = strip_tags($new_body);
+              
+    /* I really don't like this next part ... */
+    $new_body = str_replace('"', "''", $new_body);
+    $new_body = strtr($new_body, "\n", ' ');
+    
+    $function = 'translate_form_' . $translate_server;
+    $function($new_body);
+}
+
+function translate_table_end() {                     
+  ?></td>
+          </tr>
+        </table>
+      </td>
+    </tr>
+  </table>
+  </form>
+  <?php
+}
+
+
+function translate_button() {
+    global $translate_show_send;
+  
+    if (! $translate_show_send) {
+        return;
+    }
+}
+
+
+function translate_opt() {
+    global $optionpages;
+    $optionpages[] = array(
+        'name' => 'Translation Options',
+        'url'  => '../plugins/translate/options.php',
+        'desc' => 'Which translator should be used when you get messages in a different language?',
+        'js'   => false
+    );
+}
+
+function translate_sav() {
+    global $username,$data_dir;
+    global $submit_translate, $translate_translate_server;
+    global $translate_translate_location;
+    global $translate_translate_show_read;
+    global $translate_translate_show_send;
+    global $translate_translate_same_window;
+  
+    if ($submit_translate) {
+        if (isset($translate_translate_server)) {
+            setPref($data_dir, $username, 'translate_server', $translate_translate_server);
+        } else {
+            setPref($data_dir, $username, 'translate_server', 'babelfish');
+        }
+
+        if (isset($translate_translate_location)) {
+            setPref($data_dir, $username, 'translate_location', $translate_translate_location);
+        } else {
+            setPref($data_dir, $username, 'translate_location', 'center');
+        }
+
+        if (isset($translate_translate_show_read)) {
+            setPref($data_dir, $username, 'translate_show_read', '1');
+        } else {
+            setPref($data_dir, $username, 'translate_show_read', '');
+        }
+
+        if (isset($translate_translate_show_send)) {
+            setPref($data_dir, $username, 'translate_show_send', '1');
+        } else {
+            setPref($data_dir, $username, 'translate_show_send', '');
+        }
+
+        if (isset($translate_translate_same_window)) {
+           setPref($data_dir, $username, 'translate_same_window', '1');
+        } else {
+            setPref($data_dir, $username, 'translate_same_window', '');
+        }
+
+        echo '<center>Translation options saved.</center>';
+    }
+}
+
+
+function translate_pref() { 
+    global $username, $data_dir;
+    global $translate_server, $translate_location;
+    global $translate_show_send, $translate_show_read;
+    global $translate_same_window;
+
+    $translate_server = getPref($data_dir, $username, 'translate_server');
+    if ($translate_server == '') {
+        $translate_server = 'babelfish';
+    }
+    
+    $translate_location = getPref($data_dir, $username, 'translate_location');
+    if ($translate_location == '') {
+        $translate_location = 'center';
+    }
+    
+    $translate_show_send = getPref($data_dir, $username, 'translate_show_send');
+    $translate_show_read = getPref($data_dir, $username, 'translate_show_read');
+    $translate_same_window = getPref($data_dir, $username, 'translate_same_window');
+}
+
+
+/**
+ * This function could be sped up.
+ * It basically negates the process if a ! is found in the beginning and
+ * matches a * at the end with 0 or more characters.
+ */
+function translate_does_it_match_language($test) {
+    global $squirrelmail_language;
+    $true = 1;
+    $false = 0;
+    $index = 0;
+    $smindex = 0;
+  
+    if (! $test || ! $squirrelmail_language) {
+        return $false;
+    }
+      
+    if ($test[$index] == '!') {
+        $index ++;
+        $true = 0;
+        $false = 1;
+    }
+    
+    if (($index == 0) && ($test == $squirrelmail_language)) {
+        return $true;
+    }
+      
+    while ($test[$index]) {
+        if ($test[$index] == '*') {
+            return $true;
+        }
+        if ($test[$index] != $squirrelmail_language[$smindex]) {
+            return $false;
+        }
+        $index ++;
+        $smindex ++;
+    }
+      
+    return $false;
+}
+
+
+function translate_lang_opt($from, $to, $value, $text) {
+    global $translate_dir;
+    
+    echo '  <option value="' . $value . '"';
+    
+    if (translate_does_it_match_language($to) && ($translate_dir == 'to')) {
+        echo ' SELECTED';
+    }
+
+    if (translate_does_it_match_language($from) && ($translate_dir == 'from')) {
+        echo ' SELECTED';
+    }
+        
+    echo '>' . $text . "</option>\n";
+}
+
+
+function translate_new_form($action) {
+    global $translate_dir, $translate_new_window, $translate_location;
+    global $color, $translate_same_window;
+
+    echo '<form action="';
+  
+    if ($translate_dir == 'to') {
+        echo $action;
+    } else {
+        echo 'translate.php';
+    }
+  
+    echo '" method="post"';
+  
+    if (!$translate_same_window) {
+        echo ' target="_blank"';
+    }
+  
+    echo ">\n";
+
+    ?><table align="<?php echo $translate_location ?>" cellpadding=3 cellspacing=0 border=0 bgcolor=<?php echo $color[10] ?>>
+    <tr>
+      <td>
+        <table cellpadding=2 cellspacing=1 border=0 bgcolor="<?php echo $color[5] ?>">
+          <tr>
+            <td><?php
+}
+
+function translate_form_babelfish($message) {
+    translate_new_form('http://babelfish.altavista.com/translate.dyn');
+?>
+    <input type="hidden" name="doit" value="done">
+    <input type="hidden" name="BabelFishFrontPage" value="yes">
+    <input type="hidden" name="bblType" value="urltext">
+    <input type="hidden" name="urltext" value="<?php echo $message; ?>">
+    <select name="lp"><?php
+        translate_lang_opt('en',  'fr',  'en_fr', 'English to French');
+        translate_lang_opt('',    'de',  'en_de', 'English to German');
+        translate_lang_opt('',    'it',  'en_it', 'English to Italian');
+        translate_lang_opt('',    'pt*', 'en_pt', 'English to Portuguese');
+        translate_lang_opt('',    'es',  'en_es', 'English to Spanish');
+        translate_lang_opt('fr',  'en',  'fr_en', 'French to English');
+        translate_lang_opt('de',  '',    'de_en', 'German to English');
+        translate_lang_opt('it',  '',    'it_en', 'Italian to English');
+        translate_lang_opt('pt*', '',    'pt_en', 'Portuguese to English');
+        translate_lang_opt('es',  '',    'es_en', 'Spanish to English');
+        translate_lang_opt('',    '',    'de_fr', 'German to French');
+        translate_lang_opt('',    '',    'fr_de', 'French to German');
+        translate_lang_opt('ru',  '',    'ru_en', 'Russian to English');
+?></select>
+    Babelfish: <input type="Submit" value="Translate">
+<?php
+    translate_table_end();
+}
+
+function translate_form_go($message) {
+    translate_new_form('http://translator.go.com/cb/trans_entry');
+?>
+    <input type=hidden name=input_type value=text>
+    <select name=lp><?php
+        translate_lang_opt('en', 'es', 'en_sp', 'English to Spanish');
+        translate_lang_opt('',   'fr', 'en_fr', 'English to French');
+        translate_lang_opt('',   'de', 'en_ge', 'English to German');
+        translate_lang_opt('',   'it', 'en_it', 'English to Italian');
+        translate_lang_opt('',   'pt', 'en_pt', 'English to Portuguese');
+        translate_lang_opt('es', 'en', 'sp_en', 'Spanish to English');
+        translate_lang_opt('fr', '',   'fr_en', 'French to English');
+        translate_lang_opt('de', '',   'ge_en', 'German to English');
+        translate_lang_opt('it', '',   'it_en', 'Italian to English');
+        translate_lang_opt('pt', '',   'pt_en', 'Portuguese to English');
+?></select>
+    <input type="hidden" name="text" value="<?php echo $message ?>">
+    Go.com: <input type="Submit" value="Translate">
+<?php
+    translate_table_end();
+}
+
+function translate_form_intertran($message) {
+    translate_new_form('http://www.tranexp.com:2000/InterTran');
+?>
+    <INPUT TYPE="hidden" NAME="topframe" VALUE="yes">
+    <INPUT TYPE="hidden" NAME="type" VALUE="text">
+    <input type="hidden" name="text" value="<?php echo $message ?>">
+    <SELECT name="from"><?PHP
+        translate_lang_opt('pt_BR', '',    'pob', 'Brazilian Portuguese');
+        translate_lang_opt('',      '',    'bul', 'Bulgarian (CP 1251)');
+        translate_lang_opt('',      '',    'cro', 'Croatian (CP 1250)');
+        translate_lang_opt('cs',    '',    'che', 'Czech (CP 1250)');
+        translate_lang_opt('',      '',    'dan', 'Danish');
+        translate_lang_opt('nl',    '',    'dut', 'Dutch');
+        translate_lang_opt('en',    '!en', 'eng', 'English');
+        translate_lang_opt('',      '',    'spe', 'European Spanish');
+        translate_lang_opt('',      '',    'fin', 'Finnish');
+        translate_lang_opt('fr',    '',    'fre', 'French');
+        translate_lang_opt('de',    '',    'ger', 'German');
+        translate_lang_opt('',      '',    'grk', 'Greek');
+        translate_lang_opt('',      '',    'hun', 'Hungarian (CP 1250)');
+        translate_lang_opt('',      '',    'ice', 'Icelandic');
+        translate_lang_opt('it',    '',    'ita', 'Italian');
+        translate_lang_opt('',      '',    'jpn', 'Japanese (Shift JIS)');
+        translate_lang_opt('',      '',    'spl', 'Latin American Spanish');
+        translate_lang_opt('no*',   '',    'nor', 'Norwegian');
+        translate_lang_opt('pl',    '',    'pol', 'Polish (ISO 8859-2)');
+        translate_lang_opt('',      '',    'poe', 'Portuguese');
+        translate_lang_opt('',      '',    'rom', 'Romanian (CP 1250)');
+        translate_lang_opt('ru',    '',    'rus', 'Russian (CP 1251)');
+        translate_lang_opt('',      '',    'sel', 'Serbian (CP 1250)');
+        translate_lang_opt('',      '',    'slo', 'Slovenian (CP 1250)');
+        translate_lang_opt('es',    '',    'spa', 'Spanish');
+        translate_lang_opt('sv',    '',    'swe', 'Swedish');
+        translate_lang_opt('',      '',    'wel', 'Welsh');
+?></SELECT> to <SELECT name="to"><?PHP
+        translate_lang_opt('',    'pt_BR', 'pob', 'Brazilian Portuguese');
+        translate_lang_opt('',    '',      'bul', 'Bulgarian (CP 1251)');
+        translate_lang_opt('',    '',      'cro', 'Croatian (CP 1250)');
+        translate_lang_opt('',    'cs',    'che', 'Czech (CP 1250)');
+        translate_lang_opt('',    '',      'dan', 'Danish');
+        translate_lang_opt('',    'nl',    'dut', 'Dutch');
+        translate_lang_opt('!en', 'en',    'eng', 'English');
+        translate_lang_opt('',    '',      'spe', 'European Spanish');
+        translate_lang_opt('',    '',      'fin', 'Finnish');
+        translate_lang_opt('',    'fr',    'fre', 'French');
+        translate_lang_opt('',    'de',    'ger', 'German');
+        translate_lang_opt('',    '',      'grk', 'Greek');
+        translate_lang_opt('',    '',      'hun', 'Hungarian (CP 1250)');
+        translate_lang_opt('',    '',      'ice', 'Icelandic');
+        translate_lang_opt('',    'it',    'ita', 'Italian');
+        translate_lang_opt('',    '',      'jpn', 'Japanese (Shift JIS)');
+        translate_lang_opt('',    '',      'spl', 'Latin American Spanish');
+        translate_lang_opt('',    'no*',   'nor', 'Norwegian');
+        translate_lang_opt('',    'pl',    'pol', 'Polish (ISO 8859-2)');
+        translate_lang_opt('',    '',      'poe', 'Portuguese');
+        translate_lang_opt('',    '',      'rom', 'Romanian (CP 1250)');
+        translate_lang_opt('',    'ru',    'rus', 'Russian (CP 1251)');
+        translate_lang_opt('',    '',      'sel', 'Serbian (CP 1250)');
+        translate_lang_opt('',    '',      'slo', 'Slovenian (CP 1250)');
+        translate_lang_opt('',    'es',    'spa', 'Spanish');
+        translate_lang_opt('',    'sv',    'swe', 'Swedish');
+        translate_lang_opt('',    '',      'wel', 'Welsh');
+?></SELECT>
+    InterTran: <input type=submit value="Translate">
+<?php
+    translate_table_end();
+}
+
+function translate_form_gpltrans($message) {
+    translate_new_form('http://www.translator.cx/cgi-bin/gplTrans');
+?><select name="toenglish"><?php
+    translate_lang_opt('en',  '!en', 'no',  'From English');
+    translate_lang_opt('!en', 'en',  'yes', 'To English');
+?></select><select name="language">
+<?php
+    translate_lang_opt('nl', 'nl', 'dutch_dict',      'Dutch');
+    translate_lang_opt('fr', 'fr', 'french_dict',     'French');
+    translate_lang_opt('de', 'de', 'german_dict',     'German');
+    translate_lang_opt('',   '',   'indonesian_dict', 'Indonesian');
+    translate_lang_opt('it', 'it', 'italian_dict',    'Italian');
+    translate_lang_opt('',   '',   'latin_dict',      'Latin');
+    translate_lang_opt('pt', 'pt', 'portuguese_dict', 'Portuguese');
+    translate_lang_opt('es', 'es', 'spanish_dict',    'Spanish');
+?></select>
+    <input type="hidden" name="text" value="<?php echo $message ?>">
+    GPLTrans: <input type="submit" value="Translate">
+<?php
+    translate_table_end();
+}
+
+function translate_form_dictionary($message) {
+    translate_new_form('http://translate.dictionary.com:8800/systran/cgi');
+?><INPUT TYPE=HIDDEN NAME=partner VALUE=LEXICO>
+    <input type=hidden name=urltext value="<?php echo $message ?>">
+<SELECT NAME="lp"><?php
+    translate_lang_opt('en',  'fr', 'en_fr', 'English to French');
+    translate_lang_opt('',    'de', 'en_de', 'English to German');
+    translate_lang_opt('',    'it', 'en_it', 'English to Italian');
+    translate_lang_opt('',    'pt*', 'en_pt', 'English to Portuguese');
+    translate_lang_opt('',    'es', 'en_sp', 'English to Spanish');
+    translate_lang_opt('fr',  '', 'fr_en', 'French to English');
+    translate_lang_opt('',    '', 'fr_ge', 'French to German');
+    translate_lang_opt('',    '', 'ge_fr', 'German to French');
+    translate_lang_opt('de',  '', 'de_en', 'German to English');
+    translate_lang_opt('it',  '', 'it_en', 'Italian to English');
+    translate_lang_opt('pt*', '', 'pt_en', 'Portuguese to English');
+    translate_lang_opt('es',  '', 'sp_en', 'Spanish to English');
+?></SELECT>
+    Dictionary.com: <INPUT TYPE="submit" VALUE="Translate">
+<?php
+  translate_table_end();
+}
+?>

+ 382 - 204
src/options.php

@@ -1,248 +1,426 @@
 <?php
-   /**
-    **  options.php
-    **
-    **  Copyright (c) 1999-2000 The SquirrelMail development team
-    **  Licensed under the GNU GPL. For full terms see the file COPYING.
-    **
-    **  Displays the options page. Pulls from proper user preference files
-    **  and config.php. Displays preferences as selected and other options.
-    **
-    **  $Id$
-    **/
-
-   require_once('../src/validate.php');
-   require_once('../functions/display_messages.php');
-   require_once('../functions/imap.php');
-   require_once('../functions/array.php');
+    /**
+     * options.php
+     *
+     * Copyright (c) 1999-2001 The SquirrelMail Development Team
+     * Licensed under the GNU GPL. For full terms see the file COPYING.
+     *
+     * Displays the options page. Pulls from proper user preference files
+     * and config.php. Displays preferences as selected and other options.
+     *
+     *  $Id$
+     */
+
+    require_once('../src/validate.php');
+    require_once('../functions/display_messages.php');
+    require_once('../functions/imap.php');
+    require_once('../functions/array.php');
    
-  ereg ("(^.*/)[^/]+/[^/]+$", $PHP_SELF, $regs);
-  $base_uri = $regs[1];   
+    ereg ("(^.*/)[^/]+/[^/]+$", $PHP_SELF, $regs);
+    $base_uri = $regs[1];   
 
-   if (isset($language)) {
-      setcookie('squirrelmail_language', $language, time()+2592000, $base_uri);
-      $squirrelmail_language = $language;
-   }   
+    if (isset($language)) {
+        setcookie('squirrelmail_language', $language, time()+2592000, $base_uri);
+        $squirrelmail_language = $language;
+    }   
 
-   displayPageHeader($color, _("None"));
+    displayPageHeader($color, _("None"));
 
 ?>
 
 <br>
 <table bgcolor="<?php echo $color[0] ?>" width="95%" align="center" cellpadding="2" cellspacing="0" border="0">
 <tr><td align="center">
-
-      <b><?php echo _("Options") ?></b><br>
+    <b><?php echo _("Options") ?></b><br>
 
     <table width="100%" border="0" cellpadding="5" cellspacing="0">
     <tr><td bgcolor="<?php echo $color[4] ?>" align="center">
 
 <?php
-   if (isset($submit_personal)) {
-      # Save personal information
-      if (isset($full_name)) {
-         setPref($data_dir, $username, 'full_name', $full_name);
-      }
-      if (isset($email_address)) {
-         setPref($data_dir, $username, 'email_address', $email_address);
-      }
-      if (isset($reply_to)) {
-         setPref($data_dir, $username, 'reply_to', $reply_to);
-      }
-      setPref($data_dir, $username, 'reply_citation_style', $new_reply_citation_style);
-      setPref($data_dir, $username, 'reply_citation_start', $new_reply_citation_start);
-      setPref($data_dir, $username, 'reply_citation_end', $new_reply_citation_end);
-      if (! isset($usesignature))
-         $usesignature = 0;
-      setPref($data_dir, $username, 'use_signature', $usesignature);  
-      if (! isset($prefixsig)) {
-         $prefixsig = 0;
-      }
-      setPref($data_dir, $username, 'prefix_sig', $prefixsig);
-      if (isset($signature_edit)) {
-         setSig($data_dir, $username, $signature_edit);
-      }
+    if (isset($submit_personal)) {
+        /* Save personal information. */
+        if (isset($full_name)) {
+           setPref($data_dir, $username, 'full_name', $full_name);
+        }
+        if (isset($email_address)) {
+           setPref($data_dir, $username, 'email_address', $email_address);
+        }
+        if (isset($reply_to)) {
+           setPref($data_dir, $username, 'reply_to', $reply_to);
+        }
+        setPref($data_dir, $username, 'reply_citation_style', $new_reply_citation_style);
+        setPref($data_dir, $username, 'reply_citation_start', $new_reply_citation_start);
+        setPref($data_dir, $username, 'reply_citation_end', $new_reply_citation_end);
+        if (! isset($usesignature))
+            $usesignature = 0;
+        setPref($data_dir, $username, 'use_signature', $usesignature);  
+        if (! isset($prefixsig)) {
+            $prefixsig = 0;
+        }
+        setPref($data_dir, $username, 'prefix_sig', $prefixsig);
+        if (isset($signature_edit)) {
+            setSig($data_dir, $username, $signature_edit);
+        }
       
-      do_hook('options_personal_save');
+        do_hook('options_personal_save');
       
-      echo '<br><b>'._("Successfully saved personal information!").'</b><br>';
-   } else if (isset($submit_display)) {
-      // Do checking to make sure $chosentheme is in the array
-      $in_ary = false;
-      for ($i=0; $i < count($theme); $i++)
-      {
-          if ($theme[$i]['PATH'] == $chosentheme)
-	  {
-	      $in_ary = true;
-	      break;
-	  }
-      }
-      if (! $in_ary)
-          $chosentheme = '';
-   
-      # Save display preferences
-      setPref($data_dir, $username, 'chosen_theme', $chosentheme);
-      setPref($data_dir, $username, 'language', $language);
-      setPref($data_dir, $username, 'use_javascript_addr_book', $javascript_abook);
-      setPref($data_dir, $username, 'show_num', $shownum);
-      setPref($data_dir, $username, 'wrap_at', $wrapat);
-      setPref($data_dir, $username, 'editor_size', $editorsize);
-      setPref($data_dir, $username, 'left_refresh', $leftrefresh);
-      setPref($data_dir, $username, 'location_of_bar', $folder_new_location);
-      setPref($data_dir, $username, 'location_of_buttons', $button_new_location);
-      setPref($data_dir, $username, 'left_size', $leftsize);
-
-      if (isset($altIndexColors) && $altIndexColors == 1) {
-         setPref($data_dir, $username, 'alt_index_colors', 1);
-      } else {
-         setPref($data_dir, $username, 'alt_index_colors', 0);
-      }
-
-      setPref($data_dir, $username, 'show_html_default', ($showhtmldefault?1:0) );
-
-      if (isset($includeselfreplyall)) {
-         setPref($data_dir, $username, 'include_self_reply_all', 1);
-      } else {
-         removePref($data_dir, $username, 'include_self_reply_all');
-      }
-
-      if (isset($pageselectormax)) {
-         setPref($data_dir, $username, 'page_selector_max', $pageselectormax);
-      } else {
-         removePref($data_dir, $username, 'page_selector_max', 0 );
-      }
-
-      if (isset($pageselector)) {
-         removePref($data_dir, $username, 'page_selector');
-      } else {
-         setPref($data_dir, $username, 'page_selector', 1);
-      }
-
-      do_hook('options_display_save');
-
-      echo '<br><b>'._("Successfully saved display preferences!").'</b><br>';
-      echo '<a href="../src/webmail.php?right_frame=options.php" target=_top>' . _("Refresh Page") . '</a><br>';
-   } else if (isset($submit_folder)) { 
-      # Save folder preferences
-      if ($trash != 'none') {
-         setPref($data_dir, $username, 'move_to_trash', true);
-         setPref($data_dir, $username, 'trash_folder', $trash);
-      } else {
-         setPref($data_dir, $username, 'move_to_trash', '0');
-         setPref($data_dir, $username, 'trash_folder', 'none');
-      }
-      if ($sent != 'none') {
-         setPref($data_dir, $username, 'move_to_sent', true);
-         setPref($data_dir, $username, 'sent_folder', $sent);
-      } else {
-         setPref($data_dir, $username, 'move_to_sent', '0');
-         setPref($data_dir, $username, 'sent_folder', 'none');
-      }
-      if ($draft != 'none') {
-         setPref($data_dir, $username, 'save_as_draft', true);
-         setPref($data_dir, $username, 'draft_folder', $draft);
-      } else {
-         setPref($data_dir, $username, 'save_as_draft', '0');
-         setPref($data_dir, $username, 'draft_folder', 'none');
-      }
-      if (isset($folderprefix)) {
-         setPref($data_dir, $username, 'folder_prefix', $folderprefix);
-      } else {
-         setPref($data_dir, $username, 'folder_prefix', '');
-      }
-      setPref($data_dir, $username, 'unseen_notify', $unseennotify);
-      setPref($data_dir, $username, 'unseen_type', $unseentype);
-      if (isset($collapsefolders))
-          setPref($data_dir, $username, 'collapse_folders', $collapsefolders);
-      else
-          removePref($data_dir, $username, 'collapse_folders');
-      setPref($data_dir, $username, 'date_format', $dateformat);
-      setPref($data_dir, $username, 'hour_format', $hourformat);
-      do_hook('options_folders_save');
-      echo '<br><b>'._("Successfully saved folder preferences!").'</b><br>';
-      echo '<a href="../src/left_main.php" target=left>' . _("Refresh Folder List") . '</a><br>';
-   } else {
-      do_hook('options_save');
-   }
+        echo '<br><b>'._("Successfully saved personal information!").'</b><br>';
+    } else if (isset($submit_display)) {
+        // Do checking to make sure $chosentheme is in the array
+        $in_ary = false;
+        for ($i=0; $i < count($theme); $i++) {
+            if ($theme[$i]['PATH'] == $chosentheme) {
+                $in_ary = true;
+                break;
+            }
+        }
+        if (! $in_ary) {
+            $chosentheme = '';
+        }
    
+        /* Save display preferences. */
+        setPref($data_dir, $username, 'chosen_theme', $chosentheme);
+        setPref($data_dir, $username, 'language', $language);
+        setPref($data_dir, $username, 'use_javascript_addr_book', $javascript_abook);
+        setPref($data_dir, $username, 'show_num', $shownum);
+        setPref($data_dir, $username, 'wrap_at', $wrapat);
+        setPref($data_dir, $username, 'editor_size', $editorsize);
+        setPref($data_dir, $username, 'left_refresh', $leftrefresh);
+        setPref($data_dir, $username, 'location_of_bar', $folder_new_location);
+        setPref($data_dir, $username, 'location_of_buttons', $button_new_location);
+        setPref($data_dir, $username, 'left_size', $leftsize);
+
+        if (isset($altIndexColors) && $altIndexColors == 1) {
+            setPref($data_dir, $username, 'alt_index_colors', 1);
+        } else {
+            setPref($data_dir, $username, 'alt_index_colors', 0);
+        }
+
+        setPref($data_dir, $username, 'show_html_default', ($showhtmldefault?1:0) );
+
+        if (isset($includeselfreplyall)) {
+            setPref($data_dir, $username, 'include_self_reply_all', 1);
+        } else {
+            removePref($data_dir, $username, 'include_self_reply_all');
+        }
+
+        if (isset($pageselectormax)) {
+            setPref($data_dir, $username, 'page_selector_max', $pageselectormax);
+        } else {
+            removePref($data_dir, $username, 'page_selector_max', 0 );
+        }
+
+        if (isset($pageselector)) {
+            removePref($data_dir, $username, 'page_selector');
+        } else {
+            setPref($data_dir, $username, 'page_selector', 1);
+        }
+
+        do_hook('options_display_save');
+
+        echo '<br><b>'._("Successfully saved display preferences!").'</b><br>';
+        echo '<a href="../src/webmail.php?right_frame=options.php" target=_top>' . _("Refresh Page") . '</a><br>';
+    } else if (isset($submit_folder)) { 
+        /* Save folder preferences. */
+        if ($trash != 'none') {
+            setPref($data_dir, $username, 'move_to_trash', true);
+           setPref($data_dir, $username, 'trash_folder', $trash);
+        } else {
+            setPref($data_dir, $username, 'move_to_trash', '0');
+            setPref($data_dir, $username, 'trash_folder', 'none');
+        }
+        if ($sent != 'none') {
+            setPref($data_dir, $username, 'move_to_sent', true);
+            setPref($data_dir, $username, 'sent_folder', $sent);
+        } else {
+            setPref($data_dir, $username, 'move_to_sent', '0');
+            setPref($data_dir, $username, 'sent_folder', 'none');
+        }
+        if ($draft != 'none') {
+            setPref($data_dir, $username, 'save_as_draft', true);
+            setPref($data_dir, $username, 'draft_folder', $draft);
+        } else {
+            setPref($data_dir, $username, 'save_as_draft', '0');
+            setPref($data_dir, $username, 'draft_folder', 'none');
+        }
+        if (isset($folderprefix)) {
+            setPref($data_dir, $username, 'folder_prefix', $folderprefix);
+        } else {
+            setPref($data_dir, $username, 'folder_prefix', '');
+        }
+        setPref($data_dir, $username, 'unseen_notify', $unseennotify);
+        setPref($data_dir, $username, 'unseen_type', $unseentype);
+        if (isset($collapsefolders))
+             setPref($data_dir, $username, 'collapse_folders', $collapsefolders);
+        else
+             removePref($data_dir, $username, 'collapse_folders');
+        setPref($data_dir, $username, 'date_format', $dateformat);
+        setPref($data_dir, $username, 'hour_format', $hourformat);
+        do_hook('options_folders_save');
+        echo '<br><b>'._("Successfully saved folder preferences!").'</b><br>';
+        echo '<a href="../src/left_main.php" target=left>' . _("Refresh Folder List") . '</a><br>';
+    } else {
+        do_hook('options_save');
+    }
+
+    /****************************************/
+    /* Now build our array of option pages. */
+    /****************************************/
+
+    /* Build a section for Personal Options. */
+    $optionpages[] = array(
+        'name' => _("Personal Information"),
+        'url'  => 'options_personal.php',
+        'desc' => _("This contains personal information about yourself such as your name, your email address, etc."),
+        'js'   => false
+    );
+
+    /* Build a section for Display Options. */
+    $optionpages[] = array(
+        'name' => _("Display Preferences"),
+        'url'  => 'options_display.php',
+        'desc' => _("You can change the way that SquirrelMail looks and displays information to you, such as the colors, the language, and other settings."),
+        'js'   => false
+    );
+
+    /* Build a section for Message Highlighting Options. */
+    $optionpages[] = array(
+        'name' =>_("Message Highlighting"),
+        'url'  => 'options_highlight.php',
+        'desc' =>_("Based upon given criteria, incoming messages can have different background colors in the message list.  This helps to easily distinguish who the messages are from, especially for mailing lists."),
+        'js'   => false
+    );
+
+    /* Build a section for Folder Options. */
+    $optionpages[] = array(
+        'name' => _("Folder Preferences"),
+        'url'  => 'options_folder.php',
+        'desc' => _("These settings change the way your folders are displayed and manipulated."),
+        'js'   => false
+    );
+
+    /* Build a section for Index Order Options. */
+    $optionpages[] = array(
+        'name' => _("Index Order"),
+        'url'  => 'options_order.php',
+        'desc' => _("The order of the message index can be rearanged and changed to contain the headers in any order you want."),
+        'js'   => false
+    );
+    /* Build a section for plugins wanting to register an optionpage. */
+    do_hook('options_register');
+
+    /*****************************************************/
+    /* Let's sort Javascript Option Pages to the bottom. */
+    /*****************************************************/
+    foreach ($optionpages as $optpage) {
+        if ($optpage['js']) {
+            $js_optionpages[] = $optpage;
+        } else {
+            $nojs_optionpages[] = $optpage;
+        }
+    }
+    $optionpages = array_merge($nojs_optionpages, $js_optionpages);
+
+    /********************************************/
+    /* Now, print out each option page section. */
+    /********************************************/
+    $first_optpage = false;
+    foreach ($optionpages as $next_optpage) {
+        if ($first_optpage == false) {
+            $first_optpage = $next_optpage;
+        } else {
+            print_optionpages_row($first_optpage, $next_optpage);
+            $first_optpage = false;
+        }
+    }
+
+    if ($first_optpage != false) {
+        print_optionpages_row($first_optpage);
+    }
+
+    do_hook('options_link_and_description');
+
 ?>
+    </td></tr>
+    </table>
+
+</td></tr>
+</table>
+
+</body></html>
+
+<?php
+
+    /*******************************************************************/
+    /* Please be warned. The code below this point sucks. This is just */
+    /* my first implementation to make the option rows work for both   */
+    /* Javascript and non-Javascript option chunks.                    */
+    /*                                                                 */
+    /* Please, someone make these better for me. All three functions   */
+    /* below REALLY do close to the same thing.                        */
+    /*                                                                 */
+    /* This code would be GREATLY improved by a templating system.     */
+    /* Don't try to implement that now, however. That will come later. */
+    /*******************************************************************/
+
+    /*******************************************************************/
+    /* Actually, now that I think about it, don't do anything with     */
+    /* this code yet. There is ACTUALLY supposed to be a difference    */
+    /* between the three functions that write the option rows. I just  */
+    /* have not yet gotten to integrating that yet.                    */
+    /*******************************************************************/
+
+    /**
+     * This function prints out an option page row. All it actually
+     * does is call the three functions below.
+     */
+    function print_optionpages_row($leftopt, $rightopt = false) {
+        if ($rightopt == false) {
+            if ($leftopt['js']) {
+                print_optionpages_row_fulljs($leftopt);
+            } else {
+                print_optionpages_row_nojs($leftopt);
+            }
+        } else {
+            if ($leftopt['js']) {
+                if ($rightopt['js']) {
+                    print_optionpages_row_fulljs($leftopt, $rightopt);
+                } else {
+                    print_optionpages_row_partjs($leftopt, $rightopt);
+                }
+            } else {
+                print_optionpages_row_nojs($leftopt, $rightopt);
+            }
+        }
+    }
 
-<table bgcolor="<?php echo $color[4] ?>" width="100%" cellpadding="5" cellspacing="0" border="0">
-<tr>
-   <td width="50%" valign="top">
+    /**
+     * This function prints out an option page row: in which the left
+     *   Left:  options for functionality that do not require javascript
+     *   Right: options for functionality that do not require javascript
+     */
+    function print_optionpages_row_nojs($leftopt, $rightopt = false) {
+        global $color;
+?>
+<table bgcolor="<?php echo $color[4] ?>" width="100%" cellpadding="0" cellspacing="5" border="0">
+   <tr><td valign=top>
       <table width="100%" cellpadding="3" cellspacing="0" border="0">
          <tr>
-            <td bgcolor="<?php echo $color[9] ?>">
-               <a href="options_personal.php"><?php echo _("Personal Information"); ?></a>
+            <td valign="top" bgcolor="<?php echo $color[9] ?>" width="50%">
+               <a href="<?php echo $leftopt['url'] ?>"><?php echo $leftopt['name'] ?></a>
+            </td>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php if ($rightopt != false) { ?>
+            <td valign="top" bgcolor="<?php echo $color[9] ?>" width="50%">
+               <a href="<?php echo $rightopt['url'] ?>"><?php echo $rightopt['name'] ?></a>
             </td>
+<?php } else { ?>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>" width="50%">&nbsp;</td>
+<?php } ?>
          </tr>
          <tr>
-            <td bgcolor="<?php echo $color[0] ?>">
-               <?php echo _("This contains personal information about yourself such as your name, your email address, etc.") ?>
+            <td valign="top" bgcolor="<?php echo $color[0] ?>">
+               <?php echo $leftopt['desc'] ?>
             </td>
-         </tr>   
-      </table><br>
-      <table width="100%" cellpadding="3" cellspacing="0" border="0">
-         <tr>
-            <td bgcolor="<?php echo $color[9] ?>">
-               <a href="options_highlight.php"><?php echo _("Message Highlighting"); ?></a>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php if ($rightopt != false) { ?>
+            <td valign="top" bgcolor="<?php echo $color[0] ?>">
+               <?php echo $rightopt['desc'] ?>
             </td>
+<?php } else { ?>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php } ?>
          </tr>
-         <tr>
-            <td bgcolor="<?php echo $color[0] ?>">
-               <?php echo _("Based upon given criteria, incoming messages can have different background colors in the message list.  This helps to easily distinguish who the messages are from, especially for mailing lists.") ?>
-            </td>
-         </tr>   
-      </table><br>
+      </table>
+   </td></tr>
+</table>
+<?php
+    }
+
+    /**
+     * This function prints out an option page row: in which the left
+     *   Left:  options for functionality that does not require javascript
+     *   Right: options for functionality that are javascript only
+     */
+    function print_optionpages_row_partjs($leftopt, $rightopt = false) {
+        global $color;
+?>
+<table bgcolor="<?php echo $color[4] ?>" width="100%" cellpadding="0" cellspacing="5" border="0">
+   <tr><td valign=top>
       <table width="100%" cellpadding="3" cellspacing="0" border="0">
          <tr>
-            <td bgcolor="<?php echo $color[9] ?>">
-               <a href="options_order.php"><?php echo _("Index Order"); ?></a>
+            <td valign="top" bgcolor="<?php echo $color[9] ?>" width="50%">
+               <a href="<?php echo $leftopt['url'] ?>"><?php echo $leftopt['name'] ?></a>
+            </td>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php if ($rightopt != false) { ?>
+            <td valign="top" bgcolor="<?php echo $color[9] ?>" width="50%">
+               <a href="<?php echo $rightopt['url'] ?>"><?php echo $rightopt['name'] ?></a>
             </td>
+<?php } else { ?>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>" width="50%">&nbsp;</td>
+<?php } ?>
          </tr>
          <tr>
-            <td bgcolor="<?php echo $color[0] ?>">
-               <?php echo _("The order of the message index can be rearanged and changed to contain the headers in any order you want.") ?>
+            <td valign="top" bgcolor="<?php echo $color[0] ?>">
+               <?php echo $leftopt['desc'] ?>
             </td>
-         </tr>   
-      </table><br>
-   </td>
-   <td valign="top" width="50%">
-      <table width="100%" cellpadding="3" cellspacing="0" border="0">
-         <tr>
-            <td bgcolor="<?php echo $color[9] ?>">
-               <a href="options_display.php"><?php echo _("Display Preferences"); ?></a>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php if ($rightopt != false) { ?>
+            <td valign="top" bgcolor="<?php echo $color[0] ?>">
+               <?php echo $rightopt['desc'] ?>
             </td>
+<?php } else { ?>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php } ?>
          </tr>
-         <tr>
-            <td bgcolor="<?php echo $color[0] ?>">
-               <?php echo _("You can change the way that SquirrelMail looks and displays information to you, such as the colors, the language, and other settings.") ?>
-            </td>
-         </tr>   
-      </table><br>
+      </table>
+   </td></tr>
+</table>
+<?php
+    }
+
+    /**
+     * This function prints out an option page row: in which the left
+     *   Left:  options for functionality that are javascript only
+     *   Right: options for functionality that are javascript only
+     */
+    function print_optionpages_row_fulljs($leftopt, $rightopt = false) {
+        global $color;
+?>
+<table bgcolor="<?php echo $color[4] ?>" width="100%" cellpadding="0" cellspacing="5" border="0">
+   <tr><td valign=top>
       <table width="100%" cellpadding="3" cellspacing="0" border="0">
          <tr>
-            <td bgcolor="<?php echo $color[9] ?>">
-               <a href="options_folder.php"><?php echo _("Folder Preferences"); ?></a>
+            <td valign="top" bgcolor="<?php echo $color[9] ?>" width="50%">
+               <a href="<?php echo $leftopt['url'] ?>"><?php echo $leftopt['name'] ?></a>
             </td>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php if ($rightopt != false) { ?>
+            <td valign="top" bgcolor="<?php echo $color[9] ?>" width="50%">
+               <a href="<?php echo $rightopt['url'] ?>"><?php echo $rightopt['name'] ?></a>
+            </td>
+<?php } else { ?>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>" width="50%">&nbsp;</td>
+<?php } ?>
          </tr>
          <tr>
-            <td bgcolor="<?php echo $color[0] ?>">
-               <?php echo _("These settings change the way your folders are displayed and manipulated.") ?>
+            <td valign="top" bgcolor="<?php echo $color[0] ?>">
+               <?php echo $leftopt['desc'] ?>
             </td>
-         </tr>   
-      </table><br>
-   </td>
-</tr>
-</table>
-
-   <?php do_hook('options_link_and_description'); ?>
-
-
-    </td></tr>
-    </table>
-
-</td></tr>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php if ($rightopt != false) { ?>
+            <td valign="top" bgcolor="<?php echo $color[0] ?>">
+               <?php echo $rightopt['desc'] ?>
+            </td>
+<?php } else { ?>
+            <td valign="top" bgcolor="<?php echo $color[4] ?>">&nbsp;</td>
+<?php } ?>
+         </tr>
+      </table>
+   </td></tr>
 </table>
+<?php
+    }
 
-</body></html>
+?>