#!/opt/local/bin/perl
#
# Script to edit any of the Division section whiteboards
# Access to script via browser is restricted to members of the dma unix security group
#with .htacc file.
use strict;
use warnings;
# First things first. If I can't figure out who you are, you're outta here.
my $editor;
if ( exists( $ENV{REMOTE_USER} ) ) {
($editor) = $ENV{REMOTE_USER} =~ m/(m[1|3][[:alpha:]]{3}\d{2})/i;
}
($editor) || die "I cannot determine who you are; therefore, you can't access this area.\n
If you feel you are receiving this message mistakenly, please contact Jonathan Crane.\n
I stopped ";
#Uh, second things second!
#what groups is the user in?
#Assumption: first group in list is default group.
my $groups = `id -Gn $editor`;
#If I can't determine what groups you're in, like an angry ump,
#I'm throwing you out of the game
$? && die "id cmd failed w/ code $?\n can't tell what groups you're in; I have to stop";
##########################################################################################
#Modules/Pragmas
##########################################################################################
use CGI qw/:all *table'/;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use DBI;
use Readonly;
##########################################################################################
#some "global" variables (note we already set two, $editor and $groups, in the
#preliminary steps)
##########################################################################################
#DBI->trace(15); # for debugging
$\ = "\n"; #for prettier source HTML
#finish setting the stuff from before
chomp $groups;
Readonly my @GROUPS => split /\s+/, $groups;
#start the rest from scratch
Readonly my $VERSION => '1.0b';
Readonly my $WIN_TITLE => "Division of Morganic Affairs Whiteboard "
. $VERSION; # This is the window title
Readonly my $SUBTITLE_1 =>
"Morganic Affairs"; # text that appears in the header in smaller text (span)
Readonly my $SUBTITLE_2 =>
"Whiteboard"; #This is the text that appears in the header in the biggest text
Readonly my $DB_FILE => "/dma/lib/www/wb/wb/wbtest";
Readonly my $TABLE => 'wbmain';
Readonly my $EDIT_BUTTON_NAME => 'editbutton';
Readonly my $TEXT_AREA_NAME => 'noteedit'; #Note Edit Text Area Name
Readonly my $TO_MAIN_BUTTON_NAME => 'backto';
Readonly my $TO_MAIN_MSG => 'MA Division whiteboard';
my $netac = 80; #"Note Edit Text Area Columns!"
my $netar = 10; #"Note Edit Text Area Rows!"
Readonly my $COMMIT => 'commitbutton'; #"Note Edit Commit Button Name!"
my $cbn = 'Cancel'; #"Cancel Button Name!"
Readonly my $CLEAR => 'Clear'; #Clear button name
my $sbn = 'sectionbutton'; #by now I hope you can guess.
my $css = '/dma/wb/wb/wb.css'; #the stylesheet we'll be using
#Column name words are separated by underscores
Readonly my $PRIV_U => 'Private_';
Readonly my $PRIV => 'private';
#Content Note
my $cn =
"Members of the dma security group can view all content not explicitly marked as $PRIV";
my $tablestyle = {
-cellpadding => 1,
-cellspacing => 1,
-border => 2,
-frame => 'box',
-align => 'center'
};
my $pam1 = "$PRIV area
(only members of the section security group can view and edit the data below)
";
#"Private Area Message 1"
my $pam2 = "End $PRIV area";
my %sections = (
" DMA" => 'dma',
FST => 'dma/fst',
MRA => 'dma/mra',
BKS => 'bks',
MSU => 'dma/msu',
FMA => 'dma/fma',
FOMC => 'dma/FOMC'
);
my @dcols =
( "Name", "User_ID", "Note", "Last_Edit" ); #default columns for select statements
#The following two regexps lifted from Jeff Friedl's "Mastering Regular Expressions,"
#2nd ed, p. 206 with slight mods
my $hnr = qr/ #"Host Name Regex
(?: [a-z0-9] (?:[-a-z0-9]*[a-z0-9])? \. )+ # sub domains
# Now ending .com, etc.
(?: com\b
| edu\b
| biz\b
| org\b
| gov\b
| in(?:t|fo)\b # .int or .info
| mil\b
| net\b
| name\b
| museum\b
| coop\b
| aero\b
| [a-z][a-z]\b # two-letter country codes
)/x;
my $pr = qr{ #Path Regex--using braces since there's a slash in it.
(?:
/
# The rest are heuristics for what seems to work well
[^.!,?;"'<>()\[\]{}\s\x7F-\xFF]*
(?:
[.!,?]+ [^.!,?;"'<>()\[\]{}\s\x7F-\xFF]+
)*
)?}x;
my $lr = qr{\b
# Match the leading part (proto://hostname, or just hostname)
(?:
# ftp://, http://, or https:// leading part
(?:ftp|https?)://[-\w]+(?:\.\w[-\w]*)+
|
# or, try to find a hostname with our more specific sub-expression
#made the parens that enclose the proto part capturing so we can test for
#participation of $2 in the substitution later
($hnr)
)
# Allow an optional port number
(?: : \d+ )?
# The rest of the URL is optional, and begins with /, and is represented by $pr
$pr
}x;
Readonly my $LEAVE_CAL_URL => '/cgi-bin/dma/LEAVEcal/calendar.pl';
Readonly my $LEAVE_CAL_TEXT => 'MA Leave Calendar';
##########################################################################################
#subs (OK. sub)
##########################################################################################
sub fixtextforbrowser {
#assume just one argument (hey, this is _my_ program, after all!)
my $text = $_[0];
#first, escape whatever stuff is in the string
#that might be (mis)interpreted by the browser
$text = escapeHTML($text);
#Second, turn the line breaks from the text in the text
#area into HTML
tags for proper display
#in the table
$text =~ s/\r/
/g;
#Third, linkify URLs
$text =~ s!($lr)!($2)?"$1":"$1"!ge;
#returning a value allows you to use it in assignment, etc.
return $text;
} ## end sub fixtextforbrowser
##########################################################################################
#the program actually starts here
##########################################################################################
print header();
#for debugging; unfortunately, we have to do it after we print a header so it goes here.
warningsToBrowser(1);
print start_html( -style => { 'src' => "$css" }, -title => "$WIN_TITLE" );
print "
";
print "
";
my @la;
foreach my $s ( sort keys(%sections) ) {
push @la, a( { -href => "/$sections{$s}" }, "$s HOME" );
}
my $homes = join( " |", @la );
print $homes;
my @pipes;
my $pos;
while ( $homes =~ m/\|/g ) {
push @pipes, pos($homes) - 1;
}
my $numps = scalar(@pipes);
my $ptr = $numps - int( $numps / 2 );
substr( $homes, $pipes[$ptr], 1, br() );
#print $homes;
print "
";
print h1(
$SUBTITLE_2,
span( { -class => 'subtitle' }, $SUBTITLE_1 ),
span( { -class => 'versioninfo' }, "version $VERSION" )
);
print "";
print '';
print a( { -href => $LEAVE_CAL_URL, }, $LEAVE_CAL_TEXT );
#print Dump( param() );
print start_form();
#looks like this stuff should get done no matter what (at this stage anyway)
my $dbh = DBI->connect( "dbi:SQLite:dbname=$DB_FILE", "", "" ) || die $DBI::errstr;
#We kind of pre-check here to see if we need to update the DB, but this part doesn't
#do any printing.
if ( param($COMMIT) ) {
#web user has commited a change to someone's note
#no printing in this block unless we're debugging
local $\ = "
\n";
#Here we have to deal with the special cases of peoples' names
#containing apostrophes/single quotes
my $rawname = param($COMMIT);
my $i1 = index( $rawname, " " ); #Index of first space (right after 'Commit');
#Index of LAST apostrophe--peoples' names can have apostrophes in them!
#I hope no one's name ever contains the string below!
my $i2 = index( $rawname, "'s " );
my $uname = substr( $rawname, $i1 + 1, $i2 - ( $i1 + 1 ) );
my $note = param($TEXT_AREA_NAME);
#set "Tracking Info"--OK to use
because it's only ever stored and displayed
my $ti = localtime( time() ) . "
$editor";
my ( $priv, $priv_u ) = ( q{}, q{} );
if ( $rawname =~ $PRIV ) {
$priv_u = $PRIV_U;
$priv = $PRIV;
}
my $ss = "update $TABLE set ${priv_u}Note=?, Last_${priv_u}Edit = ? where Name = ?";
my $num_rows_affected = $dbh->do( $ss, undef, ( $note, $ti, $uname ) )
|| die $dbh->errstr;
if ( $num_rows_affected == 0 ) {
warn "Hey! No rows updated!";
}
#Now let's vacuum the database since a change has been made; could be
#an addition or could be a deletion.
#Keeps the DB file from growing larger than necessary.
#maybe dying here is a little extreme?
$dbh->do("VACUUM") || die $dbh->errstr;
#have to do this little bit because it makes more sense to
#return the user to the section view rather
#than the main view so they can view their handiwork
$ss = "select Section from $TABLE where Name = ?";
my $href = $dbh->selectall_hashref( $ss, 'Section', undef, $uname )
|| die $dbh->errstr;
#(that's going to be a hash with one key)
my @sec = keys(%$href);
#Make sure we got a row back!
if ( !scalar @sec ) {
warn "select statement returned no rows! Selected on name '$uname'.";
}
#This is a dummy param to get us back to the section view
#it's used before reload so no need for a hidden field to make it persist past the
#next submit/reload
param( -name => $sbn, -value => "View $sec[0]'s whiteboard" );
} ## end if ( param($COMMIT) )
##########################################################################################
#OK so default view is your section view. That's !param--first opening of the page
#so if that's not true, we check to see if it's an 'explicit' request to see a section
#whiteboard. If so, we also make sure it's not after pressing the "clear" button
#on the edit note screen.
if ( !param() || ( param($sbn) && !param($CLEAR) ) ) {
#am I in this group? not right now, we'll decide in an if statement later
my $insider = 0;
my $sec;
#if we're here because someone pressed a button to
#view the page, extract the necessary info
if ( param($sbn) ) {
#user wants to view a specific section web page,
#or just edited a person's Note,
#or just canceled out of editing a person's note
#and is back to page for that person's section
my $rawsec = param($sbn); #the "Raw" section name
my $i1 = index( $rawsec, q{ } ); #Index of first space (right after Edit);
my $i2 = index( $rawsec, q{'} ); #Index of first (and only) apostrophe
#Could well be coming in upper case
$sec = lc( substr( $rawsec, $i1 + 1, $i2 - ( $i1 + 1 ) ) );
} ## end if ( param($sbn) )
else {
#use the user's default group
$sec = $GROUPS[0];
}
print b("$sec"), br(), "section whiteboard";
print br();
#Another button to get web user back to main page (more
#easily seen when notes make table go past bottom of screen)
print submit( -name => $TO_MAIN_BUTTON_NAME, -value => $TO_MAIN_MSG );
#Now construct the SQL statement
my $ss = 'select '
. join( ",", @dcols )
. " from $TABLE where Section = ? order by length(User_ID), User_ID";
#print $ss;
my $aref = $dbh->selectall_arrayref( $ss, undef, $sec ) || die $dbh->errstr;
my @header = @dcols;
#this is the "big if"
if ( $groups =~ /(^|\s+)$sec($|\s+)/ ) {
push @header, "Edit";
$insider = 1;
}
foreach (@header) {
s/_/ /g;
}
print start_table($tablestyle);
print Tr( th( \@header ) );
foreach my $user ( @{$aref} ) {
#we have to do this so if people type or whatever it doesn't mess things up
#AND to then properly display line breaks
$user->[2] = fixtextforbrowser( $user->[2] );
#this is the "big if" here
if ($insider) {
$user->[ scalar(@$user) ] =
submit( -name => $EDIT_BUTTON_NAME, -value => "Edit $user->[1]'s note", );
}
print Tr( { -align => 'center', -valign => 'top' }, td($user) );
} ## end foreach my $user ( @{$aref} )
print end_table();
print submit( -name => $TO_MAIN_BUTTON_NAME, -value => $TO_MAIN_MSG );
if ($insider) {
print hr( { -size => 3 } );
print $pam1;
#columns for private data
my @pcols = ( "Name", "User_ID", "Private_Note", "Last_Private_Edit" );
$ss = 'select '
. join( ",", @pcols )
. " from $TABLE where Section = ? order by length(User_ID), User_ID";
my $aref = $dbh->selectall_arrayref( $ss, undef, $sec ) || die $dbh->errstr;
print start_table($tablestyle);
my @phead = ( @pcols, "Edit" );
foreach (@phead) {
s/_/ /g;
}
print Tr( th( \@phead ) );
foreach my $user ( @{$aref} ) {
$user->[2] = fixtextforbrowser( $user->[2] );
$user->[ scalar(@$user) ] = submit(
-name => $EDIT_BUTTON_NAME,
-value => "Edit $user->[1]'s $PRIV note",
);
print Tr( { -align => 'center', -valign => 'top' }, td($user) );
}
print end_table();
print submit( -name => $TO_MAIN_BUTTON_NAME, -value => $TO_MAIN_MSG );
print br();
print $pam2;
#print hr({-color=>'black'});
} ## end if ($insider)
} ## end if ( !param() || ( param($sbn) && !param($CLEAR) ) )
elsif ( param($TO_MAIN_BUTTON_NAME) ) {
#Default main page with just the general notes for each section;
#general note for MA (program direction) section
#is main note for all of MA Division
#Create some space between the masthead and the table
print br();
my $ss = "select "
. join( ",", @dcols )
. " from $TABLE where Name like '%General' order by length(Section), Section, Name";
#print "The select statement is !!$ss!!";
my $aref = $dbh->selectall_arrayref($ss) || die $dbh->errstr;
print start_table($tablestyle);
my @header = ( @dcols, "Section Whiteboard" );
#Column names are separated by underscores in DB, convert them to spaces for display
foreach (@header) {
s/_/ /g;
}
print Tr( th( \@header ) );
foreach my $user (@$aref) {
#we do this so if people type or whatever in the note it doesn't mess things up
#AND to properly display line breaks in the note
$user->[2] = fixtextforbrowser( $user->[2] );
#The default page is just a listing of sections so we're "pushing"
#submit buttons for viewing each of the sections
$user->[ scalar(@$user) ] = submit( $sbn, "View $user->[1]'s whiteboard" );
print Tr( { -align => 'center', -valign => 'top' }, td($user) );
} ## end foreach my $user (@$aref)
print end_table();
} ## end elsif ( param($TO_MAIN_BUTTON_NAME) )
elsif ( param($EDIT_BUTTON_NAME) ) {
#Someone wants to edit someone's note.
#no table so no header here
my $rawuid = param($EDIT_BUTTON_NAME);
my $i1 = index( $rawuid, " " ); #Index of first space (right after Edit);
#Index of end of m1 ID
my $i2 = index( $rawuid, "'s " );
my $uid = substr( $rawuid, $i1 + 1, $i2 - ( $i1 + 1 ) );
my ( $priv, $priv_u ) = ( q{}, q{} );
if ( $rawuid =~ $PRIV ) {
$priv_u = $PRIV_U;
$priv = $PRIV;
}
my $st = "select Name, ${priv_u}Note, User_ID, Section from $TABLE where User_ID = ?";
my $href = $dbh->selectall_hashref( $st, "User_ID", undef, $uid ) || die $dbh->errstr;
my $uname = $href->{$uid}->{Name};
#this here was problematic
print "You are editing the";
if ( $priv ne q{} ) {
print b($priv);
}
print "note that will be displayed on the Morganic Affairs whiteboard for";
print h3($uname);
#This gets all but the first and last single-quotes, hard-coded into query below
my $inlist = join( "','", @GROUPS );
my $note = $href->{$uid}->{"${priv_u}Note"};
#If user hit the "Clear" button, we need to clear BOTH $note AND the
#textarea param due to its "stickiness"
if ( param($CLEAR) ) {
#print "You hit the Clear button!";
$note = '';
param( $TEXT_AREA_NAME, '' );
}
print textarea(
-name => $TEXT_AREA_NAME,
-default => $note,
-rows => $netar,
-columns => $netac
);
#just putting in an h1 gives the same spacing as "
", which is more cumbersome.
print h1(
submit( $CLEAR, $CLEAR ),
submit( -name => $COMMIT, -value => "Commit ${uname}'s $priv note" ),
submit( $cbn, $cbn )
);
#This is a dummy param to get us back to the section view if the user cancels
#(works the similarly to the
#same line at the end of the conditional block for if the Commit button was pressed)
print hidden( -name => $sbn, -value => "View $href->{$uid}->{Section}'s whiteboard" );
#Another hidden field, necessary for the "Clear" button
print hidden( -name => $EDIT_BUTTON_NAME, -value => "Edit ${uid}'s $priv note" );
#Note that the noteeditbutton param ($TEXT_AREA_NAME) will also be passed,
#but the $COMMIT (commit button) won't
#So no database changes will be made!
} ## end elsif ( param($EDIT_BUTTON_NAME) )
print end_form();
print "";
print "
";
}
print end_html();