#!/usr/bin/env perl
use strict;
use v5.10;

# ====================[ logout.pl                          ]====================

=head1 NAME

logout - An Oddmuse module for logging out the current Oddmuse user.

=head1 SYNOPSIS

logout logs out the current Oddmuse user from the current Oddmuse Wiki by
clearing that user's client-side, Oddmuse-specific HTML cookie. As this cookie
persists that user's username and and (optional) password for this Oddmuse Wiki,
clearing this cookie effectively logs that user off this Oddmuse Wiki.

Viola!

=head1 INSTALLATION

logout is easily installable: move this file into the B<wiki/modules/>
directory of your Oddmuse Wiki.

=cut
AddModuleDescription('logout.pl', 'Logout Extension');

our ($q, %Action, $CommentsPrefix, $Message, $LinkPattern, $FreeLinks, $FreeLinkPattern, $SiteName, %CookieParameters);

# ....................{ CONFIGURATION                      }....................

=head1 CONFIGURATION

logout is easily configurable: set these variables in the B<wiki/config.pl>
file for your Oddmuse Wiki.

=cut
our ($CommentsSuffix,
            $LogoutIsDebugging);

=head2 $CommentsSuffix

A string that, unless blank, supplants that default
"${CommentsPrefix}${PageName}" link (e.g., "Comment on Logout_Extension")
in the edit bar with a new, page-agnostic
"${CommentsPrefix}${CommentsSuffix}" link (e.g., "Comment on this page"). Since
this string is blank, by default, it performs no such replacement. Enable it by
setting the string to some non-blank value in your B<wiki/config.pl> file; e.g.,

  $CommentsSuffix = 'this page';

If you do set this variable, please also ensure that you have set the
C<$CommentsPrefix> variable. (If that variable is not set, but this variable is,
this variable is rudely ignored. Such is life in the insensate code trenches!)

This variable's intent is to lend some minute uniformity to the edit bar. All
the edit bar's other links ("Edit this page," "View other revisions",
"Administration," and so on) are page-agnostic; these links do not reference
the current page's name. Why, then, should the comment link be any different?
While an admittedly minor point, it is a point... This variable addresses it.

Lastly. Although this variable has, clearly, little relation to the cookie-
clearing implementation of the rest of this module, this module's author
conspired no better place for it - and therefore placed it here. (Do with it
what thou wilt, museful wrangler!)

=cut
#$CommentsSuffix = 'this page';
$CommentsSuffix = '';

=head2 $LogoutIsDebugging

A boolean that, if true, prints all key-value pairs (composing the currently
requested URL query and current user's cookie) with each Oddmuse Wiki page; and,
if false, does nothing. This boolean defaults to false.

Key-value pairs are printed by appending their contents onto Oddmuse's
C<$Message> variable, which Oddmuse then tacks onto the header for each page.

=cut
$LogoutIsDebugging = '';

# ....................{ ACTIONS                            }....................
$Action{logout} = \&DoLogout;

=head1 ACTIONS

=head2 DoLogout

Logs the current user "out."

This erases every entry in that user's client-side, site-specific cookie, which
has several unnerving effects:

=over

=item The user's currently cached username is discarded. As the username, in
      the Oddmuse security model, is an aesthetic artifice having no relation
      to whether that user is an editor, administrator, or merely 'visitor',
      this doesn't do very much.

=item The user's currently cached password is discarded. If the user was logged
      in as editor or administrator, that user is now logged off and, hereafter,
      merely considered a 'visitor'.

=item All other key-value pairs are discarded. (This might serve as a decent
      mechanism for testing cookie-specific functionality in your own module,
      elsewhere.)

=back

=cut
sub DoLogout {
  my $id = shift;

  SetParam('username', $CookieParameters{username});
  SetParam('pwd', $CookieParameters{pwd});

  print
    GetHeader('', Ts('Logged out of %s', $SiteName), '') .
    $q->div({-class=> 'content'}, $q->p(T('You are now logged out.'), $id ? $q->p(ScriptLink('action=browse;id=' . UrlEncode($id) . '&time=' . time, Ts('Return to %s', NormalToFree($id)))) : ''));
  PrintFooter();
}

# ....................{ FUNCTIONS                          }....................
*GetFooterLinksLogoutOld = \&GetFooterLinks;
*GetFooterLinks =          \&GetFooterLinksLogout;

=head1 FUNCTIONS

=head2 GetFooterLinksLogout

Appends a "Logout" link onto the edit bar in the footer of each page.

=cut
sub GetFooterLinksLogout {
  my ($id, $rev) = @_;
  my  $footer_links = GetFooterLinksLogoutOld(@_);

  if ($CommentsPrefix and $CommentsSuffix) {
    $footer_links =~ s
      /(\Q<a class="comment\E.+?>).+?(<\/a>)
      /$1.NormalToFree($CommentsPrefix.$CommentsSuffix).$2
      /ex;
  }

  # Display the link to the "Logout" action iff the current user's already logged
  # in with some username or password.
  if (GetParam('username', '') ne '' or
      GetParam('pwd',      '') ne '') {
    my $action = 'action=logout';
    $action .= ';id=' . UrlEncode($id) if $id;
    $footer_links =~ s
      /(.+)(<\/.+?>)$
      /$1.' '.ScriptLink($action, T('Logout'), 'logout').$2
      /ex;
  } else {
    my $action = 'action=password';
    $action .= ';id=' . UrlEncode($id) if $id;
    $footer_links =~ s
      /(.+)(<\/.+?>)$
      /$1.' '.ScriptLink($action, T('Login'), 'login').$2
      /ex;
  }

  return $footer_links;
}

=head2 CookieUsernameFix

Corrects the C<CookieUsernameFix> function, which, in its original definition,
caused a festering heap of trouble.

We suspect a flaw in the innate coding of that function. But, whatever the
fickle case, it's supplanted here with a (somewhat) stabler version.

=cut
sub CookieUsernameFix {
  if ($LogoutIsDebugging) {
    $Message .= "<table>";

    $Message .= "<tr><td>QUERY::</td></tr>";
    my %query_parameters = $q->Vars;
    foreach my $query_parameter_name (keys %query_parameters) {
      $Message .= "<tr>"
        ."<td>${query_parameter_name}:</td>"
        ."<td>$query_parameters{$query_parameter_name}</td></tr>";
    }

    $Message .= "<tr><td>COOKIE::</td></tr>";
    my ($changed, %cookie_parameters) = CookieData();
    foreach my $cookie_parameter (keys %cookie_parameters) {
      $Message.="<tr>"
        ."<td>${cookie_parameter}:</td>"
        ."<td>$cookie_parameters{$cookie_parameter}</td></tr>";
    }

    $Message .= "</table>";
  }

  # Only valid usernames get stored in the new cookie.
  my   $name = GetParam('username', '');
  if (!$name) { }
  elsif (!$FreeLinks && !($name =~ /^$LinkPattern$/)) {
    CookieUsernameFixDelete(Ts('Invalid UserName %s: not saved.', $name));
  }
  elsif ($FreeLinks && (!($name =~ /^$FreeLinkPattern$/))) {
    CookieUsernameFixDelete(Ts('Invalid UserName %s: not saved.', $name));
  }
  elsif (length($name) > 50) {  # Too long
    CookieUsernameFixDelete(T('UserName must be 50 characters or less: not saved'));
  }
}

sub CookieUsernameDelete {
  if ($LogoutIsDebugging) {
    $Message .= $q->p(shift);
  }
  $q->delete('username');
}

=head1 INTERFACE

logout considers an Oddmuse user to be logged in if and only if that user has
entered either some username or one of two site-wide passwords. (See
L<SECURITY>, below.)

If the current Oddmuse Wiki user is logged in, this module appends a "Logout"
link to the edit bar; if not, this module does not. (That, perchance, ill-named
edit bar is the second line of navigational, administerial links in the footer
for each page.)

If the current Oddmuse Wiki user is logged in and does click on that "Logout"
link in the edit bar, this module logs out that user by clearing the user's
client-side, site-specific cookie of all key-value pair content.

Oddmuse should, arguably, bundle this easy functionality into the main
Oddmuse script. It doesn't; so, we do it here.

=head1 SECURITY

logout inherits its security model from Oddmuse, to which it makes no serious
change. Now, Oddmuse has no pre-defined mechanism for managing users; rather,
when editing any editable page or commenting on any commentable page, anyone
may enter any username in the "Username:" edit field. That username need not
be registered, approved, or otherwise managed (as in other content management
systems). That username, as such, has no user-defined password, passphrase, or
other identifying token. Any user may masquerade as any other user, with happy
impunity!

logout retains this (admittedly loose) concept of a "user."

=head1 SEE ALSO

logout is "little brother" to the login module - from which it was inspired and
for which it's partly named, in antiparallel.

logout only implements a slim subset of functionality implemented by the login
module. For a full-bodied, fully configurable alternative to Oddmuse security,
please use that module instead.

=head1 COPYRIGHT AND LICENSE

The information below applies to everything in this distribution,
except where noted.

Copyleft 2008 by B.w.Curry <http://www.raiazome.com>.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

=cut
