#!/usr/bin/perl -w
use strict;

## Scott Wiersdorf
## Created: Mon May 19 11:55:06 MDT 2003
## $Id$

## a simple CGI for crontab editing
##
## most useful under Apache's suEXEC wrapper ;o)
##
## This program is a demonstration of the capabilities of the
## Config::Crontab module. The bulk of this program is CGI logic (tip
## o' the hat to Lincoln Stein), but look for the Config::Crontab::*
## calls and objects named $ct, $block, $newblock, and $obj. These are
## all Config::Crontab::* objects (and the reason for this demo
## program's creation).
##

##
## WARNING * ACHTUNG * AVISO ##
##
## This program may modify your crontab file. This program is provided
## AS IS under the terms of the Perl Artistic License to illustrate
## some possible uses of Config::Crontab.
##
## This program inherently is a security problem because it provides
## no authentication mechanism. You should enable your web server's
## authentication to prevent unauthorized access to this script.
##
## The author assumes no liability for damages of any kind caused by
## (mis)?use of this program. Please see paragraph 10 of the Artistic
## License distributed with Perl. Have a nice day!
##

## to do:
##
## - enable/disable blocks and lines within a block

use CGI;
use Config::Crontab;

#########################
## user-servicable parts:
#########################
my $file = '';    ## set this to a pathname of a file (it needn't
		  ## exist) if you want to practice with a temporary
                  ## crontab file instead of your real one
my $DEBUG = 0;    ## a little extra information in your error_log

#######################################
## no user-servicable parts below here:
#######################################
my $q = new CGI;
my $self = $q->url;
my $info;  ## used for passing messages to main form

print $q->header;

ACTION: {

    #############################################################
    #############################################################
    ##                                                         ##
    ## General Block Operations: these don't require 'blockno' ##
    ##                                                         ##
    #############################################################
    #############################################################

    ##############################################
    ##         Cancel Block Operation           ##
    ##############################################
    if( $q->param('Block Cancel') || $q->param('Cancel') ) {
	$info = "Action cancelled";
	last ACTION;
    }


    ##############################################
    ##        Complete Block Operation          ##
    ##############################################
    elsif( $q->param('Block Done') ) {
	$info = "Action completed";
	last ACTION;
    }

    ##############################################
    ##              New Raw Block               ##
    ##############################################
    elsif( $q->param('Block Raw New') ) {
	## display edit page and exit
	print $q->start_html("New crontab block for " . getpwuid($<)),
	  $q->strong('Add a new crontab entry:'), $q->p, "\n\n";
	print "<ul>" . $info . "</ul>" if $info;
	print $q->start_form;
	print $q->textarea( -name    => 'blocktext',
			    -default => '',
			    -rows    => 10,
			    -columns => 50 );
	print $q->p, "\n";
	print $q->submit('Block New Commit', 'Commit'), $q->reset, $q->submit('Block Cancel', 'Cancel');
	print $q->end_form;
	print $q->end_html;
	exit;
    }

    ##########################################
    ##          New Block Commit            ##
    ##########################################
    elsif( $q->param('Block New Commit') ) {
	my $ct = new Config::Crontab( -file => $file );  $ct->read;
	my $bt = $q->param('blocktext'); $bt =~ s/\r\n/\n/g;
	my $newblock = new Config::Crontab::Block( -data => $bt );
	$ct->last($newblock);
	$ct->write;
	$info = "New block added";
	last ACTION;
    }

    ##############################################
    ##               New Block                  ##
    ##############################################
    elsif( $q->param('Block New') ) {
	my $ct = new Config::Crontab( -file => $file );  $ct->read;
	$ct->first(new Config::Crontab::Block(-data => '## new cron event') );
	$ct->write;

	$q->param('blockno', 0);
	$q->param('Block Edit', 1);
    }

    ## add new non-blockno operations here

    #########################################################
    #########################################################
    ##                                                     ##
    ## Block Operations: these require 'blockno' to be set ##
    ##                                                     ##
    #########################################################
    #########################################################
    unless( defined $q->param('blockno') ) {
	print STDERR "No block found. Jumping to end of ACTION\n" if $DEBUG;
	last ACTION;
    }

    ## parse the crontab file
    my $ct = new Config::Crontab( -file => $file );  $ct->read;
    my $block = ($ct->blocks)[$q->param('blockno')];
    unless( ref($block) ) {
	$info = "Couldn't find block!";
	print STDERR "Block " . $q->param('blockno') . " missing\n" if $DEBUG;
	last ACTION;
    }

    ##############################################
    ##             Raw Block Edit               ##
    ##############################################
    if( $q->param('Block Raw Edit') ) {
	print $q->start_html("Edit crontab block for " . getpwuid($<)),
	  $q->strong('Edit this block:'), $q->p, "\n\n";
	print "<ul>" . $info . "</ul>" if $info;
	print $q->start_form;
	print $q->hidden('blockno', $q->param('blockno'));
	print $q->textarea( -name    => 'blocktext',
			    -default => $block->dump,
			    -rows    => 10,
			    -columns => 50 );
	print $q->p, "\n";
	print $q->submit('Block Commit', 'Commit'),
	  $q->reset,
	    $q->submit('Block Cancel', 'Cancel');
	print $q->end_form;
	print $q->end_html;
	exit;
    }

    ##############################################
    ##             Commit Block                 ##
    ##############################################
    elsif( $q->param('Block Commit') ) {
	unless( defined $q->param('blocktext') && $q->param('blocktext') ) {
	    $info = "No blocktext";
	    print STDERR "Commit: $info\n" if $DEBUG;
	    last ACTION;
	}

	my $bt = $q->param('blocktext'); $bt =~ s/\r\n/\n/g;
	my $newblock = new Config::Crontab::Block( -data => $bt );
	$ct->replace( $block, $newblock );
	$ct->write;
	$info = "New block written";
	last ACTION;
    }

    ##############################################
    ##             Delete Block                 ##
    ##############################################
    elsif( $q->param('Block Delete') ) {
	$ct->remove($block);
	$ct->write;
	$info = "Block deleted!";
	last ACTION;
    }

    ##############################################
    ##               Move Block                 ##
    ##############################################
    elsif( $q->param('Block Up')    || $q->param('Block Down') ||
	   $q->param('Block First') || $q->param('Block Last') ) {

	if(    $q->param('Block Up')    ) { $ct->up($block)    }
	elsif( $q->param('Block Down')  ) { $ct->down($block)  }
	elsif( $q->param('Block First') ) { $ct->first($block) }
	elsif( $q->param('Block Last')  ) { $ct->last($block)  }

	$ct->write;
	$info = "Block moved";
	last ACTION;
    }

    ##############################################
    ##               Edit Block                 ##
    ##                                          ##
    ## This section is a "substate" since it    ##
    ## loops within itself until 'Done' is      ##
    ## pressed.                                 ##
    ##                                          ##
    ##############################################
    if( $q->param('Block Edit') ) {

      BLOCK_EDIT: {

	    ######################################
	    ## all sections below require 'blockno'
	    ######################################
	    my $block = ($ct->blocks)[$q->param('blockno')];
	    unless( ref($block) ) {
		$info = "Couldn't find block";
		print STDERR "$info (" . $q->param('blockno') . ")\n" if $DEBUG;
		last ACTION;
	    }

	    ######################################
	    ## 'objno' not required
	    ######################################

	    if( $q->param('New Line') ) {
		if( $q->param('newobjtype') eq 'Comment' ) {
		    $block->last(new Config::Crontab::Comment(-data => '## comment'));
		} elsif( $q->param('newobjtype') eq 'Environment' ) {
		    $block->last(new Config::Crontab::Env(-data => 'NAME=value'));
		} elsif( $q->param('newobjtype') eq 'Event' ) {
		    $block->last(new Config::Crontab::Event(-data => '0 0 * * 1 /bin/true'));
		} else {
		    $info = "Unknown object type";
		    print STDERR "Edit (new line): $info (" . $q->param('newobjtype') . ")\n" if $DEBUG;
		    last ACTION;
		}
		$ct->write;
		$info = "New " . $q->param('newobjtype') . " created";
		last BLOCK_EDIT;
	    }

	    ######################################
	    ## the following sections require 'objno'
	    ######################################
	    last BLOCK_EDIT unless defined $q->param('objno');

	    my $line  = ($block->lines)[$q->param('objno')];

	    if( $q->param('Delete Line') ) {
		$block->remove($line);
		$info = "Line deleted";
	    }
	    elsif( $q->param('Commit Line') ) {
		my $obj;
		if( $q->param('objtype') eq 'comment' ) {
		    $obj = new Config::Crontab::Comment( -data => $q->param('comment') );
		} elsif( $q->param('objtype') eq 'env' ) {
		    $obj = new Config::Crontab::Env( -name  => $q->param('envname'),
						     -value => $q->param('envvalue') );
		} elsif( $q->param('objtype') eq 'event' ) {
		    $obj = new Config::Crontab::Event( -datetime => $q->param('datetime'),
						       -command  => $q->param('command') );
		}
		else {
		    $info = "Invalid object type: " . $q->param('objtype');
		    print STDERR "$info\n" if $DEBUG;
		    last ACTION;
		}
		$block->replace($line, $obj);
		$info = "Line changes saved";
	    }
	    elsif( $q->param('Revert Line') ) {
		$info = "Line reverted";
	    }
	    elsif( $q->param('First Line') ) {
		$block->first($line);
		$info = "Line moved to first";
	    }
	    elsif( $q->param('Last Line') ) {
		$block->last($line);
		$info = "Line moved to last";
	    }
	    elsif( $q->param('Up Line') ) {
		$block->up($line);
		$info = "Line moved up";
	    }
	    elsif( $q->param('Down Line') ) {
		$block->down($line);
		$info = "Line moved down";
	    }
	    else { last BLOCK_EDIT; }
	    $ct->write;
	    last BLOCK_EDIT;
	}

	## this will trigger if user deletes last line in a block, or the
	## block was emptied by someone else before we got here (no locking)
	last ACTION unless $block->lines;

	## display edit block page and exit
	print $q->start_html("Edit crontab block for " . getpwuid($<)),
	  $q->strong('Edit this block:'), "<br>\n";
	print "Date: " . scalar(localtime) . "<br>\n";
	print "<ul>" . $info . "</ul>" if $info;

	print $q->start_form;
	$q->param('Block Edit', 0);
	print $q->hidden('Block Edit');
	print $q->submit('Block Done', 'Done'), $q->reset;
	print "<br>Be sure to 'Commit' your changes before hitting 'Done'<br>\n";
	print $q->end_form;

	print qq!<table border="1"><tr valign="top">\n!;
	print qq!<td>!, $q->start_form;
	print $q->submit("New Line", "Add"), $q->br;
	print $q->hidden('blockno');
	$q->param('Block Edit', 1);
	print $q->hidden('Block Edit');

	print $q->radio_group(-name    => 'newobjtype',
			      -values  => ['Comment','Environment','Event'],
			      -default => 'Event');
	print "\n";
	print $q->end_form, qq!</td></tr></table>\n!;

	print qq!<table border="1">\n!;
	print qq!<tr><td>Delete</td><td>Commit</td><td>Undo</td><td>First</td><td>Up</td><td>Down</td><td>Last</td></tr>\n!;
	my $cols = 10;
	my $i = 0;
	for my $obj ( $block->lines ) {
	    print $q->start_form;
	    print qq!<tr valign="top">\n!;
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!, 
	      $q->submit("Delete Line", 'X'), "</td>\n";
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!,
	      $q->submit("Commit Line", 'O'), "</td>\n";
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!,
	      $q->submit("Revert Line", 'U'), "</td>\n";
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!,
	      $q->submit("First Line", '|<'), "</td>\n";
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!,
	      $q->submit("Up Line", '<'), "</td>\n";
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!,
	      $q->submit("Down Line", '>'), "</td>\n";
	    print q!<td width="! . int(100*(1/$cols)) . q!%">!,
	      $q->submit("Last Line", '>|'), "</td>\n";
	    $q->param('Block Edit', 1);
	    print $q->hidden('Block Edit'), "\n";
	    $q->param('objno', $i++);
	    print $q->hidden(-name => 'objno'), "\n";
	    print $q->hidden('blockno'), "\n";

	    if( UNIVERSAL::isa($obj, 'Config::Crontab::Comment') ) {
		print qq!<td colspan="3" width="! . int(100*(3/$cols)) . q!%">!;
		$q->param('comment', $obj->dump);
		print "Comment:<br>", $q->textfield(-name => "comment",
						    -size  => 25), "\n";
		print $q->hidden('objtype', 'comment'), "\n";
		print "</td>";
	    }

	    elsif( UNIVERSAL::isa($obj, 'Config::Crontab::Env') ) {
		print qq!<td>!;
		$q->param('envname', $obj->name);
		print "Name:<br>", $q->textfield(-name  => "envname",
						 -size  => 12), "\n";
		print "</td><td>=</td><td>";
		$q->param('envvalue', $obj->value);
		print "Value:<br>", $q->textfield(-name => "envvalue",
						  -value => $obj->value), "\n";
		print $q->hidden('objtype', 'env'), "\n";
		print "</td>";
	    }

	    elsif( UNIVERSAL::isa($obj, 'Config::Crontab::Event') ) {
		print qq!<td colspan="1" width="! . int(100*(1/$cols)) . q!%">!;
		$q->param('datetime', $obj->datetime);
		print "Scheduled:<br>", $q->textfield(-name => "datetime",
						      -size  => 12), "\n";
		print qq!</td><td colspan="2" width="! . int(100*(2/$cols)) . q!%">!;
		$q->param('command', $obj->command);
		print "Command:<br>", $q->textfield(-name => "command"), "\n";
		print $q->hidden('objtype', 'event'), "\n";
		print "</td>";
	    }
	    print "</tr>\n\n";
	    print $q->end_form;
	}

	print "</table>\n";
	print $q->start_form;
	$q->param('Block Edit',0);
	print $q->hidden('Block Edit');
	print $q->submit('Block Done', 'Done'), $q->reset;
	print "<br>Be sure to 'Commit' your changes before hitting 'Done'<br>\n";
	print $q->end_form;
	print $q->end_html;

	exit;
    }
}

print $q->start_html("Crontab for " . getpwuid($<)),
  $q->strong('Edit your crontab:'), "
\n\n";
print "Date: " . scalar(localtime) . "
\n";
print "
" . $info . "
" if $info;

print $q->start_form;
print $q->submit("Block New", "New"), $q->submit("Block Raw New", "Raw New"), $q->submit("Cancel"), "\n";
print $q->end_form;
print "
\n";

my $ct = new Config::Crontab( -file => $file); $ct->read;
my $i = 0;
for my $block ( $ct->blocks ) {
    print $q->start_form;
    $q->param('blockno', $i++);
    print $q->hidden( -name=>'blockno' );
    print qq!
\n!;
    print qq! Delete Edit Raw
Edit First Up Down Last
\n!;
    print qq!\n!;
    print " ", $q->submit("Block Delete", "X"), "\n";
    print " ", $q->submit("Block Edit", "E"), "\n";
    print " ", $q->submit("Block Raw Edit", "R"), "\n";
    print " ", $q->submit("Block First", "|<"), "\n";
    print " ", $q->submit("Block Up", "<"), "\n";
    print " ", $q->submit("Block Down", ">"), "\n";
    print " ", $q->submit("Block Last", ">|"), "\n";
    print " 
\n";
    print $block->dump;
    print "
\n";
    print "

\n";

    print $q->end_form;
    print "<hr>\n";
    print "\n\n";
}
print $q->start_form;
print $q->submit("Block New", "New"), $q->submit("Block Raw New", "Raw New"), $q->submit("Cancel"), "\n";
print $q->end_form;
print $q->end_html;
exit;
