#!/usr/bin/perl
$|++; # Turn off print buffering
# print "Content-type: text/html\n\n";
#
# Page
#
#
# page.cgi -- main Edu_RSS management and administration function
# More information: http://www.downes.ca/edurss02.htm
#
# by Stephen Downes
# Version 0.2 - January 2, 2006
#
#
# Copyright 2006 Stephen Downes, National Research Council Canada
# Prerelease - please do not distribute - I'll GPL it when it's ready
#
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use XML::LibXML;
use File::Copy;
use SQL::Abstract;
use XML::Atom::Client;
use XML::Atom::Entry;
use LWP;
#use XML::RSS;
use Net::OAI::Harvester;
use Apache::DBI();
use GD;
use Image::GD::Thumbnail;
use strict;
#require "edurss/harvester.pl";
#
# page.cgi
#
# Copyright 2005 Stephen Downes, National Research Council Canada
# Prerelease - please do not distribute - I'll GPL it when it's ready
#
# Draws on content in simple_RSS database and builds content pages
#
# Initialize Session ---------------------------------------------------------------------------------------
my $query = new CGI; my $vars = $query->Vars; # Get Request variables and cookies
unless ($vars->{db}) { if ($vars->{table}) { $vars->{db} = $vars->{table}; }} # makes old form requests compatible
while (my ($vx,$vy) = each %$vars) { $vy =~ s/'/&apos/g; } # apostraphe-proofs system
if ($ARGV[0]==1) { $vars->{action} = "harvest"; } # Allow cron to trigger harvest
my $options = {}; bless $options;
our $cache = {}; bless $cache; # cache frequently loaded content
our $Site = {}; bless $Site; &get_site($Site,"/home/downess/opt-httpd-prefork-htdocs/cgi-bin/data/midm.conf"); # Get Site Information
if ($ENV) { $Site->{script} = "http://" . $ENV{'SERVER_NAME'} . $ENV{'SCRIPT_NAME'}; } else { my $ENV = (); }
$Site->{pubstatus} = $vars->{action};
#print "Content-type: text/html\n\n";while (my ($x,$y) = each %$Site) { print "$x = $y
"; } exit;
&old_get_site($vars);
if ($ENV) { $vars->{script} = "http://" . $ENV{'SERVER_NAME'} . $ENV{'SCRIPT_NAME'}; }
# Open Database
my $dbh = &db_open("DBI:mysql:$vars->{db_name}:$vars->{db_host}",$vars->{db_user},$vars->{db_pass}) or print "Database connect error: $!";
# Get User Information and verify Login
my $person = &get_person($dbh,$query);
# Initialize Page
our $page = new Page;
# Custom Pages
if ($vars->{userpage} || $vars->{userrss}) { &custom_page($dbh,$vars); }
# Actions ---------------------------------------------------------------------------------------
$vars->{action} ||= "";
for ($vars->{action}) {
/admin_main/ && do { &admin_main($dbh,$vars,$options,$person); last; };
/browse_menu/ && do { &browse_menu($dbh,$vars,$options,$person); last; };
/send_newsletter/ && do { &send_newsletter($dbh,$vars,$options,$person); last; };
/publish/ && do { &publish($dbh,$vars,$options,$person,$vars->{page}); last; };
/Delete/ && do { &delete_record($dbh,$vars,$options,$person); last; };
/Spam/ && do { &delete_record($dbh,$vars,$options,$person); last; };
/Preview/ && do { &submit_record($dbh,$vars,$options,$person,"Preview",$query); last; };
/Submit/ && do { &submit_record($dbh,$vars,$options,$person,"Submit",$query); last; };
/Update/ && do { &submit_record($dbh,$vars,$options,$person,"Update",$query); last; };
/create/ && do { &create_record($dbh,$vars); last; };
/build_gallery/ && do { require "edurss/gallery.pl";
&build_gallery($dbh,$vars->{gallery},$vars->{template}); last; };
/harvest/ && do { require "/home/downess/opt-httpd-prefork-htdocs/cgi-bin/edurss/harvester.pl"; &harvest($dbh,$vars,$options,$person); last; };
/harvall/ && do { require "/home/downess/opt-httpd-prefork-htdocs/cgi-bin/edurss/harvester.pl"; &harvest_all($dbh,$vars,$options,$person); last; };
/newqueue/ && do { require "edurss/harvester.pl"; &create_queue($dbh,$options,$vars,$person); last; };
/approve/ && do { require "edurss/harvester.pl"; &approve_feed($dbh,$options,$vars,$person); last; };
/retire/ && do { require "edurss/harvester.pl"; &retire_feed($dbh,$options,$vars,$person); last; };
/opmlopts/ && do { require "edurss/harvester.pl"; &opml_options($dbh,$options,$vars,$person); last; };
/harvopml/ && do { require "edurss/harvester.pl"; &upload_opml($dbh,$options,$vars,$person,$query); last; };
/mostcited/ && do { &most_cited($dbh,$options,$vars,$person); last; };
/conversation/ && do { &conversation($dbh,$vars,$options,$person); last; }
}
my $x = $vars->{format};
# Search ---------------------------------------------------------------------------------------
if ($vars->{query}) { &search($dbh,$vars,$options,$person); }
# Records ---------------------------------------------------------------------------------------
$vars->{db} ||= "";
my $db = $vars->{db} || "";
my $id = $vars->{id} || "";
my $format = $vars->{format} || "";
my $number = $vars->{number} || 10;
my $start = $vars->{start} || 0;
my $default_sort;
if ($vars->{db} =~ /view|journal|topic/) { $default_sort = "title"; }
elsif ($vars->{db} =~ /person|author/) { $default_sort = "name"; }
else { $default_sort = "crdate DESC"; }
my $sort = $vars->{sort} || $default_sort;
my $expires = $vars->{expires} || "";
my $heading = $vars->{heading} || "";
my $where = $vars->{where}||$vars->{q} || "";
my $all; if (defined($vars->{all})) { $all="all"; } else { $all=""; }
for my $db_type ("page","author","journal","view","post","link","photo","person","event","feed","topic") {
if ($vars->{$db_type}) { $db = $db_type; $id = $vars->{$db_type}; $number=1; } }
if ($db eq "view") { $format ||= "edit"; } # Default Formats
elsif ($db eq "post") { $format ||= "full"; }
else { $format ||= "html"; }
$page->{content} .= qq||;
# Print Page -------------------------------------------------------------------------------------
&format_content($dbh,$vars,$options,$person,$page);
print &header($dbh,$vars,$options,$person);
print $page->{content};
print &footer($dbh,$vars,$options,$person);
exit;
#-------------------------------------------------------------------------------
#
# Data Input Functions
#
#---------------------------------------------------------------------------------
sub create_record {
my ($dbh,$vars) = @_;
print &header($dbh,$vars,$options,$person);
my $view_title = $vars->{db}."_create";
my $view_text = &get_view($dbh,$view_title);
my $vidf = '[*'.$vars->{db}.'_id*]'; $view_text =~ s/\Q$vidf\E/new/g; $view_text =~ s/\[\*(.*?)\*\]//g; # Not strictly necessary
&pickup_vars($vars,\$view_text);
&ldropdown(\$view_text);
&dbdropdown($dbh,\$view_text);
print $view_text;
print &footer($dbh,$vars,$options,$person);
exit;
}
sub validate_input {
my ($vars) = @_;
# Validate Content
if ($vars->{db} eq "post") {
$vars->{post_type} ||= "comment";
if ($vars->{post_type} eq "comment") {
&error($dbh,$vars,$person,"Cannot submit a blank comment.") unless ($vars->{post_description});
&in_anti_spam($dbh,$vars);
}
if ($vars->{post_type} eq "link") { &error($dbh,$vars,$person,"Link must contain 'http'.") unless ($vars->{post_link} =~ /http/i); }
}
my $ipfield = $vars->{db}."_crip"; $vars->{$ipfield} = $ENV{'REMOTE_ADDR'};
}
sub check_permissions {
my ($dbh,$vars,$person,$idfield) = @_;
unless ($vars->{$idfield} eq "new") {
my $creator = &db_record_creator($dbh,$vars->{db},$vars->{$idfield});
unless (($person->{person_status} eq "admin") ||
($person->{person_id} eq $creator))
{ &error($dbh,$vars,$person,"You (do not have permission to edit this record."); exit; }
}
}
sub submit_record {
my ($dbh,$vars,$options,$person,$cmd,$query) = @_;
# Create and Update New Content ----------------------------------------------------------------------------
if ($vars->{action} eq "Preview") { $vars->{next} = "preview"; } else { $vars->{next} = "view"; }
my $idfield = $vars->{db}."_id";
&validate_input($vars);
&check_permissions($dbh,$vars,$person,$idfield);
# Manage Date-Time Stuff
if ($vars->{$vars->{db}."_start"}) {
if ($vars->{$vars->{db}."_start_time"}) {
$vars->{$vars->{db}."_start"} .= " ".$vars->{$vars->{db}."_start_time"}.":00";
} else {
$vars->{$vars->{db}."_start"} .= " 00:00:00";
}
}
if ($vars->{$vars->{db}."_end"}) {
if ($vars->{$vars->{db}."_end_time"}) {
$vars->{$vars->{db}."_end"} .= " ".$vars->{$vars->{db}."_end_time"}.":00";
} else {
$vars->{$vars->{db}."_end"} .= " 23:59:59";
}
}
# File Upload
my $ufilename = $query->upload('file'); # Detect uploaded file
#my $ufilename = $query->param('file');
if ($ufilename) {
if ($ufilename=~m/^.*(\\|\/)(.*)/) { $vars->{file_file} = $2; } else { $vars->{file_file} = $ufilename; } # Determine filename
$vars->{file_title} = $vars->{file_file};
$vars->{file_type} = $query->uploadInfo($ufilename)->{'Content-Type'}; # Determine file type
my $store_dir = &file_subdirectory($vars->{file_type},$vars->{file_gallery}); # Determine local directory
unless (-d $Site->{st_base}.$store_dir) {
mkdir($Site->{st_base}.$store_dir) || die "Cannot mkdir $Site->{st_base}.$store_dir: $!";
chmod 0766,$Site->{st_base}.$store_dir;
} # make directory if necessary
my $store_filename = $store_dir."/".$vars->{file_file}; $store_filename =~ s|//|/|g; # Build full store filename
my $save_in = $Site->{st_base}.$store_filename; # Build absolute filename
if(-e $save_in) {
if ($vars->{force_replace}) {
my ($new_store_file,$new_store_title) = &make_new_filename($vars->{file_file},$store_dir);
my $stmt = qq|SELECT file_id FROM file WHERE file_file = ?|;
my $sth = $dbh->prepare($stmt);
$sth->execute($store_filename);
my $ref = $sth -> fetchrow_hashref();
&db_update($dbh,"file",{file_file=>$new_store_file,file_title=>$new_store_title}, $ref->{file_id});
rename($save_in,$new_store_file) || &error($dbh,$vars,$person,"Cannot rename existing file. Oy Vey! $!");
} else {
&error($dbh,$vars,$person,"File $save_in already exists; choose a new file name. " .
"To replace this file, create ".
"a new file and select the 'replace existing file' option.");
}
}
&error($dbh,$vars,$person,"Base directory not defined in midm.conf") unless ($Site->{st_base});
open UPLOADFILE, ">$save_in" || die "Can't open: $!";
binmode UPLOADFILE;
while ( <$ufilename> ) { print UPLOADFILE $_; }
close UPLOADFILE;
if (($vars->{$idfield} eq "") || ($vars->{$idfield} eq "new")) {
$vars->{file_size} = -s $save_in;
$vars->{file_file} = $store_filename;
unless($vars->{file_size}) { &error($dbh,$vars,$person,"File upload to $store_filename failed; no record saved. $!"); }
}
}
if ($vars->{post_type} eq "gallery") {
$vars->{post_dir} = $vars->{post_title};
$vars->{post_dir} =~ s/[^a-zA-Z0-9 ]//g;
$vars->{post_dir} =~ s/ /_/g;
}
$vars->{$idfield} = &in_save_record($dbh,$person,$options,$vars->{db},$vars); # Save Input Record
# Create References (I wish I knew a shorter way of doing this)
if ($vars->{db} eq "file") {
if ($vars->{$idfield} && $vars->{file_post}) {
&db_delete($dbh,"post_file","post_file_file",$vars->{$idfield});
my $post_file = {
post_file_file => $vars->{$idfield},
post_file_post => $vars->{file_post}
};
&in_save_record($dbh,$person,$options,"post_file",$post_file);
}
}
if ($vars->{db} eq "post") {
if ($vars->{post_event}) {
&in_save_record($dbh,$person,$options,"event_post",{event_post_post => $vars->{$idfield}, event_post_event => $vars->{post_event}});
}
if ($vars->{post_type} eq "link") { # Post - Link
# Find Journal ID
my $jid; if ($vars->{post_journal}) { # Journal?
$jid = &in_find_by_val($dbh,$person,$options,"journal","title",$vars->{post_journal}); # by title...
} elsif ($vars->{post_link}) {
($jid) = &in_find_by_sub_match($dbh,$person,$options,"journal","journal_link",$vars->{post_link}) # by link...
}
# Find Author ID
my $aid; if ($vars->{post_author}) { # Author?
$aid = &in_find_by_val($dbh,$person,$options,"author","name",$vars->{post_author}); # by name...
}
# Find Link ID
my $lid; if ($vars->{post_link}) { # Author?
$lid = &in_find_by_val($dbh,$person,$options,"link","link",$vars->{post_link}); # by link...
}
# Put Author and Journal ID in Post for lookups
&in_save_record($dbh,$person,$options,"post",{post_id => $vars->{$idfield}, post_author_id => $aid, post_journal_id => $jid});
if ($lid) {
# Save Link data
my $linkdata = {
link_id => $lid,
link_title => $vars->{post_title},
link_link => $vars->{post_link},
link_crdate => time,
link_creator => $person->{person_id},
link_journal => $jid
};
&in_save_record($dbh,$person,$options,"link",$linkdata);
# Save Reference
&db_delete($dbh,"reference","reference_post",$vars->{$idfield});
my $reference = {
reference_link => $lid,
reference_post => $vars->{$idfield},
reference_type => "primary"
};
&in_save_record($dbh,$person,$options,"reference",$reference);
if ($aid) {
# Save Link_Author
&db_delete($dbh,"link_author","link_author_link",$lid);
my $link_author = {
link_author_link => $lid,
link_author_author => $aid
};
&in_save_record($dbh,$person,$options,"link_author",$link_author);
}
}
}
# Find Topics
&db_delete($dbh,"post_topic","post_topic_post",$vars->{$idfield});
my $topic_match = $vars->{post_title} . " " . $vars->{post_description};
my @topics = &in_find_by_sub_match($dbh,$person,$options,"topic","topic_where",$topic_match); # by link...
while (<@topics>) {
my $idf = $vars->{$idfield};
my $post_topic = {
post_topic_post => $idf,
post_topic_topic => $_
};
&in_save_record($dbh,$person,$options,"post_topic",$post_topic);
}
}
# If Topic, Reindex Topic
if ($vars->{db} eq "topic") {
&index_category($dbh,$options,"post",$vars->{$idfield});
}
if (($vars->{db} eq "post") && ($vars->{post_type} eq "comment")) {
if ($vars->{next} eq "preview") {
$vars->{q} = "id=".$vars->{$idfield};
$vars->{format} = "preview";
} else {
if ($vars->{post_thread}) {
$dbh->do(qq{UPDATE post SET post_replies = post_replies + 1 WHERE post_id = $vars->{post_thread}}); # Increment replies ctr
}
$vars->{q} = "id=".$vars->{post_thread};
$vars->{format} = "full";
}
} elsif ($vars->{db} eq "view") {
$vars->{q} = "id=".$vars->{$idfield};
$vars->{format} = "edit";
} else {
$vars->{q} = "id=".$vars->{$idfield};
$vars->{format} = "edit";
}
if ($cmd eq "Update") { $vars->{format} = "edit"; }
}
sub make_new_filename {
my ($file,$directory) = @_;
my $ctr = 1; my $new_file = $file;
while ($ctr < 1000) {
if ($new_file =~ /\./) { $new_file =~ s/\./-$ctr\./; } else { $new_file .= "-$ctr"; }
my $new_filename = $Site->{st_base}.$directory."/".$new_file; $new_filename =~ s|//|/|g;
unless (-e $new_filename) { return ($new_filename,$new_file); }
$ctr++;
}
&error($dbh,$vars,$person,"According to the system, you have a thousand versions of this file. Isn't that enough?");
exit;
}
sub file_subdirectory {
my ($filetype,$subsub) = @_;
if ($subsub) { $subsub .= "/"; }
&error($dbh,$vars,$person,"File upload directory not defined in midm.conf") unless ($Site->{st_file});
if ($filetype =~ /text|msword|pdf/) { return $Site->{st_file}."/".$subsub."docs"; }
elsif ($filetype =~ /audio|mpeg/) { return $Site->{st_file}."/".$subsub."audio"; }
elsif ($filetype =~ /powerpoint/i) { return $Site->{st_file}."/".$subsub."slides"; }
elsif ($filetype =~ /video/i) { return $Site->{st_file}."/".$subsub."video"; }
elsif ($filetype =~ /image/i) { &error($dbh,$vars,$person,"Photo upload directory not defined in midm.conf") unless ($Site->{st_photo});return $Site->{st_photo}."/".$subsub; }
&error($dbh,$vars,$person,"This type of file - $filetype - is not permitted for upload."); exit;
}
sub delete_record {
my ($dbh,$vars,$options,$person) = @_;
&error($dbh,$vars,$person,"Action not permitted") unless ($person->{person_mode} eq "admin");
if ($vars->{action} eq "Spam") {
my $bs=(); $bs->{banned_sites_ip} = &db_record_crip($dbh,$vars->{db},$vars->{id});
&in_save_record($dbh,$person,$options,"banned_sites",$bs); # Save Input Record
}
&db_delete($dbh,$vars->{db},$vars->{db}."_id",$vars->{id});
$page->{format} = "text/html";
$page->{content} = "Record Deleted";
$page->print();
exit;
}
sub in_save_record {
my $dbh = shift || die "Database not initialized in save record";
my $person = shift || die "Person not specified in save record";
my $options = shift || die "Options not specified in save record";
my $table = shift || die "Table not specified in save record";
my $vars = shift || die "No input content in save record";
die "Unsupported data type specified to save record" unless (ref $vars eq 'HASH' || ref $vars eq 'Link' || ref $vars eq 'Feed');
# ID Field
my $idfield = $table."_id";
$vars->{$idfield} = $vars->{$idfield} || $vars->{id} || "new";
# Save Input Record
if ($vars->{$idfield} eq "new") { # Create
$vars->{$vars->{db}."_crdate"} = time;
$vars->{$vars->{db}."_creator"} = $person->{person_id};
$vars->{$idfield} = &db_insert($dbh,$table,$vars);
$options->{msg} .= "Created new $table ($vars->{$idfield})
";
} else { # Update
my $where = { $idfield => $vars->{$idfield}};
$vars->{$idfield} = &db_update($dbh,$table, $vars, $vars->{$idfield});
$options->{msg} .= ucfirst($table)." $vars->{$idfield} successfully updated
";
}
return $vars->{$idfield};
}
sub in_find_by_val {
my ($dbh,$person,$options,$table,$field,$val) = @_;
die "Need to specify table, field and value in find by..." unless (($table) && ($field) && ($val));
my $id = db_record_list($dbh,$table,$table."_".$field,"=",$val,"1");
unless ($id) {
$id = &in_save_record($dbh,$person,$options,$table,{$table."_".$field => $val}); # Create
}
return $id;
}
sub in_find_by_sub_match {
# Given data, tries to find the field value via regex in Table
# Pretty ugly, don't use unless needed
my ($dbh,$person,$options,$table,$field,$match) = @_;
return unless (($field) && ($match));
my $tid = $table."_id"; my @return;
my $sth = $dbh->prepare("SELECT $tid,$field FROM $table");
$sth->execute();
while (my $ref = $sth -> fetchrow_hashref()) {
next unless ($ref->{$field});
if ($match =~ $ref->{$field}) { push @return,$ref->{$tid}; }
}
return @return;
}
sub in_anti_spam { # Checks input for spam content and kills on contact
my $dbh = shift;
my $vars = shift;
# No Links
if ($vars->{post_description} =~ //i) {
die "Comment rejected.";
}
# Filter by Content
if (($vars->{post_description} =~ /(You are invited|pharmacy|poker|holdem|casino|roulette|phentermine|diet| pills| loans)/i) ||
($vars->{post_title} =~ /(You are invited|pharmacy|poker|holdem|casino|roulette|phentermine|diet| pills| loans)/i)) {
open SPAMLOG,">>spamlog.txt";
print SPAMLOG "Spamming attempt: $vars->{post_description}\n";
close SPAMLOG;
&error($dbh,$vars,$person,"Your submission has been classified as spam and has been rejected.");
}
# Filter by IP
my @ipl = split /\./,$ENV{'REMOTE_ADDR'}; pop @ipl; my $ip = join '.',@ipl;
my $sth = $dbh->prepare("SELECT * FROM banned_sites WHERE banned_sites_ip REGEXP ?");
$sth->execute($ip); my $ref = $sth -> fetchrow_hashref();
&error($dbh,$vars,$person,"Your submission has been classified as spam and has been rejected.") if ($ref);
}
#-------------------------------------------------------------------------------
#
# Page Printing Functions
#
#---------------------------------------------------------------------------------
sub template {
# Get Template
my ($dbh,$title,$options,$person,$pagetitle) = @_;
my $stmt = qq|SELECT * FROM template WHERE template_title = ?|;
my $sth = $dbh -> prepare($stmt);
$sth -> execute($title); $options->{execute}++;
my $ref = $sth -> fetchrow_hashref();
my $templ = $ref->{template_description};
# Format Template
$page->{title} ||= ""; $pagetitle ||= $page->{title};
$templ =~ s//$pagetitle/g;
&autodates(\$templ);
&login_options(\$templ);
&clean_up(\$templ);
return $templ;
}
sub header {
my ($dbh,$vars,$options,$person,$format,$title) = @_;
$vars->{format} ||= ""; $format ||= $vars->{format};
if ($format eq "rss") { return "Content-type:text/xml\n\n".&template($dbh,"RSS02_top",$options,$person,$title); } # Rss / XML
elsif ($format eq "js") { return "Content-type:text/javascript\n\nfunction jview() {\n"; } # Javascript
else { return "Content-type:text/html\n\n".&template($dbh,"top",$options,$person,$title); } # HTML
}
sub footer {
my ($dbh,$vars,$options,$person,$format) = @_;
$format ||= $vars->{format};
if ($format eq "rss") { return &template($dbh,"RSS02_bottom");; } # RSS
elsif ($format eq "js") { return qq|\n}\njview();\n|; } # Javascript
elsif ($format eq "htmprint") {
return qq||.&template($dbh,"left",$options,$person).qq|