#!/usr/bin/perl -w
# This code is a part of Slash, and is released under the GPL.
# Copyright 1997-2001 by Open Source Development Network. See README
# and COPYING for more information, or see http://slashcode.com/.
# $Id: journal.pl,v 1.2.2.60 2001/10/29 17:19:21 pudge Exp $

use strict;
use Slash 2.001;	# require Slash 2.1
use Slash::Display;
use Slash::Utility;
use Slash::XML;
use vars qw($VERSION);

($VERSION) = ' $Revision: 1.2.2.60 $ ' =~ /\$Revision:\s+([^\s]+)/;

use constant ALLOWED	=> 0;
use constant FUNCTION	=> 1;
use constant MSG_CODE_JOURNAL_FRIEND => 5;

sub main {
	my $journal   = getObject('Slash::Journal');
	my $constants = getCurrentStatic();
	my $slashdb   = getCurrentDB();
	my $user      = getCurrentUser();
	my $form      = getCurrentForm();

	# require POST and logged-in user for these ops
	my $user_ok   = $user->{state}{post} && !$user->{is_anon};

	# if top 10 are allowed
	# My feeling is that an admin should be able to use the
	# feature even if the site does not use it. -Brian
	my $top_ok    = ($constants->{journal_top} && (
		$constants->{journal_top_posters} ||
		$constants->{journal_top_friend}  ||
		$constants->{journal_top_recent}
	)) || $user->{is_admin};

	# possible value of "op" parameter in form
	my %ops = (
		edit		=> [ !$user->{is_anon},	\&editArticle		],
		removemeta	=> [ !$user->{is_anon},	\&articleMeta		],
		addmeta		=> [ !$user->{is_anon},	\&friendMeta		],
		deletemeta	=> [ !$user->{is_anon},	\&friendMeta		],

		preview		=> [ $user_ok,		\&editArticle		],
		save		=> [ $user_ok,		\&saveArticle		], # formkey
		remove		=> [ $user_ok,		\&removeArticle		],
		setprefs	=> [ $user_ok,		\&setPrefs		],
		add		=> [ $user_ok,		\&addFriend		], # formkey?
		'delete'	=> [ $user_ok,		\&deleteFriend		],

		list		=> [ 1,			\&listArticle		],
		display		=> [ 1,			\&displayArticle	],
		top		=> [ $top_ok,		\&displayTop		],
		searchusers	=> [ 1,			\&searchUsers		],
		friends		=> [ 1,			\&displayFriends	],

		default		=> [ 1,			\&displayFriends	],
	);

	my $op = $form->{'op'};
	if (!$op || !exists $ops{$op} || !$ops{$op}[ALLOWED]) {
		$op = 'default';
	}

	# hijack RSS feeds
	if ($form->{content_type} eq 'rss') {
		if ($op eq 'top' && $top_ok) {
			displayTopRSS($journal, $constants, $user, $form, $slashdb);
		} else {
			displayRSS($journal, $constants, $user, $form, $slashdb);
		}
	} else {
		$ops{$op}[FUNCTION]->($journal, $constants, $user, $form, $slashdb);
		footer();
	}
}

sub displayTop {
	my($journal, $constants, $user, $form, $slashdb) = @_;
	my $journals;

	_printHead("mainhead");

	if ($constants->{journal_top_posters}) {
		$journals = $journal->top();
		slashDisplay('journaltop', { journals => $journals, type => 'top' });
	}

	if ($constants->{journal_top_friend}) {
		$journals = $journal->topFriends();
		slashDisplay('journaltop', { journals => $journals, type => 'friend' });
	}

	if ($constants->{journal_top_recent}) {
		$journals = $journal->topRecent();
		slashDisplay('journaltop', { journals => $journals, type => 'recent' });
	}
}

sub displayFriends {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	_printHead("mainhead");

	my $friends = $journal->friends();
	if (@$friends) {
		slashDisplay('journalfriends', { friends => $friends });
	} else {
		print getData('nofriends');
		slashDisplay('searchusers');
	}

}

sub searchUsers {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	if (!$form->{nickname}) {
		_printHead("mainhead");
		slashDisplay('searchusers');
		return;
	}

	my $results = $journal->searchUsers($form->{nickname});

	# if nonref and true, then display user journal
	if ($results && !ref($results)) {
		# clean up a bit, just in case
		for (keys %$form) {
			delete $form->{$_} unless $_ eq 'op';
		}
		$form->{uid} = $results;
		displayArticle(@_);
		return;
	}

	# print the lovely headers
	_printHead("mainhead");

	# if false or empty ref, no users
	if (!$results || (ref($results) eq 'ARRAY' && @$results < 1)) {
		print getData('nousers');
		slashDisplay('searchusers');

	# a hashref, that is exact user with no journal
	} elsif (ref($results) eq 'HASH') {
		print getData('nojournal', { nouser => $results });
		slashDisplay('searchusers');

	# an arrayref, we gots to display a list
	} else {
		slashDisplay('journalfriends', {
			friends => $results,
			search	=> 1,
		});
	}
}

sub displayRSS {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	$user		= $slashdb->getUser($form->{uid}, ['nickname', 'fakeemail']) if $form->{uid};
	my $uid		= $form->{uid} || $user->{uid};
	my $nickname	= $user->{nickname};

	my $articles = $journal->getsByUid($uid, 0, 15);
	my @items;
	for my $article (@$articles) {
		push @items, {
			title		=> $article->[2],
# needs a var controlling this ... what to use as desc?
#			description	=> timeCalc($article->[0]),
#			description	=> "$nickname wrote: " . strip_mode($article->[1], $article->[4]),
			'link'		=> "$constants->{absolutedir}/journal.pl?op=display&uid=$uid&id=$article->[3]"
		};
	}

	my $usertext = $nickname;
	$usertext .= " <$user->{fakeemail}>" if $user->{fakeemail};
	xmlDisplay(rss => {
		channel => {
			title		=> "$constants->{sitename} Journals",
			description	=> "${nickname}'s Journal",
			'link'		=> "$constants->{absolutedir}/journal.pl?op=display&uid=$uid",
			creator		=> $usertext,
		},
		image	=> 1,
		items	=> \@items
	});
}

sub displayTopRSS {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	my $journals;
	if ($form->{type} eq 'count' && $constants->{journal_top_posters}) {
		$journals = $journal->top();
	} elsif ($form->{type} eq 'friends' && $constants->{journal_top_friend}) {
		$journals = $journal->topFriends();
	} elsif ($constants->{journal_top_recent}) {
		$journals = $journal->topRecent();
	}

	my @items;
	for my $entry (@$journals) {
		my $time = timeCalc($entry->[3]);
		push @items, {
			title	=> "$entry->[1] ($time)",
			'link'	=> "$constants->{absolutedir}/journal.pl?op=display&uid=$entry->[2]"
		};
	}

	xmlDisplay(rss => {
		channel => {
			title		=> "$constants->{sitename} Journals",
			description	=> "Top $constants->{journal_top} Journals",
			'link'		=> "$constants->{absolutedir}/journal.pl?op=top",
		},
		image	=> 1,
		items	=> \@items
	});
}

sub displayArticle {
	my($journal, $constants, $user, $form, $slashdb) = @_;
	my($date, $forward, $back, @sorted_articles, $nickname, $uid);
	my $collection = {};

	if ($form->{uid} or $form->{nick}) {
		$uid		= $form->{uid} ? $form->{uid} : $slashdb->getUserUID($form->{nick});
		$nickname	= $slashdb->getUser($uid, 'nickname');
	} else {
		$nickname	= $user->{nickname};
		$uid		= $user->{uid};
	}

	if (isAnon($uid)) {
		return displayFriends(@_);
	}

	_printHead("userhead", { nickname => $nickname, uid => $uid });

	# clean it up
	my $start = fixint($form->{start}) || 0;
	my $articles = $journal->getsByUid($uid, $start,
		$constants->{journal_default_display} + 1, $form->{id}
	);

	unless ($articles && @$articles) {
		print getData('noentries_found');
		return;
	}

	# check for extra articles ... we request one more than we need
	# and if we get the extra one, we know we have extra ones, and
	# we pop it off
	if (@$articles == $constants->{journal_default_display} + 1) {
		pop @$articles;
		$forward = $start + $constants->{journal_default_display};
	} else {
		$forward = 0;
	}

	# if there are less than journal_default_display remaning,
	# just set it to 0
	if ($start > 0) {
		$back = $start - $constants->{journal_default_display};
		$back = $back > 0 ? $back : 0;
	} else {
		$back = -1;
	}

	my $topics = $slashdb->getTopics();
	for my $article (@$articles) {
		my($date_current) = timeCalc($article->[0], "%A %B %d, %Y");
		if ($date ne $date_current) {
			push @sorted_articles, $collection if ($date and (keys %$collection));
			$collection = {};
			$date = $date_current;
			$collection->{day} = $article->[0];
		}

		my $commentcount = $article->[6]
			? $slashdb->getDiscussion($article->[6], 'commentcount')
			: 0;

		# should get comment count, too -- pudge
		push @{$collection->{article}}, {
			article		=> strip_mode($article->[1], $article->[4]),
			date		=> $article->[0],
			description	=> strip_notags($article->[2]),
			topic		=> $topics->{$article->[5]},
			discussion	=> $article->[6],
			id		=> $article->[3],
			commentcount	=> $commentcount,
		};
	}

	push @sorted_articles, $collection;
	my $theme = $slashdb->getUser($uid, 'journal_theme');
	$theme ||= $constants->{journal_default_theme};

	slashDisplay($theme, {
		articles	=> \@sorted_articles,
		uid		=> $uid,
		nickname	=> $nickname,
		is_friend	=> $journal->is_friend($uid),
		back		=> $back,
		forward		=> $forward,
	});
}

sub setPrefs {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	my %prefs;
	for my $name (qw(journal_discuss journal_theme)) {
		$prefs{$name} = $user->{$name} = $form->{$name}
			if defined $form->{$name};
	}

	$slashdb->setUser($user->{uid}, \%prefs);
	
	listArticle(@_);
}

sub listArticle {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	my $uid = $form->{uid} || $ENV{SLASH_USER};
	if (isAnon($uid)) {
		return displayFriends(@_);
	}

	my $list 	= $journal->list($uid);
	my $themes	= $journal->themes;
	my $theme	= $user->{'journal_theme'} || $constants->{journal_default_theme};
	my $nickname	= $form->{uid}
		? $slashdb->getUser($form->{uid}, 'nickname')
		: $user->{nickname};

	_printHead("userhead", { nickname => $nickname, uid => $form->{uid} || $user->{uid} });

	if (@$list) {
		slashDisplay('journallist', {
			default		=> $theme,
			themes		=> $themes,
			articles	=> $list,
			uid		=> $form->{uid},
			nickname	=> $nickname,
		});
	} elsif (!$user->{is_anon} && (!$form->{uid} || $form->{uid} == $user->{uid})) {
		slashDisplay('journaloptions', {
			default		=> $theme,
			themes		=> $themes,
		});
		print getData('noentries');
	} else {
		print getData('noentries', { nickname => $nickname });
	}
}

sub saveArticle {
	my($journal, $constants, $user, $form, $slashdb) = @_;
	my $description = strip_notags($form->{description});

	unless ($description ne "" && $form->{article} ne "") {
		_printHead("mainhead");
		print getData('no_desc_or_article');
		return 0;
	}

	return unless _validFormkey();

	if ($form->{id}) {
		my %update;
		my $article = $journal->get($form->{id});

		# note: comments_on is a special case where we are
		# only turning on comments, not saving anything else
		if ($constants->{journal_comments} && $form->{journal_discuss} && !$article->{discussion}) {
			my $rootdir = $constants->{'rootdir'};
			if ($form->{comments_on}) {
				$description = $article->{description};
				$form->{tid} = $article->{tid};
			}
			my $did = $slashdb->createDiscussion({
				title	=> $description,
				topic	=> $form->{tid},
				url	=> "$rootdir/journal.pl?op=display&id=$form->{id}&uid=$user->{uid}",
			});
			$update{discussion}  = $did;

		# update description if changed
		} elsif (!$form->{comments_on} && $article->{discussion} && $article->{description} ne $description) {
			$slashdb->setDiscussion($article->{discussion}, { description => $description });
		}

		unless ($form->{comments_on}) {
			for (qw(article tid posttype)) {
				$update{$_} = $form->{$_} if defined $form->{$_};
			}
			$update{description} = $description;
		}

		$journal->set($form->{id}, \%update);

	} else {
		my $id = $journal->create($description,
			$form->{article}, $form->{posttype}, $form->{tid});

		unless ($id) {
			_printHead("mainhead");
			print getData('create_failed');
			return 0;
		}

		if ($constants->{journal_comments} && $form->{journal_discuss}) {
			my $rootdir = $constants->{'rootdir'};
			my $did = $slashdb->createDiscussion({
				title	=> $description,
				topic	=> $form->{tid},
				url	=> "$rootdir/journal.pl?op=display&id=$id&uid=$user->{uid}"
			});
			$journal->set($id, { discussion => $did });
		}

		# create messages
		my $messages = getObject('Slash::Messages');
		if ($messages) {
			my $friends   = $journal->message_friends;

			for (@$friends) {
				my $data = {
					template_name	=> 'messagenew',
					subject		=> { template_name => 'messagenew_subj' },
					journal		=> {
						description	=> $description,
						article		=> $form->{article},
						posttype	=> $form->{posttype},
						id		=> $id,
						uid		=> $user->{uid},
						nickname	=> $user->{nickname},
					}
				};
				$messages->create($_, MSG_CODE_JOURNAL_FRIEND, $data);
			}
		}
	}

	listArticle(@_);
}

sub articleMeta {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	if ($form->{id}) {
		my $article = $journal->get($form->{id});
		_printHead("mainhead");
		slashDisplay('meta', { article => $article });
	} else {
		listArticle(@_);
	}
}

sub removeArticle {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	for my $id (grep { $_ = /^del_(\d+)$/ ? $1 : 0 } keys %$form) {
		$journal->remove($id);
	}

	listArticle(@_);
}

sub friendMeta {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	if ($form->{uid}) {
		my $friend = $slashdb->getUser($form->{uid});
		_printHead("mainhead");
		slashDisplay('meta', { friend => $friend });
	} else {
		displayFriends(@_);
	}
}

sub addFriend {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	$journal->add($form->{uid}) if $form->{uid};
	displayFriends(@_);
}

sub deleteFriend {
	my($journal, $constants, $user, $form, $slashdb) = @_;

	for my $uid (grep { $_ = /^del_(\d+)$/ ? $1 : 0 } keys %$form) {
		$journal->delete($uid);
	}

	displayFriends(@_);
}

sub editArticle {
	my($journal, $constants, $user, $form, $slashdb) = @_;
	# This is where we figure out what is happening
	my $article = {};
	my $posttype;

	_printHead("mainhead");

	if ($form->{state}) {
		$article->{date}	= scalar(localtime(time()));
		$article->{article}	= $form->{article};
		$article->{description}	= $form->{description};
		$article->{id}		= $form->{id};
		$article->{tid}		= $form->{tid};
		$posttype		= $form->{posttype};
	} else {
		$article  = $journal->get($form->{id}) if $form->{id};
		$posttype = $article->{posttype};
		$slashdb->createFormkey('journal');
	}

	$posttype ||= $user->{'posttype'};

	if ($article->{article}) {
		my $strip_art = strip_mode($article->{article}, $posttype);
		my $strip_desc = strip_notags($article->{description});

		my $commentcount = $article->{discussion}
			? $slashdb->getDiscussion($article->{discussion}, 'commentcount')
			: 0;

		my $disp_article = {
			article		=> $strip_art,
			date		=> $article->{date},
			description	=> $strip_desc,
			topic		=> $slashdb->getTopic($article->{tid}),
			id		=> $article->{id},
			discussion	=> $article->{discussion},
			commentcount	=> $commentcount,
		};

		my $theme = $user->{'journal_theme'};
		$theme ||= $constants->{journal_default_theme};
		slashDisplay($theme, {
			articles	=> [{ day => $article->{date}, article => [ $disp_article ] }],
			uid		=> $article->{uid} || $user->{uid},
			is_friend	=> $journal->is_friend($article->{uid}),
			back		=> -1,
			forward		=> 0,
			nickname	=> $user->{nickname},
		});
	}

	my $formats = $slashdb->getDescriptions('postmodes');
	my $format_select = createSelect('posttype', $formats, $posttype, 1);

	slashDisplay('journaledit', {
		article		=> $article,
		format_select	=> $format_select,
	});
}

sub _validFormkey {
	my $error;
	# this is a hack, think more on it, OK for now -- pudge
	Slash::Utility::Anchor::getSectionColors();
	for (qw(max_post_check interval_check formkey_check)) {
		last if formkeyHandler($_, 0, 0, \$error);
	}

	if ($error) {
		_printHead("mainhead");
		print $error;
		return 0;
	} else {
		# why does anyone care the length?
		getCurrentDB()->updateFormkey(0, length(getCurrentForm()->{article}));
		return 1;
	}
}

sub _printHead {
	my($head, $data) = @_;
	my $title = getData($head, $data);
	header($title);
	slashDisplay("journalhead", { title => $title });
}

createEnvironment();
main();
1;
