home *** CD-ROM | disk | FTP | other *** search
/ PC Open 101 / PC Open 101 CD 1.bin / CD1 / INTERNET / EMAIL / pop file / setup.exe / UI / HTML.pm next >
Encoding:
Perl POD Document  |  2004-09-28  |  116.1 KB  |  3,235 lines

  1. # POPFILE LOADABLE MODULE
  2. package UI::HTML;
  3.  
  4. #----------------------------------------------------------------------------
  5. #
  6. # This package contains an HTML UI for POPFile
  7. #
  8. # Copyright (c) 2001-2004 John Graham-Cumming
  9. #
  10. #   This file is part of POPFile
  11. #
  12. #   POPFile is free software; you can redistribute it and/or modify
  13. #   it under the terms of the GNU General Public License as published by
  14. #   the Free Software Foundation; either version 2 of the License, or
  15. #   (at your option) any later version.
  16. #
  17. #   POPFile is distributed in the hope that it will be useful,
  18. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. #   GNU General Public License for more details.
  21. #
  22. #   You should have received a copy of the GNU General Public License
  23. #   along with POPFile; if not, write to the Free Software
  24. #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  25. #
  26. #----------------------------------------------------------------------------
  27.  
  28. use UI::HTTP;
  29. @ISA = ("UI::HTTP");
  30.  
  31. use strict;
  32. use warnings;
  33. use locale;
  34.  
  35. use IO::Socket;
  36. use IO::Select;
  37. use Digest::MD5 qw( md5_hex );
  38. use HTML::Template;
  39. use Date::Format;
  40.  
  41. # A handy variable containing the value of an EOL for the network
  42.  
  43. my $eol = "\015\012";
  44.  
  45. # Constant used by the history deletion code
  46.  
  47. my $seconds_per_day = 60 * 60 * 24;
  48.  
  49. # These are used for Japanese support
  50.  
  51. # ASCII characters
  52. my $ascii = '[\x00-\x7F]';
  53.  
  54. # EUC-JP 2 byte characters
  55. my $two_bytes_euc_jp = '(?:[\x8E\xA1-\xFE][\xA1-\xFE])';
  56.  
  57. # EUC-JP 3 byte characters
  58. my $three_bytes_euc_jp = '(?:\x8F[\xA1-\xFE][\xA1-\xFE])';
  59.  
  60. # EUC-JP characters
  61. my $euc_jp = "(?:$ascii|$two_bytes_euc_jp|$three_bytes_euc_jp)";
  62.  
  63. my %headers_table = ( 'from',    'From',            # PROFILE BLOCK START
  64.                       'to',      'To',
  65.                       'cc',      'Cc',
  66.                       'subject', 'Subject',
  67.                       'date',    'Date',
  68.                       'inserted', 'Arrived',
  69.                       'size',    'Size',
  70.                       'bucket',  'Classification'); # PROFILE BLOCK STOP
  71.  
  72.  
  73. #----------------------------------------------------------------------------
  74. # new
  75. #
  76. #   Class new() function
  77. #----------------------------------------------------------------------------
  78. sub new
  79. {
  80.     my $type = shift;
  81.     my $self = UI::HTTP->new();
  82.  
  83.     # The classifier (Classifier::Bayes)
  84.  
  85.     $self->{c__}      = 0;
  86.  
  87.     # Session key to make the UI safer
  88.  
  89.     $self->{session_key__}     = '';
  90.  
  91.     # The available skins
  92.  
  93.     $self->{skins__}           = ();
  94.  
  95.     # A hash containing a mapping between alphanumeric identifiers and
  96.     # appropriate strings used for localization.  The string may
  97.     # contain sprintf patterns for use in creating grammatically
  98.     # correct strings, or simply be a string
  99.  
  100.     $self->{language__}        = {};
  101.  
  102.     # This is the list of available languages
  103.  
  104.     $self->{languages__} = ();
  105.  
  106.     # The last user to login via a proxy
  107.  
  108.     $self->{last_login__}      = '';
  109.  
  110.     # Used to determine whether the cache needs to be saved
  111.  
  112.     $self->{save_cache__}      = 0;
  113.  
  114.     # Stores a Classifier::Bayes session and is set up on the first UI
  115.     # connection
  116.  
  117.     $self->{api_session__}     = '';
  118.  
  119.     # Must call bless before attempting to call any methods
  120.  
  121.     bless $self, $type;
  122.  
  123.     # This is the HTML module which we know as the HTML module
  124.  
  125.     $self->name( 'html' );
  126.  
  127.     return $self;
  128. }
  129.  
  130. #----------------------------------------------------------------------------
  131. #
  132. # initialize
  133. #
  134. # Called to initialize the interface
  135. #
  136. #----------------------------------------------------------------------------
  137. sub initialize
  138. {
  139.     my ( $self ) = @_;
  140.  
  141.     $self->config_( 'port', 8080 );
  142.  
  143.     # Checking for updates if off by default
  144.  
  145.     $self->config_( 'update_check', 0 );
  146.  
  147.     # Sending of statistics is off
  148.  
  149.     $self->config_( 'send_stats', 0 );
  150.  
  151.     # The size of a history page
  152.  
  153.     $self->config_( 'page_size', 20 );
  154.  
  155.     # Only accept connections from the local machine for the UI
  156.  
  157.     $self->config_( 'local', 1 );
  158.  
  159.     # Use the default skin
  160.  
  161.     $self->config_( 'skin', 'default' );
  162.  
  163.     # The last time we checked for an update using the local epoch
  164.  
  165.     $self->config_( 'last_update_check', 0 );
  166.  
  167.     # The user interface password
  168.  
  169.     $self->config_( 'password', md5_hex( '__popfile__' ) );
  170.  
  171.     # The last time (textual) that the statistics were reset
  172.  
  173.     my $lt = localtime;
  174.     $self->config_( 'last_reset', $lt );
  175.  
  176.     # We start by assuming that the user speaks English like the
  177.     # perfidious Anglo-Saxons that we are... :-)
  178.  
  179.     $self->config_( 'language', 'English' );
  180.  
  181.     # If this is 1 then when the language is loaded we will use the
  182.     # language string identifier as the string shown in the UI.  This
  183.     # is used to test whether which identifiers are used where.
  184.  
  185.     $self->config_( 'test_language', 0 );
  186.  
  187.     # This setting defines what is displayed in the word matrix:
  188.     # 'freq' for frequencies, 'prob' for probabilities, 'score' for
  189.     # logarithmic scores, if blank then the word table is not shown
  190.  
  191.     $self->config_( 'wordtable_format', '' );
  192.  
  193.     # Controls whether to cache templates or not
  194.  
  195.     $self->config_( 'cache_templates', 0 );
  196.  
  197.     # Controls whether or not we die if a template variable is missing
  198.     # when we try to set it.  Setting it to 1 can be useful for debugging
  199.     # purposes
  200.  
  201.     $self->config_( 'strict_templates', 0 );
  202.  
  203.     # The default columns to show in the History page.  The order here
  204.     # is important, as is the presence of a + (show this column) or -
  205.     # (hide this column) in the value.  By default we show everything
  206.  
  207.     $self->config_( 'columns',
  208.         '+inserted,+from,+to,-cc,+subject,-date,-size,+bucket' );
  209.  
  210.     # An overriden date format set by the user, if empty then the
  211.     # Locale_Date from the language file is used (see pretty_date__)
  212.  
  213.     $self->config_( 'date_format', '' );
  214.  
  215.     # If you want session dividers
  216.  
  217.     $self->config_( 'session_dividers', 1 );
  218.  
  219.     # The number of characters to show in each column in the history, if set
  220.     # to 0 then POPFile tries to do this automatically
  221.  
  222.     $self->config_( 'column_characters', 0 );
  223.  
  224.     # Two variables that tell us whether to show help items
  225.     # concerning bucket setup and training. The bucket item
  226.     # is displayed by default, when it is turned off, the
  227.     # training item is shown.
  228.  
  229.     $self->config_( 'show_training_help', 0 );
  230.     $self->config_( 'show_bucket_help', 1 );
  231.  
  232.     # Load skins
  233.  
  234.     $self->load_skins__();
  235.  
  236.     # Load the list of available user interface languages
  237.  
  238.     $self->load_languages__();
  239.  
  240.     # Calculate a session key
  241.  
  242.     $self->change_session_key__();
  243.  
  244.     # The parent needs a reference to the url handler function
  245.  
  246.     $self->{url_handler_} = \&url_handler__;
  247.  
  248.     # Finally register for the messages that we need to receive
  249.  
  250.     $self->mq_register_( 'UIREG', $self );
  251.     $self->mq_register_( 'LOGIN', $self );
  252.  
  253.     $self->calculate_today();
  254.  
  255.     return 1;
  256. }
  257.  
  258. #----------------------------------------------------------------------------
  259. #
  260. # start
  261. #
  262. # Called to start the HTML interface running
  263. #
  264. #----------------------------------------------------------------------------
  265. sub start
  266. {
  267.     my ( $self ) = @_;
  268.  
  269.     # In pre v0.21.0 POPFile the UI password was stored in plaintext
  270.     # in the configuration data.  Check to see if the password is not
  271.     # a hash and upgrade it automatically here.
  272.  
  273.     if ( length( $self->config_( 'password' ) ) != 32 ) {
  274.         $self->config_( 'password',
  275.              md5_hex( '__popfile__' . $self->config_( 'password' ) ) );
  276.     }
  277.  
  278.     # Get a query session with the History object
  279.  
  280.     $self->{q__} = $self->{history__}->start_query();
  281.  
  282.     # Ensure that the messages subdirectory exists
  283.  
  284.     if ( !$self->{history__}->make_directory__(
  285.         $self->get_user_path_( $self->global_config_( 'msgdir' ) ) ) ) {
  286.         print STDERR "Failed to create the messages subdirectory\n";
  287.         return 0;
  288.     }
  289.  
  290.     # Load the current configuration from disk and then load up the
  291.     # appropriate language, note that we always load English first
  292.     # so that any extensions to the user interface that have not yet
  293.     # been translated will still appear
  294.  
  295.     $self->load_language( 'English' );
  296.     if ( $self->config_( 'language' ) ne 'English' ) {
  297.         $self->load_language( $self->config_( 'language' ) );
  298.     }
  299.  
  300.     # Set the classifier option wmformat__ according to our wordtable_format
  301.     # option.
  302.  
  303.     $self->{c__}->wmformat( $self->config_( 'wordtable_format' ) );
  304.  
  305.     return $self->SUPER::start();
  306. }
  307.  
  308. #----------------------------------------------------------------------------
  309. #
  310. # stop
  311. #
  312. # Called to stop the HTML interface running
  313. #
  314. #----------------------------------------------------------------------------
  315. sub stop
  316. {
  317.     my ( $self ) = @_;
  318.  
  319.     if ( $self->{api_session__} ne '' ) {
  320.         $self->{c__}->release_session_key( $self->{api_session__} );
  321.     }
  322.  
  323.     $self->{history__}->stop_query( $self->{q__} );
  324.  
  325.     $self->SUPER::stop();
  326. }
  327.  
  328. #----------------------------------------------------------------------------
  329. #
  330. # deliver
  331. #
  332. # Called by the message queue to deliver a message
  333. #
  334. # There is no return value from this method
  335. #
  336. #----------------------------------------------------------------------------
  337. sub deliver
  338. {
  339.     my ( $self, $type, @message ) = @_;
  340.  
  341.     # Handle registration of UI components
  342.  
  343.     if ( $type eq 'UIREG' ) {
  344.         $self->register_configuration_item__( @message );
  345.     }
  346.  
  347.     if ( $type eq 'LOGIN' ) {
  348.         $self->{last_login__} = $message[0];
  349.     }
  350. }
  351.  
  352. #----------------------------------------------------------------------------
  353. #
  354. # url_handler__ - Handle a URL request
  355. #
  356. # $client     The web browser to send the results to
  357. # $url        URL to process
  358. # $command    The HTTP command used (GET or POST)
  359. # $content    Any non-header data in the HTTP command
  360. #
  361. # Checks the session key and refuses access unless it matches.  Serves
  362. # up a small set of specific urls that are the main UI pages and then
  363. # any GIF file in the POPFile directory and CSS files in the skins
  364. # subdirectory
  365. #
  366. #----------------------------------------------------------------------------
  367. sub url_handler__
  368. {
  369.     my ( $self, $client, $url, $command, $content ) = @_;
  370.  
  371.     # Check to see if we obtained the session key yet
  372.     if ( $self->{api_session__} eq '' ) {
  373.         $self->{api_session__} = $self->{c__}->get_session_key(
  374.             'admin', '' );
  375.     }
  376.  
  377.     # See if there are any form parameters and if there are parse them
  378.     # into the %form hash
  379.  
  380.     delete $self->{form_};
  381.  
  382.     # Remove a # element
  383.  
  384.     $url =~ s/#.*//;
  385.  
  386.     # If the URL was passed in through a GET then it may contain form
  387.     # arguments separated by & signs, which we parse out into the
  388.     # $self->{form_} where the key is the argument name and the value
  389.     # the argument value, for example if you have foo=bar in the URL
  390.     # then $self->{form_}{foo} is bar.
  391.  
  392.     if ( $command =~ /GET/i ) {
  393.         if ( $url =~ s/\?(.*)// )  {
  394.             $self->parse_form_( $1 );
  395.         }
  396.     }
  397.  
  398.     # If the URL was passed in through a POST then look for the POST data
  399.     # and parse it filling the $self->{form_} in the same way as for GET
  400.     # arguments
  401.  
  402.     if ( $command =~ /POST/i ) {
  403.         $content =~ s/[\r\n]//g;
  404.         $self->parse_form_( $content );
  405.     }
  406.  
  407.     if ( $url =~ /\/autogen_(.+)\.bmp/ ) {
  408.         $self->bmp_file__( $client, $1 );
  409.         return 1;
  410.     }
  411.  
  412.     if ( $url =~ /\/(.+\.gif)/ ) {
  413.         $self->http_file_( $client, $self->get_root_path_( $1 ), 'image/gif' );
  414.         return 1;
  415.     }
  416.  
  417.     if ( $url =~ /\/(.+\.png)/ ) {
  418.         $self->http_file_( $client, $self->get_root_path_( $1 ), 'image/png' );
  419.         return 1;
  420.     }
  421.  
  422.     if ( $url =~ /\/(.+\.ico)/ ) {
  423.         $self->http_file_( $client, $self->get_root_path_( $1 ),
  424.              'image/x-icon' );
  425.         return 1;
  426.     }
  427.  
  428.     if ( $url =~ /(skins\/.+\.css)/ ) {
  429.         $self->http_file_( $client, $self->get_root_path_( $1 ), 'text/css' );
  430.         return 1;
  431.     }
  432.  
  433.     if ( $url =~ /(manual\/.+\.html)/ ) {
  434.         $self->http_file_( $client, $self->get_root_path_( $1 ), 'text/html' );
  435.         return 1;
  436.     }
  437.  
  438.     # Check the password
  439.  
  440.     if ( $url eq '/password' )  {
  441.         if ( md5_hex( '__popfile__' . $self->{form_}{password} ) eq
  442.              $self->config_( 'password' ) )  {
  443.             $self->change_session_key__( $self );
  444.             delete $self->{form_}{password};
  445.             $self->{form_}{session} = $self->{session_key__};
  446.             if ( defined( $self->{form_}{redirect} ) ) {
  447.                 $url = $self->{form_}{redirect};
  448.                 if ( $url =~ s/\?(.*)// )  {
  449.                     $self->parse_form_( $1 );
  450.                 }
  451.             }
  452.         } else {
  453.             $self->password_page( $client, 1, '/' );
  454.             return 1;
  455.         }
  456.     }
  457.  
  458.     # If there's a password defined then check to see if the user
  459.     # already knows the session key, if they don't then drop to the
  460.     # password screen
  461.  
  462.     if ( ( (!defined($self->{form_}{session})) ||
  463.            ($self->{form_}{session} eq '' ) ||
  464.            ( $self->{form_}{session} ne $self->{session_key__} ) ) &&
  465.            ( $self->config_( 'password' ) ne md5_hex( '__popfile__' ) ) ) {
  466.  
  467.         # Since the URL that has caused us to hit the password page
  468.         # might have information stored in the form hash we need to
  469.         # extract it out (except for the session key) and package it
  470.         # up so that the password page can redirect to the right place
  471.         # if the correct password is entered. This is especially
  472.         # important for the XPL functionality.
  473.  
  474.         my $redirect_url = $url . '?';
  475.  
  476.         foreach my $k (keys %{$self->{form_}}) {
  477.  
  478.             # Skip the session key since we are in the process of
  479.             # assigning a new one through the password page
  480.  
  481.             if ( $k ne 'session' ) {
  482.  
  483.                 # If we are dealing with an array of values (see
  484.                 # parse_form_ for details) then we need to unpack it
  485.                 # into separate entries)
  486.  
  487.                 if ( $k =~ /^(.+)_array$/ ) {
  488.                     my $field = $1;
  489.  
  490.                     foreach my $v (@{$self->{form_}{$k}}) {
  491.                         $redirect_url .= "$field=$v&"
  492.                     }
  493.                 } else {
  494.                     $redirect_url .= "$k=$self->{form_}{$k}&"
  495.                 }
  496.             }
  497.         }
  498.  
  499.         $redirect_url =~ s/&$//;
  500.  
  501.         $self->password_page( $client, 0, $redirect_url );
  502.  
  503.         return 1;
  504.     }
  505.  
  506.     if ( $url eq '/jump_to_message' )  {
  507.         $self->{form_}{filter}    = '';
  508.         $self->{form_}{negate}    = '';
  509.         $self->{form_}{search}    = '';
  510.         $self->{form_}{setsearch} = 1;
  511.  
  512.         my $slot = $self->{form_}{view};
  513.  
  514.         if ( ( $slot =~ /^\d+$/ ) &&
  515.              ( $self->{history__}->is_valid_slot( $slot ) ) ) {
  516.             $self->http_redirect_( $client,
  517.                  "/view?session=$self->{session_key__}&view=$slot" );
  518.         } else {
  519.             $self->http_redirect_( $client, "/history" );
  520.         }
  521.  
  522.         return 1;
  523.     }
  524.  
  525.     if ( $url =~ /(popfile.*\.log)/ ) {
  526.         $self->http_file_( $client, $self->logger()->debug_filename(),
  527.             'text/plain' );
  528.         return 1;
  529.     }
  530.  
  531.     if ( ( defined($self->{form_}{session}) ) &&
  532.          ( $self->{form_}{session} ne $self->{session_key__} ) ) {
  533.         $self->session_page( $client, 0, $url );
  534.         return 1;
  535.     }
  536.  
  537.     if ( ( $url eq '/' ) || (!defined($self->{form_}{session})) ) {
  538.         delete $self->{form_};
  539.     }
  540.  
  541.     if ( $url eq '/shutdown' )  {
  542.         my $http_header = "HTTP/1.1 200 OK\r\n";
  543.         $http_header .= "Connection: close\r\n";
  544.         $http_header .= "Pragma: no-cache\r\n";
  545.         $http_header .= "Expires: 0\r\n";
  546.         $http_header .= "Cache-Control: no-cache\r\n";
  547.         $http_header .= "Content-Type: text/html";
  548.         $http_header .= "; charset=$self->{language__}{LanguageCharset}\r\n";
  549.         $http_header .= "Content-Length: ";
  550.  
  551.         my $text = $self->shutdown_page__();
  552.  
  553.         $http_header .= length($text);
  554.         $http_header .= "$eol$eol";
  555.  
  556.         if ( $client->connected ) {
  557.             print $client $http_header . $text;
  558.         }
  559.         return 0;
  560.     }
  561.  
  562.     # Watch out for clicks on the "Don't show me this again." buttons.
  563.     # If that button is clicked for the bucket-setup item, we turn on
  564.     # the training help item. And if this one is clicked away, both
  565.     # will no longer be shown.
  566.  
  567.     if ( exists $self->{form_}{nomore_bucket_help} &&
  568.          $self->{form_}{nomore_bucket_help} ) {
  569.         $self->config_( 'show_bucket_help', 0 );
  570.         $self->config_( 'show_training_help', 1 );
  571.     }
  572.  
  573.     if ( exists $self->{form_}{nomore_training_help} &&
  574.          $self->{form_}{nomore_training_help} ) {
  575.         $self->config_( 'show_training_help', 0 );
  576.     }
  577.  
  578.     # The url table maps URLs that we might receive to pages that we
  579.     # display, the page table maps the pages to the functions that
  580.     # handle them and the related template
  581.  
  582.     my %page_table = ( 'security'      => [ \&security_page,      'security-page.thtml'      ],       # PROFILE BLOCK START
  583.                        'configuration' => [ \&configuration_page, 'configuration-page.thtml' ],
  584.                        'buckets'       => [ \&corpus_page,        'corpus-page.thtml'        ],
  585.                        'magnets'       => [ \&magnet_page,        'magnet-page.thtml'        ],
  586.                        'advanced'      => [ \&advanced_page,      'advanced-page.thtml'      ],
  587.                        'history'       => [ \&history_page,       'history-page.thtml'       ],
  588.                        'view'          => [ \&view_page,          'view-page.thtml'          ] );     # PROFILE BLOCK STOP
  589.  
  590.     my %url_table = ( '/security'      => 'security',       # PROFILE BLOCK START
  591.                       '/configuration' => 'configuration',
  592.                       '/buckets'       => 'buckets',
  593.                       '/magnets'       => 'magnets',
  594.                       '/advanced'      => 'advanced',
  595.                       '/view'          => 'view',
  596.                       '/history'       => 'history',
  597.                       '/'              => 'history' );      # PROFILE BLOCK STOP
  598.  
  599.     # Any of the standard pages can be found in the url_table, the
  600.     # other pages are probably files on disk
  601.  
  602.     if ( defined($url_table{$url}) )  {
  603.         my ( $method, $template ) = @{$page_table{$url_table{$url}}};
  604.  
  605.         if ( !defined( $self->{api_session__} ) ) {
  606.             $self->http_error_( $client, 500 );
  607.             return;
  608.         }
  609.  
  610.         &{$method}( $self, $client, $self->load_template__( $template ) );
  611.         return 1;
  612.     }
  613.  
  614.     $self->http_error_( $client, 404 );
  615.     return 1;
  616. }
  617.  
  618. #---------------------------------------------------------------------------
  619. #
  620. # bmp_file__ - Sends a 1x1 bitmap of a specific color to the browser
  621. #
  622. # $client    The web browser to send result to
  623. # $color     An HTML color (hex or named)
  624. #
  625. #----------------------------------------------------------------------------
  626. sub bmp_file__
  627. {
  628.     my ( $self, $client, $color ) = @_;
  629.  
  630.     $color = lc($color);
  631.  
  632.     # TODO: this is dirty something higher up (HTTP) should be decoding the URL
  633.  
  634.     $color =~ s/^%23//; # if we have an prefixed hex color value,
  635.                         # just dump the encoded hash-mark (#)
  636.  
  637.     # If the color contains something other than hex then do a map
  638.     # on it first and then get the hex color, from the hex color
  639.     # create a BMP file and return it
  640.  
  641.     if ( $color !~ /^[0-9a-f]{6}$/ ) {
  642.         $color = $self->{c__}->{parser__}->map_color( $color );
  643.     }
  644.  
  645.  
  646.     if ( $color =~ /^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/ ) {
  647.         my $bmp = '424d3a0000000000000036000000280000000100000001000000010018000000000004000000eb0a0000eb0a00000000000000000000' . "$3$2$1" . '00';
  648.         my $file = '';
  649.         for my $i (0..length($bmp)/2-1) {
  650.             $file .= chr(hex(substr($bmp,$i*2,2)));
  651.         }
  652.         my $http_header = "HTTP/1.1 200 OK\r\n";
  653.         $http_header .= "Connection: close\r\n";
  654.         $http_header .= "Pragma: no-cache\r\n";
  655.         $http_header .= "Expires: 0\r\n";
  656.         $http_header .= "Cache-Control: no-cache\r\n";
  657.         $http_header .= "Content-Type: image/bmp\r\n";
  658.         $http_header .= "Content-Length: ";
  659.         $http_header .= length($file);
  660.         $http_header .= "$eol$eol";
  661.  
  662.         if ( $client->connected ) {
  663.             print $client $http_header . $file;
  664.         }
  665.         return 0;
  666.     } else {
  667.         return $self->http_error_( $client, 404 );
  668.     }
  669. }
  670.  
  671. #---------------------------------------------------------------------------
  672. #
  673. # http_ok - Output a standard HTTP 200 message with a body of data
  674. # from a template
  675. #
  676. # $client    The web browser to send result to
  677. # $templ     The template for the page to return
  678. # $selected  Which tab is to be selected
  679. #
  680. #----------------------------------------------------------------------------
  681. sub http_ok
  682. {
  683.     my ( $self, $client, $templ, $selected ) = @_;
  684.  
  685.     $selected = -1 if ( !defined( $selected ) );
  686.  
  687.     my @tab = ( 'menuStandard', 'menuStandard', 'menuStandard', 'menuStandard', 'menuStandard', 'menuStandard' );
  688.     $tab[$selected] = 'menuSelected' if ( ( $selected <= $#tab ) && ( $selected >= 0 ) );
  689.  
  690.     for my $i (0..$#tab) {
  691.         $templ->param( "Common_Middle_Tab$i" => $tab[$i] );
  692.     }
  693.  
  694.     my $update_check = '';
  695.  
  696.     # Check to see if we've checked for updates today.  If we have not
  697.     # then insert a reference to an image that is generated through a
  698.     # CGI on UseTheSource.  Also send stats to the same site if that
  699.     # is allowed
  700.  
  701.     if ( $self->{today__} ne $self->config_( 'last_update_check' ) ) {
  702.         $self->calculate_today();
  703.  
  704.         if ( $self->config_( 'update_check' ) ) {
  705.             my ( $major_version, $minor_version, $build_version ) =
  706.                 $self->version() =~ /^v([^.]*)\.([^.]*)\.(.*)$/;
  707.             $templ->param( 'Common_Middle_If_UpdateCheck' => 1 );
  708.             $templ->param( 'Common_Middle_Major_Version' => $major_version );
  709.             $templ->param( 'Common_Middle_Minor_Version' => $minor_version );
  710.             $templ->param( 'Common_Middle_Build_Version' => $build_version );
  711.         }
  712.  
  713.         if ( $self->config_( 'send_stats' ) ) {
  714.             $templ->param( 'Common_Middle_If_SendStats' => 1 );
  715.             my @buckets = $self->{c__}->get_buckets(
  716.                 $self->{api_session__} );
  717.             my $bc      = $#buckets + 1;
  718.             $templ->param( 'Common_Middle_Buckets'  => $bc );
  719.             $templ->param( 'Common_Middle_Messages' => $self->mcount__() );
  720.             $templ->param( 'Common_Middle_Errors'   => $self->ecount__() );
  721.         }
  722.  
  723.         $self->config_( 'last_update_check', $self->{today__}, 1 );
  724.     }
  725.  
  726.     # Build an HTTP header for standard HTML
  727.  
  728.     my $http_header = "HTTP/1.1 200 OK\r\n";
  729.     $http_header .= "Connection: close\r\n";
  730.     $http_header .= "Pragma: no-cache\r\n";
  731.     $http_header .= "Expires: 0\r\n";
  732.     $http_header .= "Cache-Control: no-cache\r\n";
  733.     $http_header .= "Content-Type: text/html";
  734.     $http_header .= "; charset=$self->{language__}{LanguageCharset}\r\n";
  735.     $http_header .= "Content-Length: ";
  736.  
  737.     my $text = $templ->output;
  738.  
  739.     $http_header .= length($text);
  740.     $http_header .= "$eol$eol";
  741.  
  742.     if ( $client->connected ) {
  743.         $client->print( $http_header . $text );
  744.     }
  745. }
  746.  
  747. #----------------------------------------------------------------------------
  748. #
  749. # configuration_page - get the configuration options
  750. #
  751. # $client     The web browser to send the results to
  752. #
  753. #----------------------------------------------------------------------------
  754. sub configuration_page
  755. {
  756.     my ( $self, $client, $templ ) = @_;
  757.  
  758.     if ( defined($self->{form_}{skin}) ) {
  759.         $self->config_( 'skin', $self->{form_}{skin} );
  760.         $templ = $self->load_template__( 'configuration-page.thtml' );
  761.     }
  762.  
  763.    if ( ( defined($self->{form_}{debug}) ) &&
  764.         ( ( $self->{form_}{debug} >= 1 ) &&
  765.         ( $self->{form_}{debug} <= 4 ) ) ) {
  766.        $self->global_config_( 'debug', $self->{form_}{debug}-1 );
  767.    }
  768.  
  769.     if ( defined($self->{form_}{language}) ) {
  770.         if ( $self->config_( 'language' ) ne $self->{form_}{language} ) {
  771.             $self->config_( 'language', $self->{form_}{language} );
  772.             if ( $self->config_( 'language' ) ne 'English' ) {
  773.                 $self->load_language( 'English' );
  774.             }
  775.             $self->load_language( $self->config_( 'language' ) );
  776.  
  777.             # Force a template relocalization because the language has been
  778.             # changed which changes the localization of the template
  779.  
  780.             $self->localize_template__( $templ );
  781.         }
  782.     }
  783.  
  784.     # Load all of the templates that are needed for the dynamic parts of
  785.     # the configuration page, and for each one call its validation interface
  786.     # so that any error messages or informational messages are fixed up
  787.     # first
  788.  
  789.     my %dynamic_templates;
  790.  
  791.     for my $name (keys %{$self->{dynamic_ui__}{configuration}}) {
  792.         $dynamic_templates{$name} = $self->load_template__(
  793.             $self->{dynamic_ui__}{configuration}{$name}{template} );
  794.         $self->{dynamic_ui__}{configuration}{$name}{object}->validate_item(
  795.             $name,
  796.             $dynamic_templates{$name},
  797.             \%{$self->{language__}},
  798.             \%{$self->{form_}} );
  799.     }
  800.  
  801.     if ( defined($self->{form_}{ui_port}) ) {
  802.         if ( ( $self->{form_}{ui_port} >= 1 ) &&
  803.              ( $self->{form_}{ui_port} < 65536 ) ) {
  804.             $self->config_( 'port', $self->{form_}{ui_port} );
  805.         } else {
  806.             $templ->param( 'Configuration_If_UI_Port_Error' => 1 );
  807.             delete $self->{form_}{ui_port};
  808.         }
  809.     }
  810.  
  811.     if ( defined($self->{form_}{ui_port} ) ) {
  812.         $templ->param( 'Configuration_UI_Port_Updated' =>
  813.             sprintf( $self->{language__}{Configuration_UIUpdate},
  814.                 $self->config_( 'port' ) ) );
  815.     }
  816.     $templ->param( 'Configuration_UI_Port' => $self->config_( 'port' ) );
  817.  
  818.     if ( defined($self->{form_}{page_size}) ) {
  819.         if ( ( $self->{form_}{page_size} >= 1 ) &&
  820.              ( $self->{form_}{page_size} <= 1000 ) ) {
  821.             $self->config_( 'page_size', $self->{form_}{page_size} );
  822.         } else {
  823.             $templ->param( 'Configuration_If_Page_Size_Error' => 1 );
  824.             delete $self->{form_}{page_size};
  825.         }
  826.     }
  827.  
  828.     if ( defined($self->{form_}{page_size} ) ) {
  829.         $templ->param( 'Configuration_Page_Size_Updated' =>
  830.             sprintf( $self->{language__}{Configuration_HistoryUpdate},
  831.                 $self->config_( 'page_size' ) ) )
  832.     }
  833.     $templ->param( 'Configuration_Page_Size' =>
  834.         $self->config_( 'page_size' ) );
  835.  
  836.     if ( defined($self->{form_}{history_days}) ) {
  837.         if ( ( $self->{form_}{history_days} >= 1 ) &&
  838.              ( $self->{form_}{history_days} <= 366 ) ) {
  839.             $self->module_config_( 'history', 'history_days',
  840.                 $self->{form_}{history_days} );
  841.         } else {
  842.             $templ->param( 'Configuration_If_History_Days_Error' => 1 );
  843.             delete $self->{form_}{history_days};
  844.         }
  845.  
  846.         if ( defined( $self->{form_}{purge_history} ) ) {
  847.              $self->{history__}->cleanup_history();
  848.         }
  849.     }
  850.  
  851.     $templ->param( 'Configuration_History_Days_Updated' => sprintf( $self->{language__}{Configuration_DaysUpdate}, $self->module_config_( 'history', 'history_days' ) ) ) if ( defined($self->{form_}{history_days} ) );
  852.     $templ->param( 'Configuration_History_Days' => $self->module_config_( 'history', 'history_days' ) );
  853.  
  854.     if ( defined($self->{form_}{timeout}) ) {
  855.         if ( ( $self->{form_}{timeout} >= 10 ) && ( $self->{form_}{timeout} <= 300 ) ) {
  856.             $self->global_config_( 'timeout', $self->{form_}{timeout} );
  857.         } else {
  858.             $templ->param( 'Configuration_If_TCP_Timeout_Error' => 1 );
  859.             delete $self->{form_}{timeout};
  860.         }
  861.     }
  862.  
  863.     $templ->param( 'Configuration_TCP_Timeout_Updated' => sprintf( $self->{language__}{Configuration_TCPTimeoutUpdate}, $self->global_config_( 'timeout' ) ) ) if ( defined($self->{form_}{timeout} ) );
  864.     $templ->param( 'Configuration_TCP_Timeout' => $self->global_config_( 'timeout' ) );
  865.  
  866.     if ( defined( $self->{form_}{update_fields} ) ) {
  867.         my @columns = split(',', $self->config_( 'columns' ));
  868.         my $new_columns = '';
  869.         foreach my $column (@columns) {
  870.             $column =~ s/^(\+|\-)//;
  871.             if ( defined($self->{form_}{$column})) {
  872.                 $new_columns .= '+';
  873.             } else {
  874.                 $new_columns .= '-';
  875.             }
  876.             $new_columns .= $column;
  877.             $new_columns .= ',';
  878.         }
  879.         $self->config_( 'columns', $new_columns );
  880.     }
  881.  
  882.     my ( @general_skins, @small_skins, @tiny_skins );
  883.     for my $i (0..$#{$self->{skins__}}) {
  884.         my %row_data;
  885.         my $type = 'General';
  886.         my $list = \@general_skins;
  887.         my $name = $self->{skins__}[$i];
  888.         $name =~ /\/([^\/]+)\/$/;
  889.         $name = $1;
  890.         my $selected = ( $name eq $self->config_( 'skin' ) )?'selected':'';
  891.  
  892.         if ( $name =~ /tiny/ ) {
  893.             $type = 'Tiny';
  894.             $list = \@tiny_skins;
  895.         } else {
  896.             if ( $name =~ /small/ ) {
  897.                 $type = 'Small';
  898.                 $list = \@small_skins;
  899.             }
  900.         }
  901.  
  902.         $row_data{"Configuration_$type" . '_Skin'}     = $name;
  903.         $row_data{"Configuration_$type" . '_Selected'} = $selected;
  904.  
  905.         push ( @$list, \%row_data );
  906.     }
  907.     $templ->param( "Configuration_Loop_General_Skins", \@general_skins );
  908.     $templ->param( "Configuration_Loop_Small_Skins",   \@small_skins   );
  909.     $templ->param( "Configuration_Loop_Tiny_Skins",    \@tiny_skins    );
  910.  
  911.     my @language_loop;
  912.     foreach my $lang (@{$self->{languages__}}) {
  913.         my %row_data;
  914.         $row_data{Configuration_Language} = $lang;
  915.         $row_data{Configuration_Selected_Language} = ( $lang eq $self->config_( 'language' ) )?'selected':'';
  916.         push ( @language_loop, \%row_data );
  917.     }
  918.     $templ->param( 'Configuration_Loop_Languages' => \@language_loop );
  919.  
  920.     my @columns = split(',', $self->config_( 'columns' ));
  921.     my @column_data;
  922.     foreach my $column (@columns) {
  923.         my %row;
  924.         $column =~ /(\+|\-)/;
  925.         my $selected = ($1 eq '+')?'checked':'';
  926.         $column =~ s/^.//;
  927.         $row{Configuration_Field_Name} = $column;
  928.         $row{Configuration_Localized_Field_Name} =
  929.             $self->{language__}{$headers_table{$column}};
  930.         $row{Configuration_Field_Value} = $selected;
  931.         push ( @column_data, \%row );
  932.     }
  933.     $templ->param( 'Configuration_Loop_History_Columns' => \@column_data );
  934.  
  935.     # Insert all the items that are dynamically created from the
  936.     # modules that are loaded
  937.  
  938.     my $configuration_html = '';
  939.     my $last_module = '';
  940.     for my $name (sort keys %{$self->{dynamic_ui__}{configuration}}) {
  941.         $name =~ /^([^_]+)_/;
  942.         my $module = $1;
  943.         if ( $last_module ne $module ) {
  944.             $last_module = $module;
  945.             $configuration_html .= "<hr>\n<h2 class=\"configuration\">";
  946.             $configuration_html .= uc($module);
  947.             $configuration_html .= "</h2>\n";
  948.         }
  949.         $self->{dynamic_ui__}{configuration}{$name}{object}->configure_item(
  950.             $name, $dynamic_templates{$name}, \%{$self->{language__}} );
  951.         $configuration_html .= $dynamic_templates{$name}->output;
  952.     }
  953.  
  954.     $templ->param( 'Configuration_Dynamic' => $configuration_html );
  955.     $templ->param( 'Configuration_Debug_' . ( $self->global_config_( 'debug' ) + 1 ) . '_Selected' => 'selected' );
  956.  
  957.     if ( $self->global_config_( 'debug' ) & 1 ) {
  958.         $templ->param( 'Configuration_If_Show_Log' => 1 );
  959.     }
  960.  
  961.     $self->http_ok( $client, $templ, 3 );
  962. }
  963.  
  964. #----------------------------------------------------------------------------
  965. #
  966. # security_page - get the security configuration page
  967. #
  968. # $client     The web browser to send the results to
  969. #
  970. #----------------------------------------------------------------------------
  971. sub security_page
  972. {
  973.     my ( $self, $client, $templ ) = @_;
  974.  
  975.     my $server_error = '';
  976.     my $port_error   = '';
  977.  
  978.     if ( ( defined($self->{form_}{password}) ) &&
  979.          ( $self->{form_}{password} ne $self->config_( 'password' ) ) ) {
  980.         $self->config_( 'password', md5_hex( '__popfile__' . $self->{form_}{password} ) )
  981.     }
  982.     $self->config_( 'local', $self->{form_}{localui}-1 )      if ( defined($self->{form_}{localui}) );
  983.     $self->config_( 'update_check', $self->{form_}{update_check}-1 ) if ( defined($self->{form_}{update_check}) );
  984.     $self->config_( 'send_stats', $self->{form_}{send_stats}-1 )   if ( defined($self->{form_}{send_stats}) );
  985.  
  986.     $templ->param( 'Security_If_Local' => ( $self->config_( 'local' ) == 1 ) );
  987.     $templ->param( 'Security_Password' => ( $self->config_( 'password' ) eq md5_hex( '__popfile__' ) )?'':$self->config_( 'password' ) );
  988.     $templ->param( 'Security_If_Password_Updated' => ( defined($self->{form_}{password} ) ) );
  989.     $templ->param( 'Security_If_Update_Check' => ( $self->config_( 'update_check' ) == 1 ) );
  990.     $templ->param( 'Security_If_Send_Stats' => ( $self->config_( 'send_stats' ) == 1 ) );
  991.  
  992.     my %security_templates;
  993.  
  994.     for my $name (keys %{$self->{dynamic_ui__}{security}}) {
  995.         $security_templates{$name} = $self->load_template__( $self->{dynamic_ui__}{security}{$name}{template} );
  996.         $self->{dynamic_ui__}{security}{$name}{object}->validate_item( $name,
  997.                                                                        $security_templates{$name},
  998.                                                                        \%{$self->{language__}},
  999.                                                                        \%{$self->{form_}} );
  1000.     }
  1001.  
  1002.     my %chain_templates;
  1003.  
  1004.     for my $name (keys %{$self->{dynamic_ui__}{chain}}) {
  1005.         $chain_templates{$name} = $self->load_template__( $self->{dynamic_ui__}{chain}{$name}{template} );
  1006.         $self->{dynamic_ui__}{chain}{$name}{object}->validate_item( $name,
  1007.                                                                     $chain_templates{$name},
  1008.                                                                     \%{$self->{language__}},
  1009.                                                                     \%{$self->{form_}} );
  1010.     }
  1011.  
  1012.     my $security_html = '';
  1013.  
  1014.     for my $name (sort keys %{$self->{dynamic_ui__}{security}}) {
  1015.         $self->{dynamic_ui__}{security}{$name}{object}->configure_item(
  1016.             $name, $security_templates{$name}, \%{$self->{language__}} );
  1017.         $security_html .= $security_templates{$name}->output;
  1018.     }
  1019.  
  1020.     my $chain_html = '';
  1021.  
  1022.     for my $name (sort keys %{$self->{dynamic_ui__}{chain}}) {
  1023.         $self->{dynamic_ui__}{chain}{$name}{object}->configure_item(
  1024.             $name, $chain_templates{$name}, \%{$self->{language__}} );
  1025.         $chain_html .= $chain_templates{$name}->output;
  1026.     }
  1027.  
  1028.     $templ->param( 'Security_Dynamic_Security' => $security_html );
  1029.     $templ->param( 'Security_Dynamic_Chain'    => $chain_html    );
  1030.  
  1031.     $self->http_ok( $client,$templ, 4 );
  1032. }
  1033.  
  1034. #----------------------------------------------------------------------------
  1035. #
  1036. # pretty_number - format a number with ,s every 1000
  1037. #
  1038. # $number       The number to format
  1039. #
  1040. #----------------------------------------------------------------------------
  1041. sub pretty_number
  1042. {
  1043.     my ( $self, $number ) = @_;
  1044.  
  1045.     my $c = reverse $self->{language__}{Locale_Thousands};
  1046.  
  1047.     $number = reverse $number;
  1048.     $number =~ s/(\d{3})/$1$c/g;
  1049.     $number = reverse $number;
  1050.     $c =~ s/\./\\./g;
  1051.     $number =~ s/^$c(.*)/$1/;
  1052.  
  1053.     return $number;
  1054. }
  1055.  
  1056. #----------------------------------------------------------------------------
  1057. #
  1058. # pretty_date__ - format a date as the user wants to see it
  1059. #
  1060. # $date           Epoch seconds
  1061. # $long           Set to 1 if you want only the long date option
  1062. #
  1063. #----------------------------------------------------------------------------
  1064. sub pretty_date__
  1065. {
  1066.     my ( $self, $date, $long ) = @_;
  1067.  
  1068.     $long = 0 if ( !defined( $long ) );
  1069.     my $format = $self->config_( 'date_format' );
  1070.  
  1071.     if ( $format eq '' ) {
  1072.         $format = $self->{language__}{Locale_Date};
  1073.     }
  1074.  
  1075.     if ( $format =~ /[\t ]*(.+)[\t ]*\|[\t ]*(.+)/ ) {
  1076.         if ( ( $date < time ) &&
  1077.              ( $date > ( time - ( 7 * 24 * 60 * 60 ) ) ) ) {
  1078.             if ( $long ) {
  1079.                 return time2str( $2, $date );
  1080.             } else {
  1081.                 return time2str( $1, $date );
  1082.             }
  1083.         } else {
  1084.             return time2str( $2, $date );
  1085.         }
  1086.     } else {
  1087.         return time2str( $format, $date );
  1088.     }
  1089. }
  1090.  
  1091. #----------------------------------------------------------------------------
  1092. #
  1093. # advanced_page - very advanced configuration options
  1094. #
  1095. # $client     The web browser to send the results to
  1096. #
  1097. #----------------------------------------------------------------------------
  1098. sub advanced_page
  1099. {
  1100.     my ( $self, $client, $templ ) = @_;
  1101.  
  1102.     # Handle updating the parameter table
  1103.  
  1104.     if ( defined( $self->{form_}{update_params} ) ) {
  1105.         foreach my $param (sort keys %{$self->{form_}}) {
  1106.             if ( $param =~ /parameter_(.*)/ ) {
  1107.                 $self->{configuration__}->parameter( $1,
  1108.                     $self->{form_}{$param} );
  1109.             }
  1110.         }
  1111.  
  1112.         $self->{configuration__}->save_configuration();
  1113.     }
  1114.  
  1115.     if ( defined($self->{form_}{newword}) ) {
  1116.         my $result = $self->{c__}->add_stopword( $self->{api_session__},
  1117.                          $self->{form_}{newword} );
  1118.         if ( $result == 0 ) {
  1119.             $templ->param( 'Advanced_If_Add_Message' => 1 );
  1120.         }
  1121.     }
  1122.  
  1123.     if ( defined($self->{form_}{word}) ) {
  1124.         my $result = $self->{c__}->remove_stopword( $self->{api_session__},
  1125.                          $self->{form_}{word} );
  1126.         if ( $result == 0 ) {
  1127.             $templ->param( 'Advanced_If_Delete_Message' => 1 );
  1128.         }
  1129.     }
  1130.  
  1131.     # the word census
  1132.     my $last = '';
  1133.     my $need_comma = 0;
  1134.     my $groupCounter = 0;
  1135.     my $groupSize = 5;
  1136.     my @words = $self->{c__}->get_stopword_list( $self->{api_session__} );
  1137.     my $commas;
  1138.  
  1139.     my @word_loop;
  1140.     my $c;
  1141.     @words = sort @words;
  1142.     push ( @words, ' ' );
  1143.     for my $word (@words) {
  1144.         if ( $self->config_( 'language' ) =~ /^Korean$/ ) {
  1145.             no locale;
  1146.             $word =~ /^(.)/;
  1147.             $c = $1;
  1148.         } else {
  1149.                 if ( $self->config_( 'language' ) =~ /^Nihongo$/ ) {
  1150.                no locale;
  1151.                $word =~ /^($euc_jp)/;
  1152.                $c = $1;
  1153.             } else {
  1154.                $word =~ /^(.)/;
  1155.                $c = $1;
  1156.             }
  1157.         }
  1158.  
  1159.         $last = $c if ( $last eq '' );
  1160.  
  1161.         if ( $c ne $last ) {
  1162.             my %row_data;
  1163.             $row_data{Advanced_Words} = $commas;
  1164.             $commas = '';
  1165.  
  1166.             if ( $groupCounter == $groupSize ) {
  1167.                 $row_data{Advanced_Row_Class} = 'advancedAlphabetGroupSpacing';
  1168.             } else {
  1169.                 $row_data{Advanced_Row_Class} = 'advancedAlphabet';
  1170.             }
  1171.             $row_data{Advanced_Character} = $last;
  1172.  
  1173.             if ( $groupCounter == $groupSize ) {
  1174.                 $row_data{Advanced_Word_Class} = 'advancedWordsGroupSpacing';
  1175.                 $groupCounter = 0;
  1176.             } else {
  1177.                 $row_data{Advanced_Word_Class} = 'advancedWords';
  1178.             }
  1179.             $last = $c;
  1180.             $need_comma = 0;
  1181.             $groupCounter += 1;
  1182.             push ( @word_loop, \%row_data );
  1183.         }
  1184.  
  1185.         if ( $need_comma == 1 ) {
  1186.             $commas .= ", $word";
  1187.         } else {
  1188.             $commas .= $word;
  1189.             $need_comma = 1;
  1190.         }
  1191.     }
  1192.  
  1193.     $templ->param( 'Advanced_Loop_Word' => \@word_loop );
  1194.  
  1195.     $templ->param( 'Advanced_POPFILE_CFG' =>
  1196.         $self->get_user_path_( 'popfile.cfg' ) );
  1197.  
  1198.     my $last_module = '';
  1199.  
  1200.     my @param_loop;
  1201.     foreach my $param ($self->{configuration__}->configuration_parameters()) {
  1202.         my $value = $self->{configuration__}->parameter( $param );
  1203.         $param =~ /^([^_]+)_/;
  1204.  
  1205.         my %row_data;
  1206.  
  1207.         if ( ( $last_module ne '' ) && ( $last_module ne $1 ) ) {
  1208.             $row_data{Advanced_If_New_Module} = 1;
  1209.         } else {
  1210.             $row_data{Advanced_If_New_Module} = 0;
  1211.         }
  1212.  
  1213.         $last_module = $1;
  1214.  
  1215.         $row_data{Advanced_Parameter}   = $param;
  1216.         $row_data{Advanced_Value}       = $value;
  1217.         $row_data{Advanced_If_Changed}  =
  1218.             !$self->{configuration__}->is_default( $param );
  1219.         $row_data{Advanced_If_Password} =
  1220.             ( $param =~ /_password/ ) ? 1 : 0;
  1221.  
  1222.  
  1223.         push ( @param_loop, \%row_data);
  1224.     }
  1225.  
  1226.     $templ->param( 'Advanced_Loop_Parameter' => \@param_loop );
  1227.  
  1228.     $self->http_ok( $client, $templ, 5 );
  1229. }
  1230.  
  1231. sub max
  1232. {
  1233.     my ( $a, $b ) = @_;
  1234.  
  1235.     return ( $a > $b )?$a:$b;
  1236. }
  1237.  
  1238. #----------------------------------------------------------------------------
  1239. #
  1240. # magnet_page - the list of bucket magnets
  1241. #
  1242. # $client     The web browser to send the results to
  1243. #
  1244. #----------------------------------------------------------------------------
  1245. sub magnet_page
  1246. {
  1247.     my ( $self, $client, $templ ) = @_;
  1248.  
  1249.     my $magnet_message = '';
  1250.  
  1251.     if ( defined( $self->{form_}{delete} ) ) {
  1252.         for my $i ( 1 .. $self->{form_}{count} ) {
  1253.             if ( defined( $self->{form_}{"remove$i"} ) &&
  1254.                ( $self->{form_}{"remove$i"} ) ) {
  1255.                 my $mtype   = $self->{form_}{"type$i"};
  1256.                 my $mtext   = $self->{form_}{"text$i"};
  1257.                 my $mbucket = $self->{form_}{"bucket$i"};
  1258.  
  1259.                 $self->{c__}->delete_magnet( $self->{api_session__}, $mbucket, $mtype, $mtext );
  1260.             }
  1261.         }
  1262.     }
  1263.  
  1264.     if ( defined( $self->{form_}{count} ) &&
  1265.        ( defined( $self->{form_}{update} ) ||
  1266.          defined( $self->{form_}{create} ) ) ) {
  1267.         for my $i ( 0 .. $self->{form_}{count} ) {
  1268.             my $mtype   = $self->{form_}{"type$i"};
  1269.             my $mtext   = $self->{form_}{"text$i"};
  1270.             my $mbucket = $self->{form_}{"bucket$i"};
  1271.  
  1272.             if ( defined( $self->{form_}{update} ) ) {
  1273.                 my $otype   = $self->{form_}{"otype$i"};
  1274.                 my $otext   = $self->{form_}{"otext$i"};
  1275.                 my $obucket = $self->{form_}{"obucket$i"};
  1276.  
  1277.                 if ( defined( $otype ) ) {
  1278.                     $self->{c__}->delete_magnet( $self->{api_session__},
  1279.                         $obucket, $otype, $otext );
  1280.                 }
  1281.             }
  1282.  
  1283.             if ( ( defined($mbucket) ) &&
  1284.                  ( $mbucket ne '' ) &&
  1285.                  ( $mtext ne '' ) ) {
  1286.  
  1287.                 # Support for feature request 77646 - import function.
  1288.                 # goal is a method of creating multiple magnets all
  1289.                 # with the same target bucket quickly.
  1290.                 #
  1291.                 # If we have multiple lines in $mtext, each line will
  1292.                 # actually be used to create a new magnet all with the
  1293.                 # same target.  We loop through all of the requested
  1294.                 # magnets, check to make sure they are all valid (not
  1295.                 # already existing, etc...) and then loop through them
  1296.                 # again to create them.  this way, if even one isn't
  1297.                 # valid, none will be created.
  1298.                 #
  1299.                 # We also get rid of an \r's that may have been passed
  1300.                 # in.  We also and ignore lines containing, only white
  1301.                 # space and if a line is repeated we add just one
  1302.                 # bucket for it.
  1303.  
  1304.                 $mtext =~ s/\r\n/\n/g;
  1305.  
  1306.                 my @all_mtexts = split(/\n/,$mtext);
  1307.                 my %mtext_hash;
  1308.                 @mtext_hash{@all_mtexts} = ();
  1309.                 my @mtexts = keys %mtext_hash;
  1310.                 my $found = 0;
  1311.  
  1312.                 foreach my $current_mtext (@mtexts) {
  1313.                     for my $bucket ($self->{c__}->get_buckets_with_magnets(
  1314.                                         $self->{api_session__} )) {
  1315.                         my %magnets;
  1316.                         @magnets{ $self->{c__}->get_magnets(
  1317.                                       $self->{api_session__},
  1318.                                           $bucket, $mtype )} = ();
  1319.  
  1320.                         if ( exists( $magnets{$current_mtext} ) ) {
  1321.                             $found  = 1;
  1322.                             $magnet_message .= sprintf( $self->{language__}{Magnet_Error1}, "$mtype: $current_mtext", $bucket ) . '<br>';
  1323.                             last;
  1324.                         }
  1325.                     }
  1326.  
  1327.                     if ( $found == 0 )  {
  1328.                         for my $bucket ($self->{c__}->get_buckets_with_magnets( $self->{api_session__} )) {
  1329.                             my %magnets;
  1330.                             @magnets{ $self->{c__}->get_magnets( $self->{api_session__}, $bucket, $mtype )} = ();
  1331.  
  1332.                             for my $from (keys %magnets)  {
  1333.                                 if ( ( $mtext =~ /\Q$from\E/ ) || ( $from =~ /\Q$mtext\E/ ) )  {
  1334.                                     $found = 1;
  1335.                                     $magnet_message .= sprintf( $self->{language__}{Magnet_Error2}, "$mtype: $current_mtext", "$mtype: $from", $bucket ) . '<br>';
  1336.                                     last;
  1337.                                 }
  1338.                             }
  1339.                         }
  1340.                     }
  1341.                 }
  1342.  
  1343.                 if ( $found == 0 ) {
  1344.                     foreach my $current_mtext (@mtexts) {
  1345.  
  1346.                     # Skip mangnet definition if it consists only of white spaces
  1347.  
  1348.                     if ( $current_mtext =~ /^[ \t]*$/ ) {
  1349.                         next;
  1350.                     }
  1351.  
  1352.                     # It is possible to type leading or trailing white
  1353.                     # space in a magnet definition which can later
  1354.                     # cause mysterious failures because the whitespace
  1355.                     # is eaten by the browser when the magnet is
  1356.                     # displayed but is matched in the regular
  1357.                     # expression that does the magnet matching and
  1358.                     # will cause failures... so strip off the
  1359.                     # whitespace
  1360.  
  1361.                     $current_mtext =~ s/^[ \t]+//;
  1362.                     $current_mtext =~ s/[ \t]+$//;
  1363.  
  1364.                     $self->{c__}->create_magnet( $self->{api_session__}, $mbucket, $mtype, $current_mtext );
  1365.                     if ( !defined( $self->{form_}{update} ) ) {
  1366.                         $magnet_message .= sprintf( $self->{language__}{Magnet_Error3}, "$mtype: $current_mtext", $mbucket )  . '<br>';
  1367.                     }
  1368.                 }
  1369.             }
  1370.             }
  1371.         }
  1372.     }
  1373.  
  1374.     if ( $magnet_message ne '' ) {
  1375.         $templ->param( 'Magnet_If_Message' => 1 );
  1376.         $templ->param( 'Magnet_Message'    => $magnet_message );
  1377.     }
  1378.  
  1379.     # Current Magnets panel
  1380.  
  1381.     my $start_magnet = $self->{form_}{start_magnet};
  1382.     my $stop_magnet  = $self->{form_}{stop_magnet};
  1383.     my $magnet_count = $self->{c__}->magnet_count( $self->{api_session__} );
  1384.     my $navigator = '';
  1385.  
  1386.     if ( !defined( $start_magnet ) ) {
  1387.         $start_magnet = 0;
  1388.     }
  1389.  
  1390.     if ( !defined( $stop_magnet ) ) {
  1391.         $stop_magnet = $start_magnet + $self->config_( 'page_size' ) - 1;
  1392.     }
  1393.  
  1394.     if ( $self->config_( 'page_size' ) < $magnet_count ) {
  1395.         $self->set_magnet_navigator__( $templ, $start_magnet,
  1396.             $stop_magnet, $magnet_count );
  1397.     }
  1398.  
  1399.     $templ->param( 'Magnet_Start_Magnet' => $start_magnet );
  1400.  
  1401.     my %magnet_types = $self->{c__}->get_magnet_types( $self->{api_session__} );
  1402.     my $i = 0;
  1403.     my $count = -1;
  1404.  
  1405.     my @magnet_type_loop;
  1406.     foreach my $type (keys %magnet_types) {
  1407.         my %row_data;
  1408.         $row_data{Magnet_Type} = $type;
  1409.         $row_data{Magnet_Type_Name} = $magnet_types{$type};
  1410.         push ( @magnet_type_loop, \%row_data );
  1411.     }
  1412.     $templ->param( 'Magnet_Loop_Types' => \@magnet_type_loop );
  1413.  
  1414.     my @buckets = $self->{c__}->get_buckets( $self->{api_session__} );
  1415.     my @magnet_bucket_loop;
  1416.     foreach my $bucket (@buckets) {
  1417.         my %row_data;
  1418.         my $bcolor = $self->{c__}->get_bucket_color( $self->{api_session__}, $bucket );
  1419.         $row_data{Magnet_Bucket} = $bucket;
  1420.         $row_data{Magnet_Bucket_Color} = $bcolor;
  1421.         push ( @magnet_bucket_loop, \%row_data );
  1422.     }
  1423.     $templ->param( 'Magnet_Loop_Buckets' => \@magnet_bucket_loop );
  1424.  
  1425.     # magnet listing
  1426.  
  1427.     my @magnet_loop;
  1428.     for my $bucket ($self->{c__}->get_buckets_with_magnets( $self->{api_session__} )) {
  1429.         for my $type ($self->{c__}->get_magnet_types_in_bucket( $self->{api_session__}, $bucket )) {
  1430.             for my $magnet ($self->{c__}->get_magnets( $self->{api_session__}, $bucket, $type ))  {
  1431.                 my %row_data;
  1432.                 $count += 1;
  1433.                 if ( ( $count < $start_magnet ) || ( $count > $stop_magnet ) ) {
  1434.                     next;
  1435.                 }
  1436.  
  1437.                 $i += 1;
  1438.  
  1439.                 # to validate, must replace & with & stan todo
  1440.                 # note: come up with a smarter regex, this one's a
  1441.                 # bludgeon another todo: Move this stuff into a
  1442.                 # function to make text safe for inclusion in a form
  1443.                 # field
  1444.  
  1445.                 my $validatingMagnet = $magnet;
  1446.                 $validatingMagnet =~ s/&/&/g;
  1447.                 $validatingMagnet =~ s/</</g;
  1448.                 $validatingMagnet =~ s/>/>/g;
  1449.  
  1450.                 # escape quotation characters to avoid orphan data
  1451.                 # within tags todo: function to make arbitrary data
  1452.                 # safe for inclusion within a html tag attribute
  1453.                 # (inside double-quotes)
  1454.  
  1455.                 $validatingMagnet =~ s/\"/\"\;/g;
  1456.  
  1457.                 $row_data{Magnet_Row_ID}     = $i;
  1458.                 $row_data{Magnet_Bucket}     = $bucket;
  1459.                 $row_data{Magnet_MType}      = $type;
  1460.                 $row_data{Magnet_Validating} = $validatingMagnet;
  1461.                 $row_data{Magnet_Size}       = max(length($magnet),50);
  1462.  
  1463.                 my @type_loop;
  1464.                 for my $mtype (keys %magnet_types) {
  1465.                     my %type_data;
  1466.                     my $selected = ( $mtype eq $type )?"selected":"";
  1467.                     $type_data{Magnet_Type_Name} = $mtype;
  1468.                     $type_data{Magnet_Type_Localized} = $self->{language__}{$magnet_types{$mtype}};
  1469.                     $type_data{Magnet_Type_Selected} = $selected;
  1470.                     push ( @type_loop, \%type_data );
  1471.                 }
  1472.                 $row_data{Magnet_Loop_Loop_Types} = \@type_loop;
  1473.  
  1474.                 my @bucket_loop;
  1475.                 my @buckets = $self->{c__}->get_buckets( $self->{api_session__} );
  1476.                 foreach my $mbucket (@buckets) {
  1477.                     my %bucket_data;
  1478.                     my $selected = ( $bucket eq $mbucket )?"selected":"";
  1479.                     my $bcolor   = $self->{c__}->get_bucket_color( $self->{api_session__}, $mbucket );
  1480.                     $bucket_data{Magnet_Bucket_Bucket}   = $mbucket;
  1481.                     $bucket_data{Magnet_Bucket_Color}    = $bcolor;
  1482.                     $bucket_data{Magnet_Bucket_Selected} = $selected;
  1483.                     push ( @bucket_loop, \%bucket_data );
  1484.  
  1485.                 }
  1486.                 $row_data{Magnet_Loop_Loop_Buckets} = \@bucket_loop;
  1487.                 push ( @magnet_loop, \%row_data );
  1488.             }
  1489.         }
  1490.     }
  1491.  
  1492.     $templ->param( 'Magnet_Loop_Magnets' => \@magnet_loop );
  1493.     $templ->param( 'Magnet_Count_Magnet' => $i );
  1494.  
  1495.     $self->http_ok( $client, $templ, 2 );
  1496. }
  1497.  
  1498. #----------------------------------------------------------------------------
  1499. #
  1500. # bucket_page - information about a specific bucket
  1501. #
  1502. # $client     The web browser to send the results to
  1503. #
  1504. #----------------------------------------------------------------------------
  1505. sub bucket_page
  1506. {
  1507.     my ( $self, $client, $templ ) = @_;
  1508.  
  1509.     $templ = $self->load_template__( 'bucket-page.thtml' );
  1510.  
  1511.     my $color = $self->{c__}->get_bucket_color( $self->{api_session__}, $self->{form_}{showbucket} );
  1512.     $templ->param( 'Bucket_Main_Title' => sprintf( $self->{language__}{SingleBucket_Title}, "<font color=\"$color\">$self->{form_}{showbucket}</font>" ) );
  1513.  
  1514.     my $bucket_count = $self->{c__}->get_bucket_word_count( $self->{api_session__}, $self->{form_}{showbucket} );
  1515.     $templ->param( 'Bucket_Word_Count'   => $self->pretty_number( $bucket_count ) );
  1516.     $templ->param( 'Bucket_Unique_Count' => sprintf( $self->{language__}{SingleBucket_Unique}, $self->pretty_number( $self->{c__}->get_bucket_unique_count( $self->{api_session__}, $self->{form_}{showbucket} ) ) ) );
  1517.     $templ->param( 'Bucket_Total_Word_Count' => $self->pretty_number( $self->{c__}->get_word_count( $self->{api_session__} ) ) );
  1518.     $templ->param( 'Bucket_Bucket' => $self->{form_}{showbucket} );
  1519.  
  1520.     my $percent = '0%';
  1521.     if ( $self->{c__}->get_word_count( $self->{api_session__} ) > 0 )  {
  1522.         $percent = sprintf( '%6.2f%%', int( 10000 * $bucket_count / $self->{c__}->get_word_count( $self->{api_session__} ) ) / 100 );
  1523.     }
  1524.     $templ->param( 'Bucket_Percentage' => $percent );
  1525.  
  1526.     if ( $self->{c__}->get_bucket_word_count( $self->{api_session__}, $self->{form_}{showbucket} ) > 0 ) {
  1527.         $templ->param( 'Bucket_If_Has_Words' => 1 );
  1528.         my @letter_data;
  1529.         for my $i ($self->{c__}->get_bucket_word_prefixes( $self->{api_session__}, $self->{form_}{showbucket} )) {
  1530.             my %row_data;
  1531.             $row_data{Bucket_Letter} = $i;
  1532.             $row_data{Bucket_Bucket} = $self->{form_}{showbucket};
  1533.             $row_data{Session_Key}   = $self->{session_key__};
  1534.             if ( defined( $self->{form_}{showletter} ) && ( $i eq $self->{form_}{showletter} ) ) {
  1535.                 $row_data{Bucket_If_Show_Letter} = 1;
  1536.                 $row_data{Bucket_Word_Table_Title} = sprintf( $self->{language__}{SingleBucket_WordTable}, $self->{form_}{showbucket} );
  1537.                 my %temp;
  1538.  
  1539.                 for my $j ( $self->{c__}->get_bucket_word_list( $self->{api_session__}, $self->{form_}{showbucket}, $i ) ) {
  1540.                     $temp{$j} = $self->{c__}->get_count_for_word( $self->{api_session__}, $self->{form_}{showbucket}, $j );
  1541.                 }
  1542.  
  1543.                 my $count = 0;
  1544.                 my @word_data;
  1545.                 my %word_row;
  1546.                 for my $word (sort { $temp{$b} <=> $temp{$a} } keys %temp) {
  1547.                     if ( ( $count % 6 ) == 0 ) {
  1548.                         my %temp_row = %word_row;
  1549.                         push ( @word_data, \%temp_row );
  1550.                         $count = 0;
  1551.                     }
  1552.                     $word_row{"Bucket_Word_$count"} = $word;
  1553.                     $word_row{"Bucket_Word_Count_$count"} = $temp{$word};
  1554.                     $word_row{"Session_Key"} = $self->{session_key__};
  1555.                     $count++;
  1556.                 }
  1557.                 if ( $count != 0 ) {
  1558.                     for my $i ( $count..5) {
  1559.                         $word_row{"Bucket_Word_$i"} = '';
  1560.                         $word_row{"Bucket_Word_Count_$i"} = '';
  1561.                     }
  1562.                     push ( @word_data, \%word_row );
  1563.                 }
  1564.                 $row_data{Bucket_Loop_Loop_Word_Row} = \@word_data;
  1565.  
  1566.             } else {
  1567.                 $row_data{Bucket_If_Show_Letter} = 0;
  1568.             }
  1569.             push ( @letter_data, \%row_data );
  1570.        }
  1571.  
  1572.        $templ->param( 'Bucket_Loop_Letters' => \@letter_data );
  1573.     }
  1574.  
  1575.     $self->http_ok( $client, $templ, 1 );
  1576. }
  1577.  
  1578. #----------------------------------------------------------------------------
  1579. #
  1580. # bar_chart_100 - Output an HTML bar chart
  1581. #
  1582. # %values       A hash of bucket names with values in series 0, 1, 2, ...
  1583. #
  1584. #----------------------------------------------------------------------------
  1585. sub bar_chart_100
  1586. {
  1587.     my ( $self, %values ) = @_;
  1588.     my $body = '';
  1589.     my $total_count = 0;
  1590.     my @xaxis = sort {
  1591.         if ( $self->{c__}->is_pseudo_bucket( $self->{api_session__}, $a ) == $self->{c__}->is_pseudo_bucket( $self->{api_session__}, $b ) ) {
  1592.             $a cmp $b;
  1593.         } else {
  1594.             $self->{c__}->is_pseudo_bucket( $self->{api_session__}, $a ) <=> $self->{c__}->is_pseudo_bucket( $self->{api_session__}, $b );
  1595.         }
  1596.     } keys %values;
  1597.  
  1598.     return '' if ( $#xaxis < 0 );
  1599.  
  1600.     my @series = sort keys %{$values{$xaxis[0]}};
  1601.  
  1602.     for my $bucket (@xaxis)  {
  1603.         $total_count += $values{$bucket}{0};
  1604.     }
  1605.  
  1606.     for my $bucket (@xaxis)  {
  1607.         $body .= "<tr>\n<td align=\"left\"><font color=\"". $self->{c__}->get_bucket_color( $self->{api_session__}, $bucket ) . "\">$bucket</font></td>\n<td> </td>";
  1608.  
  1609.         for my $s (@series) {
  1610.             my $value = $values{$bucket}{$s} || 0;
  1611.             my $count   = $self->pretty_number( $value );
  1612.             my $percent = '';
  1613.  
  1614.             if ( $s == 0 ) {
  1615.                 my $d = $self->{language__}{Locale_Decimal};
  1616.                 if ( $total_count == 0 ) {
  1617.                     $percent = " (  0$d" . "00%)";
  1618.                 } else {
  1619.                    $percent = sprintf( " (%.2f%%)", int( $value * 10000 / $total_count ) / 100 );
  1620.                    $percent =~ s/\./$d/;
  1621.                 }
  1622.             }
  1623.  
  1624.             if ( ( $s == 2 ) &&
  1625.                  ( $self->{c__}->is_pseudo_bucket( $self->{api_session__}, $bucket ) ) ) {
  1626.                 $count = '';
  1627.                 $percent = '';
  1628.             }
  1629.  
  1630.             $body .= "\n<td align=\"right\">$count$percent</td>";
  1631.         }
  1632.         $body .= "\n</tr>\n";
  1633.     }
  1634.  
  1635.     my $colspan = 3 + $#series;
  1636.  
  1637.     $body .= "<tr>\n<td colspan=\"$colspan\"> </td>\n</tr>\n<tr>\n<td colspan=\"$colspan\">\n";
  1638.  
  1639.     if ( $total_count != 0 ) {
  1640.         $body .= "<table class=\"barChart\" width=\"100%\" summary=\"$self->{language__}{Bucket_BarChartSummary}\">\n<tr>\n";
  1641.         foreach my $bucket (@xaxis) {
  1642.             my $percent = int( $values{$bucket}{0} * 10000 / $total_count ) / 100;
  1643.             if ( $percent != 0 )  {
  1644.                 $body .= "<td bgcolor=\"" . $self->{c__}->get_bucket_color( $self->{api_session__}, $bucket ) . "\" title=\"$bucket ($percent%)\" width=\"";
  1645.                 $body .= (int($percent)<1)?1:int($percent);
  1646.                 $body .= "%\"><img src=\"pix.gif\" alt=\"\" height=\"20\" width=\"1\" /></td>\n";
  1647.             }
  1648.         }
  1649.         $body .= "</tr>\n</table>";
  1650.     }
  1651.  
  1652.     $body .= "</td>\n</tr>\n";
  1653.  
  1654.     if ( $total_count != 0 )  {
  1655.         $body .= "<tr>\n<td colspan=\"$colspan\" align=\"right\"><span class=\"graphFont\">100%</span></td>\n</tr>\n";
  1656.     }
  1657.  
  1658.     return $body;
  1659. }
  1660.  
  1661. #----------------------------------------------------------------------------
  1662. #
  1663. # corpus_page - the corpus management page
  1664. #
  1665. # $client     The web browser to send the results to
  1666. #
  1667. #----------------------------------------------------------------------------
  1668. sub corpus_page
  1669. {
  1670.     my ( $self, $client, $templ ) = @_;
  1671.  
  1672.     if ( defined( $self->{form_}{clearbucket} ) ) {
  1673.         $self->{c__}->clear_bucket( $self->{api_session__}, $self->{form_}{showbucket} );
  1674.     }
  1675.  
  1676.     if ( defined($self->{form_}{reset_stats}) ) {
  1677.         foreach my $bucket ($self->{c__}->get_all_buckets( $self->{api_session__} )) {
  1678.             $self->set_bucket_parameter__( $bucket, 'count', 0 );
  1679.             $self->set_bucket_parameter__( $bucket, 'fpcount', 0 );
  1680.             $self->set_bucket_parameter__( $bucket, 'fncount', 0 );
  1681.         }
  1682.         my $lasttime = localtime;
  1683.         $self->config_( 'last_reset', $lasttime );
  1684.         $self->{configuration__}->save_configuration();
  1685.     }
  1686.  
  1687.     if ( defined($self->{form_}{showbucket}) )  {
  1688.         $self->bucket_page( $client, $templ );
  1689.         return;
  1690.     }
  1691.  
  1692.     if ( ( defined($self->{form_}{color}) ) && ( defined($self->{form_}{bucket}) ) ) {
  1693.         $self->{c__}->set_bucket_color( $self->{api_session__}, $self->{form_}{bucket}, $self->{form_}{color});
  1694.     }
  1695.  
  1696.     if ( ( defined($self->{form_}{bucket}) ) && ( defined($self->{form_}{subject}) ) && ( $self->{form_}{subject} > 0 ) ) {
  1697.         $self->set_bucket_parameter__( $self->{form_}{bucket}, 'subject', $self->{form_}{subject} - 1 );
  1698.     }
  1699.  
  1700.     if ( ( defined($self->{form_}{bucket}) ) && ( defined($self->{form_}{xtc}) ) && ( $self->{form_}{xtc} > 0 ) ) {
  1701.         $self->set_bucket_parameter__( $self->{form_}{bucket}, 'xtc', $self->{form_}{xtc} - 1 );
  1702.     }
  1703.  
  1704.     if ( ( defined($self->{form_}{bucket}) ) && ( defined($self->{form_}{xpl}) ) && ( $self->{form_}{xpl} > 0 ) ) {
  1705.         $self->set_bucket_parameter__( $self->{form_}{bucket}, 'xpl', $self->{form_}{xpl} - 1 );
  1706.     }
  1707.  
  1708.     if ( ( defined($self->{form_}{bucket}) ) &&  ( defined($self->{form_}{quarantine}) ) && ( $self->{form_}{quarantine} > 0 ) ) {
  1709.         $self->set_bucket_parameter__( $self->{form_}{bucket}, 'quarantine', $self->{form_}{quarantine} - 1 );
  1710.     }
  1711.  
  1712.     # This regular expression defines the characters that are NOT valid
  1713.     # within a bucket name
  1714.  
  1715.     my $invalid_bucket_chars = '[^[:lower:]\-_0-9]';
  1716.  
  1717.     if ( ( defined($self->{form_}{cname}) ) && ( $self->{form_}{cname} ne '' ) ) {
  1718.         if ( $self->{form_}{cname} =~ /$invalid_bucket_chars/ )  {
  1719.             $templ->param( 'Corpus_If_Create_Error' => 1 );
  1720.         } else {
  1721.             if ( $self->{c__}->is_bucket( $self->{api_session__}, $self->{form_}{cname} ) ||
  1722.                 $self->{c__}->is_pseudo_bucket( $self->{api_session__}, $self->{form_}{cname} ) ) {
  1723.                 $templ->param( 'Corpus_If_Create_Message' => 1 );
  1724.                 $templ->param( 'Corpus_Create_Message' => sprintf( $self->{language__}{Bucket_Error2}, $self->{form_}{cname} ) );
  1725.             } else {
  1726.                 $self->{c__}->create_bucket( $self->{api_session__}, $self->{form_}{cname} );
  1727.                 $templ->param( 'Corpus_If_Create_Message' => 1 );
  1728.                 $templ->param( 'Corpus_Create_Message' => sprintf( $self->{language__}{Bucket_Error3}, $self->{form_}{cname} ) );
  1729.             }
  1730.        }
  1731.     }
  1732.  
  1733.     if ( ( defined($self->{form_}{delete}) ) && ( $self->{form_}{name} ne '' ) ) {
  1734.         $self->{form_}{name} = lc($self->{form_}{name});
  1735.         $self->{c__}->delete_bucket( $self->{api_session__}, $self->{form_}{name} );
  1736.         $templ->param( 'Corpus_If_Delete_Message' => 1 );
  1737.         $templ->param( 'Corpus_Delete_Message' => sprintf( $self->{language__}{Bucket_Error6}, $self->{form_}{name} ) );
  1738.     }
  1739.  
  1740.     if ( ( defined($self->{form_}{newname}) ) &&
  1741.          ( $self->{form_}{oname} ne '' ) ) {
  1742.         if ( ( $self->{form_}{newname} eq '' ) ||
  1743.              ( $self->{form_}{newname} =~ /$invalid_bucket_chars/ ) )  {
  1744.             $templ->param( 'Corpus_If_Rename_Error' => 1 );
  1745.         } else {
  1746.             $self->{form_}{oname} = lc($self->{form_}{oname});
  1747.             $self->{form_}{newname} = lc($self->{form_}{newname});
  1748.             if ( $self->{c__}->rename_bucket( $self->{api_session__}, $self->{form_}{oname}, $self->{form_}{newname} ) == 1 ) {
  1749.                 $templ->param( 'Corpus_If_Rename_Message' => 1 );
  1750.                 $templ->param( 'Corpus_Rename_Message' => sprintf( $self->{language__}{Bucket_Error5}, $self->{form_}{oname}, $self->{form_}{newname} ) );
  1751.             } else {
  1752.                 $templ->param( 'Corpus_If_Rename_Message' => 1 );
  1753.                 $templ->param( 'Corpus_Rename_Message' => 'Internal error: rename failed' );
  1754.             }
  1755.         }
  1756.     }
  1757.  
  1758.     my @buckets = $self->{c__}->get_buckets( $self->{api_session__} );
  1759.  
  1760.     my $total_count = 0;
  1761.     my @delete_data;
  1762.     my @rename_data;
  1763.     foreach my $bucket (@buckets) {
  1764.         my %delete_row;
  1765.         my %rename_row;
  1766.         $delete_row{Corpus_Delete_Bucket} = $bucket;
  1767.         $delete_row{Corpus_Delete_Bucket_Color} = $self->get_bucket_parameter__( $bucket, 'color' );
  1768.         $rename_row{Corpus_Rename_Bucket} = $bucket;
  1769.         $rename_row{Corpus_Rename_Bucket_Color} = $self->get_bucket_parameter__( $bucket, 'color' );
  1770.         $total_count += $self->get_bucket_parameter__( $bucket, 'count' );
  1771.         push ( @delete_data, \%delete_row );
  1772.         push ( @rename_data, \%rename_row );
  1773.     }
  1774.     $templ->param( 'Corpus_Loop_Delete_Buckets' => \@delete_data );
  1775.     $templ->param( 'Corpus_Loop_Rename_Buckets' => \@rename_data );
  1776.  
  1777.     my @pseudos = $self->{c__}->get_pseudo_buckets( $self->{api_session__} );
  1778.     push @buckets, @pseudos;
  1779.  
  1780.     my @corpus_data;
  1781.     foreach my $bucket (@buckets) {
  1782.         my %row_data;
  1783.         $row_data{Corpus_Bucket}        = $bucket;
  1784.         $row_data{Corpus_Bucket_Color}  = $self->get_bucket_parameter__( $bucket, 'color' );
  1785.         $row_data{Corpus_Bucket_Unique} = $self->pretty_number(  $self->{c__}->get_bucket_unique_count( $self->{api_session__}, $bucket ) );
  1786.         $row_data{Corpus_If_Bucket_Not_Pseudo} = !$self->{c__}->is_pseudo_bucket( $self->{api_session__}, $bucket );
  1787.         $row_data{Corpus_If_Subject}    = !$self->get_bucket_parameter__( $bucket, 'subject' );
  1788.         $row_data{Corpus_If_XTC}        = !$self->get_bucket_parameter__( $bucket, 'xtc' );
  1789.         $row_data{Corpus_If_XPL}        = !$self->get_bucket_parameter__( $bucket, 'xpl' );
  1790.         $row_data{Corpus_If_Quarantine} = !$self->get_bucket_parameter__( $bucket, 'quarantine' );
  1791.         $row_data{Localize_On}          = $self->{language__}{On};
  1792.         $row_data{Localize_Off}         = $self->{language__}{Off};
  1793.         $row_data{Localize_TurnOn}      = $self->{language__}{TurnOn};
  1794.         $row_data{Localize_TurnOff}     = $self->{language__}{TurnOff};
  1795.         my @color_data;
  1796.         foreach my $color (@{$self->{c__}->{possible_colors__}} ) {
  1797.             my %color_row;
  1798.             $color_row{Corpus_Available_Color} = $color;
  1799.             $color_row{Corpus_Color_Selected}  = ( $row_data{Corpus_Bucket_Color} eq $color )?'selected':'';
  1800.             push ( @color_data, \%color_row );
  1801.         }
  1802.         $row_data{Localize_Apply}          = $self->{language__}{Apply};
  1803.         $row_data{Session_Key}             = $self->{session_key__};
  1804.         $row_data{Corpus_Loop_Loop_Colors} = \@color_data;
  1805.         push ( @corpus_data, \%row_data );
  1806.     }
  1807.     $templ->param( 'Corpus_Loop_Buckets' => \@corpus_data );
  1808.  
  1809.     my %bar_values;
  1810.     for my $bucket (@buckets)  {
  1811.         $bar_values{$bucket}{0} = $self->get_bucket_parameter__( $bucket, 'count' );
  1812.         $bar_values{$bucket}{1} = $self->get_bucket_parameter__( $bucket, 'fpcount' );
  1813.         $bar_values{$bucket}{2} = $self->get_bucket_parameter__( $bucket, 'fncount' );
  1814.     }
  1815.  
  1816.     $templ->param( 'Corpus_Bar_Chart_Classification' => $self->bar_chart_100( %bar_values ) );
  1817.  
  1818.     @buckets = $self->{c__}->get_buckets( $self->{api_session__} );
  1819.  
  1820.     delete $bar_values{unclassified};
  1821.  
  1822.     for my $bucket (@buckets)  {
  1823.         $bar_values{$bucket}{0} = $self->{c__}->get_bucket_word_count( $self->{api_session__}, $bucket );
  1824.         delete $bar_values{$bucket}{1};
  1825.         delete $bar_values{$bucket}{2};
  1826.     }
  1827.  
  1828.     $templ->param( 'Corpus_Bar_Chart_Word_Counts' => $self->bar_chart_100( %bar_values ) );
  1829.  
  1830.     my $number = $self->pretty_number(  $self->{c__}->get_unique_word_count( $self->{api_session__} ) );
  1831.     $templ->param( 'Corpus_Total_Unique' => $number );
  1832.  
  1833.     my $pmcount = $self->pretty_number(  $self->mcount__() );
  1834.     $templ->param( 'Corpus_Message_Count' => $pmcount );
  1835.  
  1836.     my $pecount = $self->pretty_number(  $self->ecount__() );
  1837.     $templ->param( 'Corpus_Error_Count' => $pecount );
  1838.  
  1839.     my $accuracy = $self->{language__}{Bucket_NotEnoughData};
  1840.     my $percent = 0;
  1841.     if ( $self->mcount__() > $self->ecount__() ) {
  1842.         $percent = int( 10000 * ( $self->mcount__() - $self->ecount__() ) / $self->mcount__() ) / 100;
  1843.         $accuracy = "$percent%";
  1844.     }
  1845.     $templ->param( 'Corpus_Accuracy' => $accuracy );
  1846.     $templ->param( 'Corpus_If_Last_Reset' => 1 );
  1847.     $templ->param( 'Corpus_Last_Reset' => $self->config_( 'last_reset' ) );
  1848.  
  1849.     if ( ( defined($self->{form_}{lookup}) ) || ( defined($self->{form_}{word}) ) ) {
  1850.         $templ->param( 'Corpus_If_Looked_Up' => 1 );
  1851.         $templ->param( 'Corpus_Word' => $self->{form_}{word} );
  1852.         my $word = $self->{form_}{word};
  1853.  
  1854.         if ( !( $word =~ /^[A-Za-z0-9\-_]+:/ ) ) {
  1855.             $word = $self->{c__}->{parser__}->{mangle__}->mangle($word, 1);
  1856.         }
  1857.  
  1858.         if ( $self->{form_}{word} ne '' ) {
  1859.             my $max = 0;
  1860.                 my $max_bucket = '';
  1861.             my $total = 0;
  1862.             foreach my $bucket (@buckets) {
  1863.                 my $val = $self->{c__}->get_value_( $self->{api_session__}, $bucket, $word );
  1864.                 if ( $val != 0 ) {
  1865.                     my $prob = exp( $val );
  1866.                     $total += $prob;
  1867.                     if ( $prob > $max ) {
  1868.                         $max = $prob;
  1869.                         $max_bucket = $bucket;
  1870.                     }
  1871.                 } else {
  1872.  
  1873.                     # Take into account the probability the Bayes
  1874.                     # calculation applies for the buckets in which the
  1875.                     # word is not found.
  1876.  
  1877.                     $total += exp( $self->{c__}->get_not_likely_( $self->{api_session__} ) );
  1878.                 }
  1879.             }
  1880.  
  1881.             my @lookup_data;
  1882.             foreach my $bucket (@buckets) {
  1883.                 my $val = $self->{c__}->get_value_( $self->{api_session__}, $bucket, $word );
  1884.  
  1885.                 if ( $val != 0 ) {
  1886.                     my %row_data;
  1887.                     my $prob    = exp( $val );
  1888.                       my $n       = ($total > 0)?$prob / $total:0;
  1889.                     my $score   = ($#buckets >= 0)?($val - $self->{c__}->get_not_likely_( $self->{api_session__} ) )/log(10.0):0;
  1890.                     my $d = $self->{language__}{Locale_Decimal};
  1891.                     my $normal  = sprintf("%.10f", $n);
  1892.                     $normal =~ s/\./$d/;
  1893.                     $score      = sprintf("%.10f", $score);
  1894.                     $score =~ s/\./$d/;
  1895.                     my $probf   = sprintf("%.10f", $prob);
  1896.                     $probf =~ s/\./$d/;
  1897.                     my $bold    = '';
  1898.                     my $endbold = '';
  1899.                     if ( $score =~ /^[^\-]/ ) {
  1900.                         $score = " $score";
  1901.                     }
  1902.                     $row_data{Corpus_If_Most_Likely} = ( $max == $prob );
  1903.                     $row_data{Corpus_Bucket}         = $bucket;
  1904.                     $row_data{Corpus_Bucket_Color}   = $self->get_bucket_parameter__( $bucket, 'color' );
  1905.                     $row_data{Corpus_Probability}    = $probf;
  1906.                     $row_data{Corpus_Normal}         = $normal;
  1907.                     $row_data{Corpus_Score}          = $score;
  1908.                     push ( @lookup_data, \%row_data );
  1909.                 }
  1910.             }
  1911.             $templ->param( 'Corpus_Loop_Lookup' => \@lookup_data );
  1912.  
  1913.             if ( $max_bucket ne '' ) {
  1914.                 $templ->param( 'Corpus_Lookup_Message' => sprintf( $self->{language__}{Bucket_LookupMostLikely}, $word, $self->{c__}->get_bucket_color( $self->{api_session__}, $max_bucket ), $max_bucket ) );
  1915.             } else {
  1916.                 $templ->param( 'Corpus_Lookup_Message' => sprintf( $self->{language__}{Bucket_DoesNotAppear}, $word ) );
  1917.             }
  1918.         }
  1919.     }
  1920.  
  1921.     $self->http_ok( $client, $templ, 1 );
  1922. }
  1923.  
  1924. #----------------------------------------------------------------------------
  1925. #
  1926. # compare_mf - Compares two mailfiles, used for sorting mail into order
  1927. #
  1928. #----------------------------------------------------------------------------
  1929. sub compare_mf
  1930. {
  1931.     $a =~ /popfile(\d+)=(\d+)\.msg/;
  1932.     my ( $ad, $am ) = ( $1, $2 );
  1933.  
  1934.     $b =~ /popfile(\d+)=(\d+)\.msg/;
  1935.     my ( $bd, $bm ) = ( $1, $2 );
  1936.  
  1937.     if ( $ad == $bd ) {
  1938.         return ( $bm <=> $am );
  1939.     } else {
  1940.         return ( $bd <=> $ad );
  1941.     }
  1942. }
  1943.  
  1944. #----------------------------------------------------------------------------
  1945. #
  1946. # set_history_navigator__
  1947. #
  1948. # Fix up the history-navigator-widget.thtml template
  1949. #
  1950. # $templ                - The template to fix up
  1951. # $start_message        - The number of the first message displayed
  1952. # $stop_message         - The number of the last message displayed
  1953. #
  1954. #----------------------------------------------------------------------------
  1955. sub set_history_navigator__
  1956. {
  1957.     my ( $self, $templ, $start_message, $stop_message ) = @_;
  1958.  
  1959.     $templ->param( 'History_Navigator_Fields' => $self->print_form_fields_(0,1,('session','filter','search','sort','negate' ) ) );
  1960.  
  1961.     if ( $start_message != 0 )  {
  1962.         $templ->param( 'History_Navigator_If_Previous' => 1 );
  1963.         $templ->param( 'History_Navigator_Previous'    => $start_message - $self->config_( 'page_size' ) );
  1964.     }
  1965.  
  1966.     # Only show two pages either side of the current page, the first
  1967.     # page and the last page
  1968.     #
  1969.     # e.g. [1] ... [4] [5] [6] [7] [8] ... [24]
  1970.  
  1971.     my $i = 0;
  1972.     my $p = 1;
  1973.     my $dots = 0;
  1974.     my @nav_data;
  1975.     while ( $i < $self->{history__}->get_query_size( $self->{q__} ) ) {
  1976.         my %row_data;
  1977.         if ( ( $i == 0 ) ||
  1978.              ( ( $i + $self->config_( 'page_size' ) ) >= $self->{history__}->get_query_size( $self->{q__} ) ) ||
  1979.              ( ( ( $i - 2 * $self->config_( 'page_size' ) ) <= $start_message ) &&
  1980.                ( ( $i + 2 * $self->config_( 'page_size' ) ) >= $start_message ) ) ) {
  1981.             $row_data{History_Navigator_Page} = $p;
  1982.             $row_data{History_Navigator_I} = $i;
  1983.             if ( $i == $start_message ) {
  1984.                 $row_data{History_Navigator_If_This_Page} = 1;
  1985.             } else {
  1986.                 $row_data{History_Navigator_Fields} = $self->print_form_fields_(0,1,('session','filter','search','sort','negate'));
  1987.             }
  1988.  
  1989.             $dots = 1;
  1990.         } else {
  1991.             $row_data{History_Navigator_If_Spacer} = 1;
  1992.             if ( $dots ) {
  1993.                 $row_data{History_Navigator_If_Dots} = 1;
  1994.             }
  1995.             $dots = 0;
  1996.         }
  1997.  
  1998.         $i += $self->config_( 'page_size' );
  1999.         $p++;
  2000.         push ( @nav_data, \%row_data );
  2001.     }
  2002.     $templ->param( 'History_Navigator_Loop' => \@nav_data );
  2003.  
  2004.     if ( $start_message < ( $self->{history__}->get_query_size( $self->{q__} ) - $self->config_( 'page_size' ) ) )  {
  2005.         $templ->param( 'History_Navigator_If_Next' => 1 );
  2006.         $templ->param( 'History_Navigator_Next'    => $start_message + $self->config_( 'page_size' ) );
  2007.     }
  2008. }
  2009.  
  2010. #----------------------------------------------------------------------------
  2011. #
  2012. # set_magnet_navigator__
  2013. #
  2014. # Sets the magnet navigator up in a template
  2015. #
  2016. # $templ         - The loaded Magnet page template
  2017. # $start_magnet  - The number of the first magnet
  2018. # $stop_magnet   - The number of the last magnet
  2019. # $magnet_count  - Total number of magnets
  2020. #
  2021. #----------------------------------------------------------------------------
  2022. sub set_magnet_navigator__
  2023. {
  2024.     my ( $self, $templ, $start_magnet, $stop_magnet, $magnet_count ) = @_;
  2025.  
  2026.     if ( $start_magnet != 0 )  {
  2027.         $templ->param( 'Magnet_Navigator_If_Previous' => 1 );
  2028.         $templ->param( 'Magnet_Navigator_Previous'    => $start_magnet - $self->config_( 'page_size' ) );
  2029.     }
  2030.  
  2031.     my $i = 0;
  2032.     my $count = 0;
  2033.     my @page_loop;
  2034.     while ( $i < $magnet_count ) {
  2035.         $templ->param( 'Magnet_Navigator_Enabled' => 1 );
  2036.         my %row_data;
  2037.         $count += 1;
  2038.         $row_data{Magnet_Navigator_Count} = $count;
  2039.         $row_data{Session_Key} = $self->{session_key__};
  2040.         if ( $i == $start_magnet )  {
  2041.             $row_data{Magnet_Navigator_If_This_Page} = 1;
  2042.         } else {
  2043.             $row_data{Magnet_Navigator_If_This_Page} = 0;
  2044.             $row_data{Magnet_Navigator_Start_Magnet} = $i;
  2045.         }
  2046.  
  2047.         $i += $self->config_( 'page_size' );
  2048.         push ( @page_loop, \%row_data );
  2049.     }
  2050.     $templ->param( 'Magnet_Navigator_Loop_Pages' => \@page_loop );
  2051.  
  2052.     if ( $start_magnet < ( $magnet_count - $self->config_( 'page_size' ) ) )  {
  2053.         $templ->param( 'Magnet_Navigator_If_Next' => 1 );
  2054.         $templ->param( 'Magnet_Navigator_Next'    => $start_magnet + $self->config_( 'page_size' ) );
  2055.     }
  2056. }
  2057.  
  2058.  
  2059. #----------------------------------------------------------------------------
  2060. #
  2061. # history_reclassify - handle the reclassification of messages on the
  2062. # history page
  2063. #
  2064. #----------------------------------------------------------------------------
  2065. sub history_reclassify
  2066. {
  2067.     my ( $self ) = @_;
  2068.  
  2069.     if ( defined( $self->{form_}{change} ) ) {
  2070.  
  2071.         # Look for all entries in the form of the form
  2072.         # reclassify_X and see if they have values, those
  2073.         # that have values indicate a reclassification
  2074.  
  2075.         # Set up %messages to map a slot ID to the new
  2076.         # bucket
  2077.  
  2078.         my %messages;
  2079.  
  2080.         foreach my $key (keys %{$self->{form_}}) {
  2081.             if ( $key =~ /^reclassify_([0-9]+)$/ ) {
  2082.                 if ( defined( $self->{form_}{$key} ) &&
  2083.                      ( $self->{form_}{$key} ne '' ) ) {
  2084.                     $messages{$1} = $self->{form_}{$key};
  2085.                 }
  2086.             }
  2087.         }
  2088.  
  2089.         my %work;
  2090.  
  2091.         while ( my ( $slot, $newbucket ) = each %messages ) {
  2092.             push @{$work{$newbucket}},
  2093.                 $self->{history__}->get_slot_file( $slot );
  2094.             my @fields = $self->{history__}->get_slot_fields( $slot);
  2095.             my $bucket = $fields[8];
  2096.             $self->{c__}->reclassified(
  2097.                 $self->{api_session__}, $bucket, $newbucket, 0 );
  2098.             $self->{history__}->change_slot_classification(
  2099.                  $slot, $newbucket, $self->{api_session__}, 0);
  2100.             $self->{feedback}{$slot} = sprintf(
  2101.                  $self->{language__}{History_ChangedTo},
  2102.                  $self->{c__}->get_bucket_color(
  2103.                      $self->{api_session__}, $newbucket ), $newbucket );
  2104.         }
  2105.  
  2106.         # At this point the work hash maps the buckets to lists of
  2107.         # files to reclassify, so run through them doing bulk updates
  2108.  
  2109.         foreach my $newbucket (keys %work) {
  2110.             $self->{c__}->add_messages_to_bucket(
  2111.                 $self->{api_session__}, $newbucket, @{$work{$newbucket}} );
  2112.         }
  2113.     }
  2114. }
  2115.  
  2116. #----------------------------------------------------------------------------
  2117. #
  2118. # history_undo - handle undoing of reclassifications of messages on
  2119. # the history page
  2120. #
  2121. #----------------------------------------------------------------------------
  2122. sub history_undo
  2123. {
  2124.     my( $self ) = @_;
  2125.  
  2126.     # Look for all entries in the form of the form
  2127.     # undo_X and see if they have values, those
  2128.     # that have values indicate a reclassification
  2129.  
  2130.     foreach my $key (keys %{$self->{form_}}) {
  2131.         if ( $key =~ /^undo_([0-9]+)$/ ) {
  2132.             my $slot = $1;
  2133.             my @fields = $self->{history__}->get_slot_fields( $slot );
  2134.             my $bucket = $fields[8];
  2135.             my $newbucket = $self->{c__}->get_bucket_name(
  2136.                                 $self->{api_session__},
  2137.                                 $fields[9] );
  2138.             $self->{c__}->reclassified(
  2139.                 $self->{api_session__}, $newbucket, $bucket, 1 );
  2140.             $self->{history__}->change_slot_classification(
  2141.                  $slot, $newbucket, $self->{api_session__}, 1 );
  2142.             $self->{c__}->remove_message_from_bucket(
  2143.                 $self->{api_session__}, $bucket,
  2144.                 $self->{history__}->get_slot_file( $slot ) );
  2145.         }
  2146.     }
  2147. }
  2148.  
  2149. #----------------------------------------------------------------------------
  2150. #
  2151. # history_page - get the message classification history page
  2152. #
  2153. # $client     The web browser to send the results to
  2154. #
  2155. #----------------------------------------------------------------------------
  2156. sub history_page
  2157. {
  2158.     my ( $self, $client, $templ ) = @_;
  2159.  
  2160.     # Set up default values for various form elements that have been passed
  2161.     # in or not so that we don't have to worry about undefined values later
  2162.     # on in the function
  2163.  
  2164.     $self->{form_}{sort}   = $self->{old_sort__} || '-inserted' if ( !defined( $self->{form_}{sort}   ) );
  2165.     $self->{form_}{search} = (!defined($self->{form_}{setsearch})?$self->{old_search__}:'') || '' if ( !defined( $self->{form_}{search} ) );
  2166.     $self->{form_}{filter} = (!defined($self->{form_}{setfilter})?$self->{old_filter__}:'') || '' if ( !defined( $self->{form_}{filter} ) );
  2167.  
  2168.     # If the user hits the Reset button on a search then we need to
  2169.     # clear the search value but make it look as though they hit the
  2170.     # search button so that sort_filter_history will get called below
  2171.     # to get the right values in history_keys
  2172.  
  2173.     if ( defined( $self->{form_}{reset_filter_search} ) ) {
  2174.         $self->{form_}{filter}    = '';
  2175.         $self->{form_}{negate}    = '';
  2176.         delete $self->{form_}{negate_array};
  2177.         $self->{form_}{search}    = '';
  2178.         $self->{form_}{setsearch} = 1;
  2179.     }
  2180.  
  2181.     # If the user is asking for a new sort option then it needs to get
  2182.     # stored in the sort form variable so that it can be used for
  2183.     # subsequent page views of the History to keep the sort in place
  2184.  
  2185.     $self->{form_}{sort} = $self->{form_}{setsort} if ( defined( $self->{form_}{setsort} ) );
  2186.  
  2187.     # Cache some values to keep interface widgets updated if history
  2188.     # is re-accessed without parameters
  2189.  
  2190.     $self->{old_sort__} = $self->{form_}{sort};
  2191.  
  2192.     # We are using a checkbox for negate, so we have to
  2193.     # use an empty hidden input of the same name and
  2194.     # check for multiple occurences or any of the name
  2195.     # being defined
  2196.  
  2197.     if ( !defined( $self->{form_}{negate} ) ) {
  2198.  
  2199.         # if none of our negate inputs are active,
  2200.         # this is a "clean" access of the history
  2201.  
  2202.         $self->{form_}{negate} = $self->{old_negate__} || '';
  2203.  
  2204.     } elsif ( defined( $self->{form_}{negate_array} ) ) {
  2205.         for ( @{$self->{form_}{negate_array}} ) {
  2206.             if ($_ ne '') {
  2207.                 $self->{form_}{negate} = 'on';
  2208.                 $self->{old_negate__} = 'on';
  2209.                 last;
  2210.             }
  2211.         }
  2212.     } else {
  2213.         # We have a negate form, but no array.. this is likely
  2214.         # the hidden input, so this is not a "clean" visit
  2215.         $self->{old_negate__} = $self->{form_}{negate};
  2216.     }
  2217.  
  2218.  
  2219.  
  2220.  
  2221.     # Information from submit buttons isn't always preserved if the
  2222.     # buttons aren't pressed. This compares values in some fields and
  2223.     # sets the button-values as though they had been pressed
  2224.  
  2225.     # Set setsearch if search changed and setsearch is undefined
  2226.     $self->{form_}{setsearch} = 'on' if ( ( ( !defined($self->{old_search__}) && ($self->{form_}{search} ne '') ) || ( defined($self->{old_search__}) && ( $self->{old_search__} ne $self->{form_}{search} ) ) ) && !defined($self->{form_}{setsearch} ) );
  2227.     $self->{old_search__} = $self->{form_}{search};
  2228.  
  2229.     # Set setfilter if filter changed and setfilter is undefined
  2230.     $self->{form_}{setfilter} = 'Filter' if ( ( ( !defined($self->{old_filter__}) && ($self->{form_}{filter} ne '') ) || ( defined($self->{old_filter__}) && ( $self->{old_filter__} ne $self->{form_}{filter} ) ) ) && !defined($self->{form_}{setfilter} ) );
  2231.     $self->{old_filter__} = $self->{form_}{filter};
  2232.  
  2233.     # Set up the text that will appear at the top of the history page
  2234.     # indicating the current filter and search settings
  2235.  
  2236.     my $filter = $self->{form_}{filter};
  2237.  
  2238.     # Handle the reinsertion of a message file or the user hitting the
  2239.     # undo button
  2240.  
  2241.     $self->history_reclassify();
  2242.     $self->history_undo();
  2243.  
  2244.     # Handle removal of one or more items from the history page.  Two
  2245.     # important possibilities:
  2246.     #
  2247.     # clearpage is defined: this will delete everything on the page
  2248.     # which means we will call delete_slot in the history with the
  2249.     # ID of ever message displayed.   The IDs are encoded in the
  2250.     # hidden rowid_* form elements.
  2251.     #
  2252.     # clearchecked is defined: this will delete the messages that are
  2253.     # checked (i.e. the check box has been clicked).  The check box
  2254.     # is called remove_* in the form_ hash once we get here.
  2255.     #
  2256.     # The third possibility is clearall which is handled below and
  2257.     # uses the delete_query API of History.
  2258.  
  2259.     if ( defined( $self->{form_}{clearpage} ) ) {
  2260.  
  2261.         # Remove the list of marked messages using the array of
  2262.         # "remove" checkboxes
  2263.  
  2264.         for my $i ( keys %{$self->{form_}} ) {
  2265.             if ( $i =~ /^rowid_(\d+)$/ ) {
  2266.                 $self->log_( 1, "clearpage $i" );
  2267.                 $self->{history__}->delete_slot( $1 );
  2268.             }
  2269.         }
  2270.     }
  2271.  
  2272.     if ( defined( $self->{form_}{clearchecked} ) ) {
  2273.  
  2274.         # Remove the list of marked messages using the array of
  2275.         # "remove" checkboxes
  2276.  
  2277.         for my $i ( keys %{$self->{form_}} ) {
  2278.             if ( $i =~ /^remove_(\d+)$/ ) {
  2279.                 my $slot = $1;
  2280.                 if ( $self->{form_}{$i} ne '' ) {
  2281.                     $self->log_( 1, "clearchecked $i" );
  2282.                     $self->{history__}->delete_slot( $slot );
  2283.                 }
  2284.             }
  2285.         }
  2286.     }
  2287.  
  2288.     # Handle clearing the history files, there are two options here,
  2289.     # clear the current page or clear all the files in the cache
  2290.  
  2291.     if ( defined( $self->{form_}{clearall} ) ) {
  2292.         $self->{history__}->delete_query( $self->{q__} );
  2293.     }
  2294.  
  2295.     $self->{history__}->set_query( $self->{q__},
  2296.                                    $self->{form_}{filter},
  2297.                                    $self->{form_}{search},
  2298.                                    $self->{form_}{sort},
  2299.                                    ( $self->{form_}{negate} ne '' ) );
  2300.  
  2301.     # Redirect somewhere safe if non-idempotent action has been taken
  2302.  
  2303.     if ( defined( $self->{form_}{deletemessage}  ) ||  # PROFILE BLOCK START
  2304.          defined( $self->{form_}{clearpage}      ) ||
  2305.          defined( $self->{form_}{undo}           ) ||
  2306.          defined( $self->{form_}{reclassify}     ) ) { # PROFILE BLOCK STOP
  2307.         return $self->http_redirect_( $client, "/history?" . $self->print_form_fields_(1,0,('start_message','filter','search','sort','session','negate') ) );
  2308.     }
  2309.  
  2310.     $templ->param( 'History_Field_Search'  => $self->{form_}{search} );
  2311.     $templ->param( 'History_Field_Not'  => $self->{form_}{negate} );
  2312.     $templ->param( 'History_If_Search'     => defined( $self->{form_}{search} ) );
  2313.     $templ->param( 'History_Field_Sort'    => $self->{form_}{sort} );
  2314.     $templ->param( 'History_Field_Filter'  => $self->{form_}{filter} );
  2315.     $templ->param( 'History_If_MultiPage'  => $self->config_( 'page_size' ) <= $self->{history__}->get_query_size( $self->{q__} ) );
  2316.  
  2317.     my @buckets = $self->{c__}->get_buckets( $self->{api_session__} );
  2318.  
  2319.     my @bucket_data;
  2320.     foreach my $bucket (@buckets) {
  2321.         my %row_data;
  2322.         $row_data{History_Bucket} = $bucket;
  2323.         $row_data{History_Bucket_Color}  = $self->{c__}->get_bucket_parameter( $self->{api_session__},
  2324.                                                                       $bucket,
  2325.                                                                       'color' );
  2326.         push ( @bucket_data, \%row_data );
  2327.     }
  2328.  
  2329.     my @sf_bucket_data;
  2330.     foreach my $bucket (@buckets) {
  2331.         my %row_data;
  2332.         $row_data{History_Bucket} = $bucket;
  2333.         $row_data{History_Selected} = ( defined( $self->{form_}{filter} ) && ( $self->{form_}{filter} eq $bucket ) )?'selected':'';
  2334.         $row_data{History_Bucket_Color}  = $self->{c__}->get_bucket_parameter( $self->{api_session__},
  2335.                                                                       $bucket,
  2336.                                                                       'color' );
  2337.         push ( @sf_bucket_data, \%row_data );
  2338.     }
  2339.     $templ->param( 'History_Loop_SF_Buckets' => \@sf_bucket_data );
  2340.  
  2341.     $templ->param( 'History_Filter_Magnet' => ($self->{form_}{filter} eq '__filter__magnet')?'selected':'' );
  2342.     $templ->param( 'History_Filter_Unclassified' => ($self->{form_}{filter} eq 'unclassified')?'selected':'' );
  2343.     $templ->param( 'History_Field_Not' => ($self->{form_}{negate} ne '')?'checked':'' );
  2344.  
  2345.     my $c = $self->{history__}->get_query_size( $self->{q__} );
  2346.     if ( $c > 0 ) {
  2347.         $templ->param( 'History_If_Some_Messages' => 1 );
  2348.         $templ->param( 'History_Count' => $self->pretty_number( $c ) );
  2349.  
  2350.         my $start_message = 0;
  2351.         $start_message = $self->{form_}{start_message} if ( ( defined($self->{form_}{start_message}) ) && ($self->{form_}{start_message} > 0 ) );
  2352.         if ( $start_message >= $c ) {
  2353.             $start_message -= $self->config_( 'page_size' );
  2354.         }
  2355.         if ( $start_message < 0 ) {
  2356.             $start_message = 0;
  2357.         }
  2358.         $self->{form_}{start_message} = $start_message;
  2359.         $templ->param( 'History_Start_Message' => $start_message );
  2360.  
  2361.         my $stop_message  = $start_message + $self->config_( 'page_size' ) - 1;
  2362.         $stop_message = $self->{history__}->get_query_size( $self->{q__} ) - 1 if ( $stop_message >= $self->{history__}->get_query_size( $self->{q__} ) );
  2363.  
  2364.         $self->set_history_navigator__( $templ, $start_message, $stop_message );
  2365.  
  2366.         # Work out which columns to show by splitting the columns
  2367.         # parameter at commas keeping all the items that start with a
  2368.         # +, and then strip the +
  2369.  
  2370.         my @columns = split( ',', $self->config_( 'columns' ) );
  2371.         my @header_data;
  2372.         my $colspan = 1;
  2373.         my $length = 90;
  2374.         foreach my $header (@columns) {
  2375.             my %row_data;
  2376.             $header =~ /^(.)/;
  2377.             next if ( $1 eq '-' );
  2378.             $colspan++;
  2379.             $header =~ s/^.//;
  2380.             $row_data{History_Fields} =
  2381.                 $self->print_form_fields_(1,1,
  2382.                     ('filter','session','search','negate'));
  2383.             $row_data{History_Sort}   =
  2384.                 ( $self->{form_}{sort} eq $header )?'-':'';
  2385.             $row_data{History_Header} = $header;
  2386.  
  2387.             my $label = '';
  2388.             if ( defined $self->{language__}{ $headers_table{$header} }) {
  2389.                 $label = $self->{language__}{ $headers_table{$header} };
  2390.             } else {
  2391.                 $label = $headers_table{$header};
  2392.             }
  2393.             $row_data{History_Label} = $label;
  2394.             $row_data{History_If_Sorted} =
  2395.                 ( $self->{form_}{sort} =~ /^\-?\Q$header\E$/ );
  2396.             $row_data{History_If_Sorted_Ascending} =
  2397.                 ( $self->{form_}{sort} !~ /^-/ );
  2398.             push ( @header_data, \%row_data );
  2399.             $length -= 10;
  2400.         }
  2401.         $templ->param( 'History_Loop_Headers' => \@header_data );
  2402.         $templ->param( 'History_Colspan' => $colspan );
  2403.  
  2404.         my @rows = $self->{history__}->get_query_rows(
  2405.             $self->{q__}, $start_message+1,
  2406.             $stop_message - $start_message + 1 );
  2407.  
  2408.         my @history_data;
  2409.         my $i = $start_message;
  2410.         @columns = split( ',', $self->config_( 'columns' ) );
  2411.         my $last = -1;
  2412.         if ( defined($self->{form_}{automatic}) ) {
  2413.             $self->config_( 'column_characters', 0 );
  2414.         }
  2415.         if ( $self->config_( 'column_characters' ) != 0 ) {
  2416.             $length = $self->config_( 'column_characters' );
  2417.         }
  2418.         if ( defined($self->{form_}{increase}) ) {
  2419.             $length++;
  2420.             $self->config_( 'column_characters', $length );
  2421.         }
  2422.         if ( defined($self->{form_}{decrease}) ) {
  2423.             $length--;
  2424.             if ( $length < 5 ) {
  2425.                 $length = 5;
  2426.             }
  2427.             $self->config_( 'column_characters', $length );
  2428.         }
  2429.         foreach my $row (@rows) {
  2430.             my %row_data;
  2431.             my $mail_file = $row_data{History_Mail_File} = $$row[0];
  2432.             foreach my $header (@columns) {
  2433.                 $header =~ /(.)(.+)/;
  2434.                 $row_data{"History_If_$2"} = ( $1 eq '+')?1:0;
  2435.             }
  2436.             $row_data{History_Arrived}       = $self->pretty_date__( $$row[7] );
  2437.             $row_data{History_From}          = $$row[1];
  2438.             $row_data{History_To}            = $$row[2];
  2439.             $row_data{History_Cc}            = $$row[3];
  2440.             $row_data{History_Date}          = $self->pretty_date__( $$row[5] );
  2441.             $row_data{History_Subject}       = $$row[4];
  2442.             $row_data{History_Short_From}    = $self->shorten__( $$row[1], $length );
  2443.             $row_data{History_Short_To}      = $self->shorten__( $$row[2], $length );
  2444.             $row_data{History_Short_Cc}      = $self->shorten__( $$row[3], $length );
  2445.             $row_data{History_Short_Subject} = $self->shorten__( $$row[4], $length );
  2446.             my $bucket = $row_data{History_Bucket} = $$row[8];
  2447.             $row_data{History_Bucket_Color}  = $self->{c__}->get_bucket_parameter( $self->{api_session__},
  2448.                                                                           $bucket,
  2449.                                                                           'color' );
  2450.             $row_data{History_If_Reclassified} = ( $$row[9] != 0 );
  2451.             $row_data{History_I}             = $$row[0];
  2452.             $row_data{History_I1}            = $$row[0];
  2453.             $row_data{History_Fields}        = $self->print_form_fields_(0,1,('start_message','session','filter','search','sort','negate' ) );
  2454.             $row_data{History_If_Not_Pseudo} = !$self->{c__}->is_pseudo_bucket( $self->{api_session__},
  2455.                                                                            $bucket );
  2456.             $row_data{History_If_Magnetized} = ($$row[11] ne '');
  2457.             $row_data{History_Magnet}        = $$row[11];
  2458.             my $size = $$row[12];
  2459.             if ( defined $size ) {
  2460.                 if ( $size >= 1024 * 1024 ) {
  2461.                     $row_data{History_Size} = sprintf $self->{language__}{History_Size_MegaBytes}, $size / ( 1024 * 1024 );
  2462.                 }
  2463.                 elsif ( $size >= 1024 ) {
  2464.                     $row_data{History_Size} = sprintf $self->{language__}{History_Size_KiloBytes}, $size / 1024;
  2465.                 }
  2466.                 else {
  2467.                     $row_data{History_Size} = sprintf $self->{language__}{History_Size_Bytes}, $size;
  2468.                 }
  2469.             }
  2470.             else {
  2471.                 $row_data{History_Size} = "?";
  2472.             }
  2473.             $row_data{History_Loop_Loop_Buckets} = \@bucket_data;
  2474.             if ( defined $self->{feedback}{$mail_file} ) {
  2475.                 $row_data{History_If_Feedback} = 1;
  2476.                 $row_data{History_Feedback} = $self->{feedback}{$mail_file};
  2477.                 delete $self->{feedback}{$mail_file};
  2478.             }
  2479.             $row_data{Session_Key} = $self->{session_key__};
  2480.  
  2481.             if ( ( $last != -1 ) && ( $self->{form_}{sort} =~ /inserted/ ) && ( $self->config_( 'session_dividers' ) ) ) {
  2482.                 $row_data{History_If_Session} = ( abs( $$row[7] - $last ) > 300 );
  2483.                 $row_data{History_Colspan} = $colspan+1;
  2484.             }
  2485.  
  2486.             $last = $$row[7];
  2487.  
  2488.             $row_data{Localize_History_Reclassified} = $self->{language__}{History_Reclassified};
  2489.             $row_data{Localize_Undo} = $self->{language__}{Undo};
  2490.             push ( @history_data, \%row_data );
  2491.         }
  2492.         $templ->param( 'History_Loop_Messages' => \@history_data );
  2493.     }
  2494.  
  2495.     $self->http_ok( $client, $templ, 0 );
  2496. }
  2497.  
  2498. sub shorten__
  2499. {
  2500.     my ( $self, $string, $length ) = @_;
  2501.  
  2502.     if ( length($string)>$length) {
  2503.        $string =~ /(.{$length})/;
  2504.        $string = "$1...";
  2505.     }
  2506.  
  2507.     return $string;
  2508. }
  2509.  
  2510. #----------------------------------------------------------------------------
  2511. #
  2512. # view_page - Shows a single email
  2513. #
  2514. # $client     The web browser to send the results to
  2515. #
  2516. #----------------------------------------------------------------------------
  2517. sub view_page
  2518. {
  2519.     my ( $self, $client, $templ ) = @_;
  2520.  
  2521.     my $mail_file = $self->{history__}->get_slot_file( $self->{form_}{view} );
  2522.     my $start_message = $self->{form_}{start_message} || 0;
  2523.  
  2524.     my ( $id, $from, $to, $cc, $subject, $date, $hash, $inserted,
  2525.         $bucket, $reclassified, $bucketid, $magnet ) =
  2526.         $self->{history__}->get_slot_fields( $self->{form_}{view} );
  2527.  
  2528.     my $color = $self->{c__}->get_bucket_color(
  2529.                     $self->{api_session__}, $bucket );
  2530.     my $page_size = $self->config_( 'page_size' );
  2531.  
  2532.     $self->{form_}{sort}   = '' if ( !defined( $self->{form_}{sort}   ) );
  2533.     $self->{form_}{search} = '' if ( !defined( $self->{form_}{search} ) );
  2534.     $self->{form_}{filter} = '' if ( !defined( $self->{form_}{filter} ) );
  2535.     if ( !defined( $self->{form_}{format} ) ) {
  2536.         $self->{form_}{format} = $self->config_( 'wordtable_format' );
  2537.     }
  2538.  
  2539.     # If a format change was requested for the word matrix, record it in the
  2540.     # configuration and in the classifier options.
  2541.  
  2542.     $self->{c__}->wmformat( $self->{form_}{format} );
  2543.  
  2544.     my $index = $self->{form_}{view};
  2545.  
  2546.     $templ->param( 'View_All_Fields'       => $self->print_form_fields_(1,1,('start_message','filter','session','search','sort','negate')));
  2547.     $templ->param( 'View_Field_Search'     => $self->{form_}{search} );
  2548.     $templ->param( 'View_Field_Negate'     => $self->{form_}{negate} );
  2549.     $templ->param( 'View_Field_Sort'       => $self->{form_}{sort}   );
  2550.     $templ->param( 'View_Field_Filter'     => $self->{form_}{filter} );
  2551.  
  2552.     $templ->param( 'View_From'             => $from );
  2553.     $templ->param( 'View_To'               => $to );
  2554.     $templ->param( 'View_Cc'               => $cc );
  2555.     $templ->param( 'View_Date'             => $self->pretty_date__( $date, 1 ) );
  2556.     $templ->param( 'View_Subject'          => $subject );
  2557.     $templ->param( 'View_Bucket'           => $bucket );
  2558.     $templ->param( 'View_Bucket_Color'     => $color );
  2559.  
  2560.     $templ->param( 'View_Index'            => $index );
  2561.     $templ->param( 'View_This'             => $index );
  2562.     $templ->param( 'View_This_Page'        => (( $index ) >= $start_message )?$start_message:($start_message - $self->config_( 'page_size' ))); # TODO
  2563.  
  2564.     $templ->param( 'View_If_Reclassified'  => $reclassified );
  2565.     if ( $reclassified ) {
  2566.         $templ->param( 'View_Already' => sprintf( $self->{language__}{History_Already}, ($color || ''), ($bucket || '') ) );
  2567.     } else {
  2568.         $templ->param( 'View_If_Magnetized' => ( $magnet ne '' ) );
  2569.         if ( $magnet eq '' ) {
  2570.             my @bucket_data;
  2571.             foreach my $abucket ($self->{c__}->get_buckets( $self->{api_session__} )) {
  2572.                 my %row_data;
  2573.                 $row_data{View_Bucket_Color} = $self->{c__}->get_bucket_color( $self->{api_session__}, $abucket );
  2574.                 $row_data{View_Bucket} = $abucket;
  2575.                 push ( @bucket_data, \%row_data );
  2576.             }
  2577.             $templ->param( 'View_Loop_Buckets' => \@bucket_data );
  2578.         } else {
  2579.             $templ->param( 'View_Magnet' => $magnet );
  2580.         }
  2581.     }
  2582.  
  2583.     if ( $magnet eq '' ) {
  2584.         my %matrix;
  2585.         my %idmap;
  2586.  
  2587.         # Enable saving of word-scores
  2588.  
  2589.         $self->{c__}->wordscores( 1 );
  2590.  
  2591.         # Build the scores by classifying the message, since
  2592.         # get_html_colored_message has parsed the message for us we do
  2593.         # not need to parse it again and hence we pass in undef for
  2594.         # the filename
  2595.  
  2596.         my $current_class = $self->{c__}->classify(
  2597.             $self->{api_session__}, $mail_file, $templ, \%matrix, \%idmap );
  2598.  
  2599.         # Check whether the original classfication is still valid.  If
  2600.         # not, add a note at the top of the page:
  2601.  
  2602.         if ( $current_class ne $bucket ) {
  2603.             my $new_color = $self->{c__}->get_bucket_color(
  2604.                 $self->{api_session__}, $current_class );
  2605.             $templ->param( 'View_If_Class_Changed' => 1 );
  2606.             $templ->param( 'View_Class_Changed' => $current_class );
  2607.             $templ->param( 'View_Class_Changed_Color' => $new_color );
  2608.         }
  2609.  
  2610.         # Disable, print, and clear saved word-scores
  2611.  
  2612.         $self->{c__}->wordscores( 0 );
  2613.  
  2614.         $templ->param( 'View_Message' =>
  2615.             $self->{c__}->fast_get_html_colored_message(
  2616.                 $self->{api_session__}, $mail_file, \%matrix, \%idmap ) );
  2617.  
  2618.         # We want to insert a link to change the output format at the
  2619.         # start of the word matrix.  The classifier puts a comment in
  2620.         # the right place, which we can replace by the link.  (There's
  2621.         # probably a better way.)
  2622.  
  2623.         my $view = $self->{language__}{View_WordProbabilities};
  2624.         if ( $self->{form_}{format} eq 'freq' ) {
  2625.             $view = $self->{language__}{View_WordFrequencies};
  2626.         }
  2627.         if ( $self->{form_}{format} eq 'score' ) {
  2628.             $view = $self->{language__}{View_WordScores};
  2629.         }
  2630.  
  2631.         if ( $self->{form_}{format} ne '' ) {
  2632.             $templ->param( 'View_If_Format' => 1 );
  2633.             $templ->param( 'View_View' => $view );
  2634.         }
  2635.         if ($self->{form_}{format} ne 'freq' ) {
  2636.             $templ->param( 'View_If_Format_Freq' => 1 );
  2637.         }
  2638.         if ($self->{form_}{format} ne 'prob' ) {
  2639.             $templ->param( 'View_If_Format_Prob' => 1 );
  2640.         }
  2641.         if ($self->{form_}{format} ne 'score' ) {
  2642.             $templ->param( 'View_If_Format_Score' => 1 );
  2643.         }
  2644.     } else {
  2645.  
  2646.         # TODO: See comment below for details
  2647.  
  2648.         # $magnet =~ /(.+): ([^\r\n]+)/;
  2649.         # my $header = $1;
  2650.         # my $text   = $2;
  2651.  
  2652.         my $body = '<tt>';
  2653.  
  2654.         open MESSAGE, '<' . $mail_file;
  2655.         my $line;
  2656.  
  2657.         while ($line = <MESSAGE>) {
  2658.             $line =~ s/</</g;
  2659.             $line =~ s/>/>/g;
  2660.  
  2661.             $line =~ s/([^\r\n]{100,150} )/$1<br \/>/g;
  2662.             $line =~ s/([^ \r\n]{150})/$1<br \/>/g;
  2663.             $line =~ s/[\r\n]+/<br \/>/g;
  2664.  
  2665.             # TODO: This code is now useless because the magnet itself
  2666.             # doesn't contain the information about which header we are
  2667.             # looking for.  Ultimately, we need to fix this but I decided
  2668.             # for v0.22.0 release to not make further changes and leave this
  2669.             # code as unfixed.
  2670.  
  2671.             # if ( $line =~ /^([A-Za-z-]+): ?([^\n\r]*)/ ) {
  2672.             #    my $head = $1;
  2673.             #    my $arg  = $2;
  2674.  
  2675.             #    if ( $head =~ /\Q$header\E/i ) {
  2676.  
  2677.             #        $text =~ s/</</g;
  2678.             #        $text =~ s/>/>/g;
  2679.  
  2680.             #        if ( $arg =~ /\Q$text\E/i ) {
  2681.             #            my $new_color = $self->{c__}->get_bucket_color( $self->{api_session__}, $bucket );
  2682.             #            $line =~ s/(\Q$text\E)/<b><font color=\"$new_color\">$1<\/font><\/b>/;
  2683.             #        }
  2684.             #    }
  2685.             # }
  2686.  
  2687.             $body .= $line;
  2688.         }
  2689.         close MESSAGE;
  2690.         $body .= '</tt>';
  2691.         $templ->param( 'View_Message' => $body );
  2692.     }
  2693.  
  2694.     if ($magnet ne '') {
  2695.         $templ->param( 'View_Magnet_Reason' => sprintf( $self->{language__}{History_MagnetBecause},  # PROFILE BLOCK START
  2696.                           $color, $bucket,
  2697.                           Classifier::MailParse::splitline($magnet,0)
  2698.             ) );                                                                                     # PROFILE BLOCK STOP
  2699.     }
  2700.  
  2701.     $self->http_ok( $client, $templ, 0 );
  2702. }
  2703.  
  2704. #----------------------------------------------------------------------------
  2705. #
  2706. # password_page - Simple page asking for the POPFile password
  2707. #
  2708. # $client     The web browser to send the results to
  2709. # $error      1 if the user previously typed the password incorrectly
  2710. # $redirect   The page to go to on a correct password
  2711. #
  2712. #----------------------------------------------------------------------------
  2713. sub password_page
  2714. {
  2715.     my ( $self, $client, $error, $redirect ) = @_;
  2716.     my $session_temp = $self->{session_key__};
  2717.  
  2718.     # Show a page asking for the password with no session key
  2719.     # information on it
  2720.  
  2721.     $self->{session_key__} = '';
  2722.     my $templ = $self->load_template__( 'password-page.thtml' );
  2723.     $self->{session_key__} = $session_temp;
  2724.  
  2725.     # These things need fixing up on the password page:
  2726.     #
  2727.     # The page to redirect to if the user gets the password right
  2728.     # An error if they typed in the wrong password
  2729.  
  2730.     $templ->param( 'Password_If_Error' => $error );
  2731.     $templ->param( 'Password_Redirect' => $redirect );
  2732.  
  2733.     $self->http_ok( $client, $templ );
  2734. }
  2735.  
  2736. #----------------------------------------------------------------------------
  2737. #
  2738. # session_page - Simple page information the user of a bad session key
  2739. #
  2740. # $client     The web browser to send the results to
  2741. #
  2742. #----------------------------------------------------------------------------
  2743. sub session_page
  2744. {
  2745.     my ( $self, $client ) = @_;
  2746.  
  2747.     my $templ = $self->load_template__( 'session-page.thtml' );
  2748.     $self->http_ok( $client, $templ );
  2749. }
  2750.  
  2751. #----------------------------------------------------------------------------
  2752. #
  2753. # load_template__
  2754. #
  2755. # Loads the named template and returns a new HTML::Template object
  2756. #
  2757. # $template          The name of the template to load from the current skin
  2758. #
  2759. #----------------------------------------------------------------------------
  2760. sub load_template__
  2761. {
  2762.     my ( $self, $template ) = @_;
  2763.  
  2764.     # First see if that template exists in the currently selected
  2765.     # skin, if it does not then load the template from the default.
  2766.     # This allows a skin author to change just a single part of
  2767.     # POPFile with duplicating that entire set of templates
  2768.  
  2769.     my $root = 'skins/' . $self->config_( 'skin' ) . '/';
  2770.     my $template_root = $root;
  2771.     my $file = $self->get_root_path_( "$template_root$template" );
  2772.     if ( !( -e $file ) ) {
  2773.         $template_root = 'skins/default/';
  2774.         $file = $self->get_root_path_( "$template_root$template" );
  2775.     }
  2776.  
  2777.     my $css = $self->get_root_path_( $root . 'style.css' );
  2778.     if ( !( -e $css ) ) {
  2779.         $root = 'skins/default/';
  2780.     }
  2781.  
  2782.     my $templ = HTML::Template->new(
  2783.         filename          => $file,
  2784.         case_sensitive    => 1,
  2785.         loop_context_vars => 1,
  2786.         cache             => $self->config_( 'cache_templates' ),
  2787.         die_on_bad_params => $self->config_( 'strict_templates' ),
  2788.         search_path_on_include => 1,
  2789.         path => [$self->get_root_path_( "$root" ),
  2790.                  $self->get_root_path_( 'skins/default' ) ]
  2791.                                    );
  2792.  
  2793.     # Set a variety of common elements that are used repeatedly
  2794.     # throughout POPFile's pages
  2795.  
  2796.     my $now = time;
  2797.     my %fixups = ( 'Skin_Root'               => $root,
  2798.                    'Session_Key'             => $self->{session_key__},
  2799.                    'Common_Bottom_Date'      => $self->pretty_date__( $now ),
  2800.                    'Common_Bottom_LastLogin' => $self->{last_login__},
  2801.                    'Common_Bottom_Version'   => $self->version(),
  2802.                    'If_Show_Bucket_Help'     => $self->config_( 'show_bucket_help' ),
  2803.                    'If_Show_Training_Help'   => $self->config_( 'show_training_help' ) );
  2804.  
  2805.     foreach my $fixup (keys %fixups) {
  2806.         if ( $templ->query( name => $fixup ) ) {
  2807.             $templ->param( $fixup => $fixups{$fixup} );
  2808.         }
  2809.     }
  2810.  
  2811.     $self->localize_template__( $templ );
  2812.  
  2813.     return $templ;
  2814. }
  2815.  
  2816. #----------------------------------------------------------------------------
  2817. #
  2818. # localize_template__
  2819. #
  2820. # Localize a template by converting all the Localize_X variables to the
  2821. # appropriate variable X from the language__ hash.
  2822. #
  2823. #----------------------------------------------------------------------------
  2824. sub localize_template__
  2825. {
  2826.     my ( $self, $templ ) = @_;
  2827.  
  2828.     # Localize the template in use.
  2829.     #
  2830.     # Templates are automatically localized.  Any TMPL_VAR that begins with
  2831.     # Localize_ will be fixed up automatically with the appropriate string
  2832.     # for the language in use.  For example if you write
  2833.     #
  2834.     #     <TMPL_VAR name="Localize_Foo_Bar">
  2835.     #
  2836.     # this will automatically be converted to the string associated with
  2837.     # Foo_Bar in the current language file.
  2838.  
  2839.     my @vars = $templ->param();
  2840.  
  2841.     foreach my $var (@vars) {
  2842.         if ( $var =~ /^Localize_(.*)/ ) {
  2843.             $templ->param( $var => $self->{language__}{$1} );
  2844.         }
  2845.     }
  2846. }
  2847.  
  2848. #----------------------------------------------------------------------------
  2849. #
  2850. # load_skins__
  2851. #
  2852. # Gets the names of all the directory in the skins subdirectory and
  2853. # loads them into the skins array.
  2854. #
  2855. #----------------------------------------------------------------------------
  2856. sub load_skins__
  2857. {
  2858.     my ( $self ) = @_;
  2859.  
  2860.     @{$self->{skins__}} = glob $self->get_root_path_( 'skins/*' );
  2861.  
  2862.     for my $i (0..$#{$self->{skins__}}) {
  2863.         $self->{skins__}[$i] =~ s/\/$//;
  2864.         $self->{skins__}[$i] .= '/';
  2865.     }
  2866. }
  2867.  
  2868. #----------------------------------------------------------------------------
  2869. #
  2870. # load_languages__
  2871. #
  2872. # Get the names of the available languages for the user interface
  2873. #
  2874. #----------------------------------------------------------------------------
  2875. sub load_languages__
  2876. {
  2877.     my ( $self ) = @_;
  2878.  
  2879.     @{$self->{languages__}} = glob $self->get_root_path_( 'languages/*.msg' );
  2880.  
  2881.     for my $i (0..$#{$self->{languages__}}) {
  2882.         $self->{languages__}[$i] =~ s/.*\/(.+)\.msg$/$1/;
  2883.     }
  2884. }
  2885.  
  2886. #----------------------------------------------------------------------------
  2887. #
  2888. # change_session_key__
  2889. #
  2890. # Changes the session key, the session key is a randomly chosen 6 to
  2891. # 10 character key that protects and identifies sessions with the
  2892. # POPFile user interface.  At the current time it is primarily used
  2893. # for two purposes: to prevent a malicious user telling the browser to
  2894. # hit a specific URL causing POPFile to do something undesirable (like
  2895. # shutdown) and to handle the password mechanism: if the session key
  2896. # is wrong the password challenge is made.
  2897. #
  2898. # The characters valid in the session key are A-Z, a-z and 0-9
  2899. #
  2900. #----------------------------------------------------------------------------
  2901. sub change_session_key__
  2902. {
  2903.     my ( $self ) = @_;
  2904.  
  2905.     my @chars = ( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',   # PROFILE BLOCK START
  2906.                   'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'U', 'V', 'W', 'X', 'Y',
  2907.                   'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A' ); # PROFILE BLOCK STOP
  2908.  
  2909.     $self->{session_key__} = '';
  2910.  
  2911.     my $length = int( 6 + rand(4) );
  2912.  
  2913.     for my $i (0 .. $length) {
  2914.         my $random = $chars[int( rand(36) )];
  2915.  
  2916.         # Just to add spice to things we sometimes lowercase the value
  2917.  
  2918.         if ( rand(1) < rand(1) ) {
  2919.             $random = lc($random);
  2920.         }
  2921.  
  2922.         $self->{session_key__} .= $random;
  2923.     }
  2924. }
  2925.  
  2926. #----------------------------------------------------------------------------
  2927. #
  2928. # load_language
  2929. #
  2930. # Fill the language hash with the language strings that are from the
  2931. # named language file
  2932. #
  2933. # $lang    - The language to load (no .msg extension)
  2934. #
  2935. #----------------------------------------------------------------------------
  2936. sub load_language
  2937. {
  2938.     my ( $self, $lang ) = @_;
  2939.  
  2940.     if ( open LANG, '<' . $self->get_root_path_( "languages/$lang.msg" ) ) {
  2941.         while ( <LANG> ) {
  2942.             next if ( /[ \t]*#/ );
  2943.  
  2944.             if ( /([^\t ]+)[ \t]+(.+)/ ) {
  2945.                 my ( $id, $value )  = ( $1, $2 );
  2946.                 if ( $value =~ /^\"(.+)\"$/ ) {
  2947.                     $value = $1;
  2948.                 }
  2949.                 my $msg = ($self->config_( 'test_language' )) ? $id : $value;
  2950.                 $msg =~ s/[\r\n]//g;
  2951.  
  2952.                 $self->{language__}{$id} = $msg;
  2953.             }
  2954.         }
  2955.         close LANG;
  2956.     }
  2957. }
  2958.  
  2959. #----------------------------------------------------------------------------
  2960. #
  2961. # calculate_today - set the global $self->{today__} variable to the
  2962. # current day in seconds
  2963. #
  2964. #----------------------------------------------------------------------------
  2965. sub calculate_today
  2966. {
  2967.     my ( $self ) = @_;
  2968.  
  2969.     $self->{today__} = int( time / $seconds_per_day ) * $seconds_per_day;
  2970. }
  2971.  
  2972. #----------------------------------------------------------------------------
  2973. #
  2974. # print_form_fields_ - Returns a form string containing any presently
  2975. # defined form fields
  2976. #
  2977. # $first - 1 if the form field is at the beginning of a query, 0
  2978. #     otherwise
  2979. # $in_href - 1 if the form field is printing in a href, 0
  2980. #     otherwise (eg, for a 302 redirect)
  2981. # $include - a list of fields to
  2982. #     return
  2983. #
  2984. #----------------------------------------------------------------------------
  2985. sub print_form_fields_
  2986. {
  2987.     my ($self, $first, $in_href, @include) = @_;
  2988.  
  2989.     my $amp;
  2990.     if ($in_href) {
  2991.         $amp = '&';
  2992.     } else {
  2993.         $amp = '&';
  2994.     }
  2995.  
  2996.     my $count = 0;
  2997.     my $formstring = '';
  2998.  
  2999.     $formstring = "$amp" if (!$first);
  3000.  
  3001.     foreach my $field ( @include ) {
  3002.         if ($field eq 'session') {
  3003.             $formstring .= "$amp" if ($count > 0);
  3004.             $formstring .= "session=$self->{session_key__}";
  3005.             $count++;
  3006.             next;
  3007.             }
  3008.         unless ( !defined($self->{form_}{$field}) || ( $self->{form_}{$field} eq '' ) ) {
  3009.             $formstring .= "$amp" if ($count > 0);
  3010.             $formstring .= "$field=". $self->url_encode_($self->{form_}{$field});
  3011.             $count++;
  3012.         }
  3013.     }
  3014.  
  3015.     return ($count>0)?$formstring:'';
  3016. }
  3017.  
  3018. #----------------------------------------------------------------------------
  3019. # register_configuration_item__
  3020. #
  3021. #     $type            The type of item (configuration, security or chain)
  3022. #     $name            Unique name for this item
  3023. #     $template        The name of the template to load
  3024. #     $object          Reference to the object calling this method
  3025. #
  3026. # This seemingly innocent method disguises a lot.  It is called by
  3027. # modules that wish to register that they have specific elements of UI
  3028. # that need to be dynamically added to the Configuration and Security
  3029. # screens of POPFile.  This is done so that the HTML module does not
  3030. # need to know about the modules that are loaded, their individual
  3031. # configuration elements or how to do validation
  3032. #
  3033. # A module calls this method for each separate UI element (normally an
  3034. # HTML form that handles a single configuration option stored in a
  3035. # template) and passes in four pieces of information:
  3036. #
  3037. # The type is the position in the UI where the element is to be
  3038. # displayed. configuration means on the Configuration screen under
  3039. # "Module Options"; security means on the Security page and is used
  3040. # exclusively for stealth mode operation right now; chain is also on
  3041. # the security page and is used for identifying chain servers (in the
  3042. # case of SMTP the chained server and for POP3 the SPA server)
  3043. #
  3044. # A unique name for this configuration item
  3045. #
  3046. # The template (this is the name of a template file and must be unique
  3047. # for each call to this method)
  3048. #
  3049. # A reference to itself.
  3050. #
  3051. # When this module needs to display an element of UI it will call the
  3052. # object's configure_item public method passing in the name of the
  3053. # element required, a reference to the loaded template and
  3054. # configure_item must set whatever variables are required in the
  3055. # template.
  3056. #
  3057. # When the module needs to validate it will call the object's
  3058. # validate_item interface passing in the name of the element, a
  3059. # reference to the template and a reference to the form hash which has
  3060. # been parsed.
  3061. #
  3062. # Example the module foo has a configuration item called bar which it
  3063. # needs a UI for, and so it calls
  3064. #
  3065. #    register_configuration_item( 'configuration', 'foo', 'foo-bar.thtml',
  3066. #        $self )
  3067. #
  3068. # later it will receive a call to its
  3069. #
  3070. #    configure_item( 'foo', loaded foo-bar.thtml, language hash )
  3071. #
  3072. # and needs to fill the template variables.  Then it will receive
  3073. # a call to its
  3074. #
  3075. #    validate_item( 'foo', loaded foo-bar.thtml, language hash, form hash )
  3076. #
  3077. # and needs to check the form for information from any form it created
  3078. # and returned from the call to configure_item and update its own
  3079. # state.
  3080. #
  3081. #----------------------------------------------------------------------------
  3082. sub register_configuration_item__
  3083. {
  3084.    my ( $self, $type, $name, $templ, $object ) = @_;
  3085.  
  3086.    $self->{dynamic_ui__}{$type}{$name}{object}   = $object;
  3087.    $self->{dynamic_ui__}{$type}{$name}{template} = $templ;
  3088. }
  3089.  
  3090. #----------------------------------------------------------------------------
  3091. #
  3092. # mcount__, ecount__ get the total message count, or the total error count
  3093. #
  3094. #----------------------------------------------------------------------------
  3095.  
  3096. sub mcount__
  3097. {
  3098.     my ( $self ) = @_;
  3099.  
  3100.     my $count = 0;
  3101.  
  3102.     my @buckets = $self->{c__}->get_all_buckets( $self->{api_session__} );
  3103.  
  3104.     foreach my $bucket (@buckets) {
  3105.         $count += $self->get_bucket_parameter__( $bucket, 'count' );
  3106.     }
  3107.  
  3108.     return $count;
  3109. }
  3110.  
  3111. sub ecount__
  3112. {
  3113.     my ( $self ) = @_;
  3114.  
  3115.     my $count = 0;
  3116.  
  3117.     my @buckets = $self->{c__}->get_all_buckets( $self->{api_session__} );
  3118.  
  3119.     foreach my $bucket (@buckets) {
  3120.         $count += $self->get_bucket_parameter__( $bucket, 'fncount' );
  3121.     }
  3122.  
  3123.     return $count;
  3124. }
  3125.  
  3126. #----------------------------------------------------------------------------
  3127. #
  3128. # get_bucket_parameter__/set_bucket_parameter__
  3129. #
  3130. # Wrapper for Classifier::Bayes::get_bucket_parameter__ the eliminates
  3131. # the need for all our calls to mention $self->{api_session__}
  3132. #
  3133. # See Classifier::Bayes::get_bucket_parameter for parameters and
  3134. # return values.
  3135. #
  3136. # (same thing for set_bucket_parameter__)
  3137. #
  3138. #----------------------------------------------------------------------------
  3139. sub get_bucket_parameter__
  3140. {
  3141.  
  3142.     # The first parameter is going to be a reference to this class, the
  3143.     # rest we leave untouched in @_ and pass to the real API
  3144.  
  3145.     my $self = shift;
  3146.     return $self->{c__}->get_bucket_parameter( $self->{api_session__}, @_ );
  3147. }
  3148. sub set_bucket_parameter__
  3149. {
  3150.     my $self = shift;
  3151.     return $self->{c__}->set_bucket_parameter( $self->{api_session__}, @_ );
  3152. }
  3153.  
  3154. # GETTERS/SETTERS
  3155.  
  3156. sub classifier
  3157. {
  3158.     my ( $self, $value ) = @_;
  3159.  
  3160.     if ( defined( $value ) ) {
  3161.         $self->{c__} = $value;
  3162.     }
  3163.  
  3164.     return $self->{c__};
  3165. }
  3166.  
  3167. sub language
  3168. {
  3169.     my ( $self ) = @_;
  3170.  
  3171.     return %{$self->{language__}};
  3172. }
  3173.  
  3174. sub session_key
  3175. {
  3176.     my ( $self ) = @_;
  3177.  
  3178.     return $self->{session_key__};
  3179. }
  3180.  
  3181.  
  3182. #----------------------------------------------------------------------------
  3183. # shutdown_page__
  3184. #
  3185. #   Determines the text to send in response to a click on the
  3186. #   shutdown link.
  3187. #----------------------------------------------------------------------------
  3188. sub shutdown_page__
  3189. {
  3190.     my ( $self ) = @_;
  3191.  
  3192.     # Figure out what style sheet we are using
  3193.     my $root = 'skins/' . $self->config_( 'skin' ) . '/';
  3194.     my $css_file = $self->get_root_path_( $root . 'style.css' );
  3195.     if ( !( -e $css_file ) ) {
  3196.         $root = 'skins/default/';
  3197.         $css_file = $self->get_root_path_( $root . 'style.css' );
  3198.     }
  3199.  
  3200.     # Now load the style sheet
  3201.  
  3202.     my $css = '<style type="text/css">';
  3203.     open CSS, $css_file;
  3204.     while ( <CSS> ) {
  3205.         $css .= $_;
  3206.     }
  3207.     close CSS;
  3208.     $css .= "</style>";
  3209.  
  3210.     # Load the template, set the class of the menu tabs, and send the output to $text
  3211.  
  3212.     my $templ = $self->load_template__( 'shutdown-page.thtml' );
  3213.  
  3214.     for my $i (0..5) {
  3215.         $templ->param( "Common_Middle_Tab$i" => "menuStandard" );
  3216.     }
  3217.  
  3218.     my $text = $templ->output();
  3219.  
  3220.     # Replace the reference to the favicon, we won't be able
  3221.     # to handle that request
  3222.     $text =~ s/<link rel="icon" href="favicon\.ico">//;
  3223.  
  3224.     # Replace the link to the style sheet with the style sheet itself
  3225.     $text =~ s/\Q<link rel="stylesheet" type="text\/css" href="${root}style.css" title="POPFile-Style">\E/$css/;
  3226.  
  3227.     # Remove the session key from the menu links:
  3228.  
  3229.     $text =~ s/href="(.+?)\?session=.+?"/href="$1"/g;
  3230.  
  3231.     return $text;
  3232. }
  3233.  
  3234. 1;
  3235.