Began rework of options page. Also added 3 plugins (filters, translate, and squirrels...
authorthomppj <thomppj@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Wed, 31 Oct 2001 06:10:21 +0000 (06:10 +0000)
committerthomppj <thomppj@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Wed, 31 Oct 2001 06:10:21 +0000 (06:10 +0000)
git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@1662 7612ce4b-ef26-0410-bec9-ea0150e637f0

44 files changed:
plugins/filters/CHANGES [new file with mode: 0644]
plugins/filters/README [new file with mode: 0644]
plugins/filters/filters.php [new file with mode: 0644]
plugins/filters/options.php [new file with mode: 0644]
plugins/filters/setup.php [new file with mode: 0644]
plugins/filters/sqimap_read_data.php [new file with mode: 0644]
plugins/squirrelspell/INSTALL [new file with mode: 0644]
plugins/squirrelspell/doc/CRYPTO [new file with mode: 0644]
plugins/squirrelspell/doc/ChangeLog [new file with mode: 0644]
plugins/squirrelspell/doc/PRIVACY [new file with mode: 0644]
plugins/squirrelspell/doc/README [new file with mode: 0644]
plugins/squirrelspell/doc/UPGRADING [new file with mode: 0644]
plugins/squirrelspell/doc/index.php [new file with mode: 0644]
plugins/squirrelspell/index.php [new file with mode: 0644]
plugins/squirrelspell/js/WHATISTHIS [new file with mode: 0644]
plugins/squirrelspell/js/check_me.js [new file with mode: 0644]
plugins/squirrelspell/js/crypto_settings.js [new file with mode: 0644]
plugins/squirrelspell/js/decrypt_error.js [new file with mode: 0644]
plugins/squirrelspell/js/index.php [new file with mode: 0644]
plugins/squirrelspell/js/init.js [new file with mode: 0644]
plugins/squirrelspell/modules/WHATISTHIS [new file with mode: 0644]
plugins/squirrelspell/modules/check_me.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/crypto.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/crypto_badkey.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/edit_dic.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/enc_setup.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/forget_me.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/forget_me_not.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/index.php [new file with mode: 0644]
plugins/squirrelspell/modules/init.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/lang_change.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/lang_setup.mod.php [new file with mode: 0644]
plugins/squirrelspell/modules/options_main.mod.php [new file with mode: 0644]
plugins/squirrelspell/setup.php [new file with mode: 0644]
plugins/squirrelspell/sqspell_config.dist [new file with mode: 0644]
plugins/squirrelspell/sqspell_config.php [new file with mode: 0644]
plugins/squirrelspell/sqspell_functions.php [new file with mode: 0644]
plugins/squirrelspell/sqspell_interface.php [new file with mode: 0644]
plugins/squirrelspell/sqspell_options.php [new file with mode: 0644]
plugins/translate/INSTALL [new file with mode: 0644]
plugins/translate/README [new file with mode: 0644]
plugins/translate/options.php [new file with mode: 0644]
plugins/translate/setup.php [new file with mode: 0644]
src/options.php

diff --git a/plugins/filters/CHANGES b/plugins/filters/CHANGES
new file mode 100644 (file)
index 0000000..e762423
--- /dev/null
@@ -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.  
+
diff --git a/plugins/filters/README b/plugins/filters/README
new file mode 100644 (file)
index 0000000..efb6599
--- /dev/null
@@ -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.
diff --git a/plugins/filters/filters.php b/plugins/filters/filters.php
new file mode 100644 (file)
index 0000000..7fe9783
--- /dev/null
@@ -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);
+      }
+   }
+?>
diff --git a/plugins/filters/options.php b/plugins/filters/options.php
new file mode 100644 (file)
index 0000000..b53ef1a
--- /dev/null
@@ -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);
+      }
+?>
diff --git a/plugins/filters/setup.php b/plugins/filters/setup.php
new file mode 100644 (file)
index 0000000..152afe5
--- /dev/null
@@ -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
+      );
+   }
+?>
diff --git a/plugins/filters/sqimap_read_data.php b/plugins/filters/sqimap_read_data.php
new file mode 100644 (file)
index 0000000..840a4bf
--- /dev/null
@@ -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;
+   }
+
+
+
+
diff --git a/plugins/squirrelspell/INSTALL b/plugins/squirrelspell/INSTALL
new file mode 100644 (file)
index 0000000..01c2c38
--- /dev/null
@@ -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. ;)
diff --git a/plugins/squirrelspell/doc/CRYPTO b/plugins/squirrelspell/doc/CRYPTO
new file mode 100644 (file)
index 0000000..1ae82ed
--- /dev/null
@@ -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.
diff --git a/plugins/squirrelspell/doc/ChangeLog b/plugins/squirrelspell/doc/ChangeLog
new file mode 100644 (file)
index 0000000..040ebc9
--- /dev/null
@@ -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.
diff --git a/plugins/squirrelspell/doc/PRIVACY b/plugins/squirrelspell/doc/PRIVACY
new file mode 100644 (file)
index 0000000..a88e192
--- /dev/null
@@ -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.
diff --git a/plugins/squirrelspell/doc/README b/plugins/squirrelspell/doc/README
new file mode 100644 (file)
index 0000000..297331e
--- /dev/null
@@ -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. :)
+---------
diff --git a/plugins/squirrelspell/doc/UPGRADING b/plugins/squirrelspell/doc/UPGRADING
new file mode 100644 (file)
index 0000000..1537b67
--- /dev/null
@@ -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.
+
diff --git a/plugins/squirrelspell/doc/index.php b/plugins/squirrelspell/doc/index.php
new file mode 100644 (file)
index 0000000..9b39265
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>
diff --git a/plugins/squirrelspell/index.php b/plugins/squirrelspell/index.php
new file mode 100644 (file)
index 0000000..9b39265
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>
diff --git a/plugins/squirrelspell/js/WHATISTHIS b/plugins/squirrelspell/js/WHATISTHIS
new file mode 100644 (file)
index 0000000..b26031d
--- /dev/null
@@ -0,0 +1,3 @@
+squirrelspell/js
+
+These are javascript files used by SquirrelSpell.
diff --git a/plugins/squirrelspell/js/check_me.js b/plugins/squirrelspell/js/check_me.js
new file mode 100644 (file)
index 0000000..852ca0e
--- /dev/null
@@ -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();
+  }
+}
diff --git a/plugins/squirrelspell/js/crypto_settings.js b/plugins/squirrelspell/js/crypto_settings.js
new file mode 100644 (file)
index 0000000..9de86ea
--- /dev/null
@@ -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);
+}
diff --git a/plugins/squirrelspell/js/decrypt_error.js b/plugins/squirrelspell/js/decrypt_error.js
new file mode 100644 (file)
index 0000000..56ec95e
--- /dev/null
@@ -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;
+}
+
diff --git a/plugins/squirrelspell/js/index.php b/plugins/squirrelspell/js/index.php
new file mode 100644 (file)
index 0000000..9b39265
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>
diff --git a/plugins/squirrelspell/js/init.js b/plugins/squirrelspell/js/init.js
new file mode 100644 (file)
index 0000000..ca2a368
--- /dev/null
@@ -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();
+}
diff --git a/plugins/squirrelspell/modules/WHATISTHIS b/plugins/squirrelspell/modules/WHATISTHIS
new file mode 100644 (file)
index 0000000..e764b11
--- /dev/null
@@ -0,0 +1,3 @@
+squirrelspell/modules
+
+This is where the loadable modules for SquirrelSpell are.
diff --git a/plugins/squirrelspell/modules/check_me.mod.php b/plugins/squirrelspell/modules/check_me.mod.php
new file mode 100644 (file)
index 0000000..0a65094
--- /dev/null
@@ -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);
+ }
+?>
diff --git a/plugins/squirrelspell/modules/crypto.mod.php b/plugins/squirrelspell/modules/crypto.mod.php
new file mode 100644 (file)
index 0000000..dce2980
--- /dev/null
@@ -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);
+?>
diff --git a/plugins/squirrelspell/modules/crypto_badkey.mod.php b/plugins/squirrelspell/modules/crypto_badkey.mod.php
new file mode 100644 (file)
index 0000000..2fd9409
--- /dev/null
@@ -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;
+ }
+?>
diff --git a/plugins/squirrelspell/modules/edit_dic.mod.php b/plugins/squirrelspell/modules/edit_dic.mod.php
new file mode 100644 (file)
index 0000000..2bb16ff
--- /dev/null
@@ -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);
+ }
+?>
diff --git a/plugins/squirrelspell/modules/enc_setup.mod.php b/plugins/squirrelspell/modules/enc_setup.mod.php
new file mode 100644 (file)
index 0000000..b70050a
--- /dev/null
@@ -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);
+?>
diff --git a/plugins/squirrelspell/modules/forget_me.mod.php b/plugins/squirrelspell/modules/forget_me.mod.php
new file mode 100644 (file)
index 0000000..73444a0
--- /dev/null
@@ -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>");
+ }
+?>
+
diff --git a/plugins/squirrelspell/modules/forget_me_not.mod.php b/plugins/squirrelspell/modules/forget_me_not.mod.php
new file mode 100644 (file)
index 0000000..0770ba9
--- /dev/null
@@ -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);
+?>
+
diff --git a/plugins/squirrelspell/modules/index.php b/plugins/squirrelspell/modules/index.php
new file mode 100644 (file)
index 0000000..9b39265
--- /dev/null
@@ -0,0 +1,3 @@
+<?php
+header("Location: http://www.mricon.com/");
+?>
diff --git a/plugins/squirrelspell/modules/init.mod.php b/plugins/squirrelspell/modules/init.mod.php
new file mode 100644 (file)
index 0000000..ade9c78
--- /dev/null
@@ -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);
+?> 
diff --git a/plugins/squirrelspell/modules/lang_change.mod.php b/plugins/squirrelspell/modules/lang_change.mod.php
new file mode 100644 (file)
index 0000000..dd83847
--- /dev/null
@@ -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);
+?>
diff --git a/plugins/squirrelspell/modules/lang_setup.mod.php b/plugins/squirrelspell/modules/lang_setup.mod.php
new file mode 100644 (file)
index 0000000..634db6e
--- /dev/null
@@ -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); 
+?>
diff --git a/plugins/squirrelspell/modules/options_main.mod.php b/plugins/squirrelspell/modules/options_main.mod.php
new file mode 100644 (file)
index 0000000..9ad26b0
--- /dev/null
@@ -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);
+
+?>
diff --git a/plugins/squirrelspell/setup.php b/plugins/squirrelspell/setup.php
new file mode 100644 (file)
index 0000000..671b63a
--- /dev/null
@@ -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
+}
+
+?>
diff --git a/plugins/squirrelspell/sqspell_config.dist b/plugins/squirrelspell/sqspell_config.dist
new file mode 100644 (file)
index 0000000..e0405b4
--- /dev/null
@@ -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";
+
+?>
diff --git a/plugins/squirrelspell/sqspell_config.php b/plugins/squirrelspell/sqspell_config.php
new file mode 100644 (file)
index 0000000..e0405b4
--- /dev/null
@@ -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";
+
+?>
diff --git a/plugins/squirrelspell/sqspell_functions.php b/plugins/squirrelspell/sqspell_functions.php
new file mode 100644 (file)
index 0000000..5eaa461
--- /dev/null
@@ -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";
+
+
+?>
diff --git a/plugins/squirrelspell/sqspell_interface.php b/plugins/squirrelspell/sqspell_interface.php
new file mode 100644 (file)
index 0000000..fa81e4f
--- /dev/null
@@ -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");
+?>
diff --git a/plugins/squirrelspell/sqspell_options.php b/plugins/squirrelspell/sqspell_options.php
new file mode 100644 (file)
index 0000000..86c75cd
--- /dev/null
@@ -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");
+?>
diff --git a/plugins/translate/INSTALL b/plugins/translate/INSTALL
new file mode 100644 (file)
index 0000000..945acc9
--- /dev/null
@@ -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
diff --git a/plugins/translate/README b/plugins/translate/README
new file mode 100644 (file)
index 0000000..b81fd1c
--- /dev/null
@@ -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
diff --git a/plugins/translate/options.php b/plugins/translate/options.php
new file mode 100644 (file)
index 0000000..9a702f2
--- /dev/null
@@ -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>
diff --git a/plugins/translate/setup.php b/plugins/translate/setup.php
new file mode 100644 (file)
index 0000000..f33cc19
--- /dev/null
@@ -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();
+}
+?>
index 01692c2d0e1c3f46e777ef2e9c2b3a4f199aba0b..1c3067147bd6ea3c3b61479d2ca6ec10713d89dc 100644 (file)
 <?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>
+?>