#! CRADDON 1
#! NAME News Categories
#! DESCRIPTION Allows news items to be categorized and allows profiles to include only certain categories of news.
#! VERSION build 10-RC8 (privacy compliant)
#! DOC 0

# This is a semi-addon. Some categories functionality is already built in, such as:
# - Categories support when building news. (Enabled if $EnableCategories is true.)
# - Categories support when reading/writing profiles.
# - The $Category field is standard.
# This could all have been done via an addon, but given how common and central
# categories are, and given the luxury of being able to put this stuff in the core,
# it... wasn't.

# Will compile & work with the following.
#use strict;
#use vars qw(%newscategories %Subs %in %userdata $EnableCategories %CConfig);

$NewsCategory_build = 10;

my $addon = new Addon('News Categories');
$addon->checkBuild(28);
$addon->isPrivacyCompatible if ($crcgiBuild>=32);

# Handle the language support
if($crcgiBuild >= 45) {
	$addon->addLocalization();
} else {
$Addon::Subs{'getMsg'} ||= <<'END_SUB';
sub getMsg {
	my ($self, $str, @variables) = @_;
	$str =~ s/\[_(\d+)\]/$variables[${1}-1]/ge;
	$str =~ s/\[quant,_(\d+),([^\]]+)]/$variables[${1}-1] . ' ' . (split(\/,\/,${2}))[0]/ge;
	$str =~ s/\[bool,_(\d+),([^\]]+)]/(split(\/,\/,${2}))[($variables[${1}-1]?0:1)]/ge;
	return $str;
}
END_SUB
}

$addon->addAdminFunction($addon->getMsg('News Categories'), $addon->getMsg('Create and modify different categories that news items can be placed in.'), 'newscatmain');
$addon->registerAdminFunction('newscatmain', 'NewsCategoriesMain');
$addon->registerAdminFunction('createcat', 'NewsCategories_CreateCat');
$addon->registerAdminFunction('editcat', 'NewsCategories_EditCat');
$addon->registerAdminFunction('editcatsave', 'NewsCategories_EditCatSave');
$addon->registerAdminFunction('delcat', 'NewsCategories_DeleteCat');

my $EditProfDefinition_1 = <<'END_CODE';
	my %selectedcats;
	foreach $i (@{$newsprofiles{$prof}->{'cats'}}) {
		$selectedcats{$i} = 'selected';
	}
	my $catselect = '';
# PARAHEAD: Fixed so the categories are now sorted by full name at the profiles admin page (2005-05-18)
	foreach $i (sort {uc($newscategories{$a}->{'FullName'}) cmp uc($newscategories{$b}->{'FullName'})} keys %newscategories) {
		$catselect .= qq~
		<option value="$i" $selectedcats{$i}>$newscategories{$i}->{'FullName'}</option>~;
	}
	push(@EditProfileSettings,
		['cats', $addon->getMsg('Categories'), $addon->getMsg("Only news from selected categories will be included in this profile. Multiple selections are allowed: to make multiple selections, Windows users hold down CTRL, Mac users hold down Option, most UNIX users hold down CTRL, users of other operating systems see your browser's help."),
		qq~<select name="cats" size="6" multiple>
		<option value="AllCategories" $selectedcats{'AllCategories'}>(~ . $addon->getMsg('All Categories') . qq~)</option>
		$catselect
		</select>~]);
END_CODE

my $DisplaySubForm_TopRow = <<'END_CODE';
	print $addon->fieldsTableRow($addon->getMsg('Category'), NewsCategories_GetCatSelect('Category', '(default)'));
END_CODE

my $SaveNews_1 = <<'END_CODE';
	$Category = $in{'Category'};
	CRcough($addon->getMsg('You must provide a category.')) unless $Category;
	CRcough($addon->getMsg("Category [_1] doesn't exist.", $Category)) unless $newscategories{$Category};
	unless( NewsCategories_CheckPermission() ) {
		CRcough($addon->getMsg("You are not authorized to submit items into this category."));
	}
	$CategoryName = $newscategories{$Category}->{'FullName'};
END_CODE

my $ModifyNews_Permissions = <<'END_CODE';
	unless( NewsCategories_CheckPermission() ) {
		#next NDLOOP;
		$stoppermnow = 1;
	}
END_CODE

my $ModifyNews_Delete_1 = <<'END_CODE';
	unless( NewsCategories_CheckPermission() ) {
		CRcough($addon->getMsg("You are not authorized to delete items from this category."));
	}
END_CODE

my $ModifyNews_Edit_TopRow = <<'END_CODE';
	unless( NewsCategories_CheckPermission() ) {
		CRcough($addon->getMsg("You are not authorized to modify items in this category."));
	}
	if($newscategories{$Category}->{'ModPermission'} || ($up == 3)) {
		print $addon->fieldsTableRow($addon->getMsg('Category'), NewsCategories_GetCatSelect('Category', $Category));
	} else {
		print $addon->fieldsTableRow($addon->getMsg('Category'), $newscategories{$Category}->{'FullName'});
	}
END_CODE

my $ModifyNews_EditSave_Permissions = <<'END_CODE';
	# PARAHEAD: Added support for changing category at the Modify News page (2004-08-27)
	if($Category ne $in{'Category'}) {
		$OldNewsCategory = $Category;
	}
	unless( NewsCategories_CheckPermission() ) {
		CRcough($addon->getMsg("You are not authorized to modify items in this category."));
	}
	if ($in{'Category'} && $newscategories{$in{'Category'}}) {
		$Category = $in{'Category'};
		unless( NewsCategories_CheckPermission() ) {
			CRcough($addon->getMsg("You are not authorized to move items into this category."));
		}
		$CategoryName = $newscategories{$Category}->{'FullName'};
	}
END_CODE

my $ModifyNews_NewColumn_1 = <<'END_CODE';
	if ($ColCount % 2) {
		print q~<td class="yellowbg">~;
	} 
	else {
		print q~<td class="lightgbg">~;
	}
	print '<div align="center"><b>' . $addon->getMsg('Cat.') . '</b></div></td>';
	$ColCount++;
END_CODE

my $ModifyNews_NewColumn_2 = <<'END_CODE';
	if ($ColCount % 2) {
		print q~<td class="navlink"><div align="center">~;
	}
	else {
		print q~<td class="lightgbg"><div align="center" class="footnote">~;
	}
	print $newscategories{$Category}->{'FullName'};
	print '</div></td>';
	$ColCount++;
END_CODE

my $EditProfileSave = <<'END_CODE';
	if ($in{'cats'}) {
		@{$newsprofiles{$prof}->{'cats'}} = split(/\|x\|/, $in{'cats'});
		delete $in{'cats'};
	}
END_CODE

my $ModifyNews_NewSearchField_1 = <<'END_CODE';
	print qq~<option value="Category"~ . ('Category' eq $selectedSearchField ? ' selected>' : '>') . $addon->getMsg('Category') . qq~</option>~;
	print qq~<option value="CategoryName"~ . ('CategoryName' eq $selectedSearchField ? ' selected>' : '>') . $addon->getMsg('Category Name') . qq~</option>~;
END_CODE

my $ModifyNews_NewSearchField_2 = <<'END_CODE';
	if ($in{'searchfield'} eq 'Category') {
		$FilterData{'field'} = 'Category';
	} elsif ($in{'searchfield'} eq 'CategoryName') {
		$FilterData{'field'} = 'CategoryName';
	}
END_CODE

# Since we don´t do the actual filtering here, just initilize a variable, it is OK to use the permission hook
my $ModifyNews_Permissions = <<'END_CODE';
	if ($Filter eq 'search') {
		$CategoryName = $newscategories{$Category}->{'FullName'};
		$completeNDline .= qq~``x$CategoryName~;
	}
END_CODE

# PARAHEAD: Make sure that profiles containing the old category (if changed at Modify News) is rebuilt as well (2004-08-27)
my $BuildNews_Speedup_Code_Delete = <<'END_CODE';
	if($OldNewsCategory && $ProfCats{$i}->{$OldNewsCategory}) {
		last BUILDNEWS_SKIPDELETE;
	}
END_CODE

my $Build_GetData = <<'END_CODE';
	$CategoryName = $newscategories{$Category}->{'FullName'};
END_CODE

# PARAHEAD: Extracted this check to a sub of its own, since it was used on several occasions (2005-05-18)
$Subs{'NewsCategories_CheckPermission'} = <<'END_SUB';
sub NewsCategories_CheckPermission {
	my ($cat) = @_;
	$cat ||= $Category;
	return  ($NC_Permission{$cat}->{'(AllUsers)'} || # The category is open to all
		($NC_Permission{$cat}->{'(High)'} && $up == 2) || # The category is open to High levelers
		$NC_Permission{$cat}->{$CurrentUser} || # This user has been granted permission
		$up == 3);
}
END_SUB



$addon->hook('EditProfDefinition_1', \$EditProfDefinition_1, 5);
$addon->hook('EarlyHook', 'NewsCategories_ReadCategoryInfo', 5);
$addon->hook('DisplaySubForm_TopRow', \$DisplaySubForm_TopRow, 1);
$addon->hook('SaveNews_1', \$SaveNews_1, 6);
$addon->hook('ModifyNews_Permissions', \$ModifyNews_Permissions, 1);
$addon->hook('ModifyNews_Delete_1', \$ModifyNews_Delete_1, 1);
$addon->hook('ModifyNews_Edit_TopRow', \$ModifyNews_Edit_TopRow, 1);
$addon->hook('ModifyNews_EditSave_Permissions', \$ModifyNews_EditSave_Permissions, 1);
$addon->hook('ModifyNews_NewColumn_1', \$ModifyNews_NewColumn_1, -5);
$addon->hook('ModifyNews_NewColumn_2', \$ModifyNews_NewColumn_2, -5);
$addon->hook('EditProfileSave', \$EditProfileSave);
$addon->hook('ModifyNews_NewSearchField_1', \$ModifyNews_NewSearchField_1);
$addon->hook('ModifyNews_NewSearchField_2', \$ModifyNews_NewSearchField_2);
$addon->hook('ModifyNews_Permissions', \$ModifyNews_Permissions);
$addon->hook('BuildNews_Speedup_Code_Delete', \$BuildNews_Speedup_Code_Delete);
$addon->hook('Build_GetData', \$Build_GetData);
$addon->hook('BuildNews_Filtered', \$Build_GetData, 10);
$addon->hook('ViewNews_If', 'NewsCategories_ReadCategoryInfo', 5);
$addon->hook('ViewNews_2', \$Build_GetData, 5);


sub NewsCategories_ReadCategoryInfo {
	%newscategories = ();
	%NC_Permission = ();
	ReadInnerHash('NewsCategories', 'Category', \%newscategories);
	foreach my $id (keys %newscategories) {
		$newscategories{$id}->{'FullName'} = $id unless (defined $newscategories{$id}->{'FullName'});
		$newscategories{$id}->{'ModPermission'} = 1 unless (defined $newscategories{$id}->{'ModPermission'});
		foreach my $i(split(/\`\|\`/,$newscategories{$id}->{'Permission'})){
			$NC_Permission{$id}->{$i} = 1;
		}
	}
	# Make sure we allways have the default category initilized
	unless(exists $newscategories{'(default)'}) {
		$newscategories{'(default)'}->{'FullName'} = '(Default Category)';
		$newscategories{'(default)'}->{'ModPermission'} = 1;
		$NC_Permission{'(default)'}->{'(AllUsers)'} = 1;
	}
	$EnableCategories = 1;
}


sub NewsCategories_SaveCategoryInfo {
	WriteInnerHash('NewsCategories', 'Category', \%newscategories);
}



$Subs{'NewsCategoriesMain'} = <<'END_SUB';
sub NewsCategoriesMain {
	my $addon = shift;
	$addon->pageHeader($addon->getMsg('News Categories'), 1);
	print $addon->heading($addon->getMsg('Current Categories'));
	my $i;
	foreach $i (sort {uc($newscategories{$a}->{'FullName'}) cmp uc($newscategories{$b}->{'FullName'})} keys %newscategories) {
		my ($name, $desc, $actions);
		$name = $newscategories{$i}->{'FullName'};
		my $value = $NC_Permission{$i};
		if ($value->{'(AllUsers)'}) {
			$desc = $addon->getMsg('This category is available to all users.');
		}
		else {
			my @userlist;
			$desc = $addon->getMsg('This category is available to:') . ' <b>';
			if ($value->{'(High)'}) {
				@userlist = ($addon->getMsg('High level users'))
			}
			push(@userlist, grep(!/^\(/, keys %$value));
			if (@userlist) {
				$desc .= join(', ', @userlist);
			}
			else {
				$desc .= $addon->getMsg('nobody');
			}
			$desc .= '</b>.';
		}
		$desc .= qq~<br><b>~ . $addon->getMsg('Internal Category Name') . qq~:</b> $i~;
		$actions = '[' . $addon->link({'action' => 'admin', 'adminarea' => 'delcat', 'cat' => $i}) .
			$addon->getMsg('Delete') . '</a>] ' unless $i eq '(default)';
		$actions .= '[' . $addon->link({'action' => 'admin', 'adminarea' => 'editcat', 'cat' => $i}) . 
			$addon->getMsg('Edit') . '</a>]';

		# HOOK: NewsCategoriesMain_Item
		if($Addons{'NewsCategoriesMain_Item'}){my $w;foreach $w (@{$Addons{'NewsCategoriesMain_Item'}}){my $addon=$w->[2];eval ${$w->[0]};AErr($addon,$@)if $@;};}
	
		print $addon->itemTable($name, $desc, $actions);
	}
	print $addon->heading($addon->getMsg('Create New Category')),
		$addon->form({'action' => 'admin', 'adminarea' => 'createcat'}),
		$addon->settingTable($addon->getMsg('Category Name') . ':', '<input type="text" name="cat">', $addon->getMsg('Category names may only contain letters, numbers, and underscores (_).')),
		$addon->submitButton($addon->getMsg('Create Category')), '</form>';
	$addon->pageFooter;
}
END_SUB

$Subs{'NewsCategories_CreateCat'} = <<'END_SUB';
sub NewsCategories_CreateCat {
	my $addon = shift;
	my $cat = $in{'cat'};
	CRcough($addon->getMsg("The entered category name contains illegal characters.")) if ($cat =~ /[^a-zA-Z0-9_]/);
	CRcough($addon->getMsg("Category [_1] already exists.", $cat)) if $newscategories->{$cat};
	
	$NC_Permission{$cat} = {'(AllUsers)' => 1};
	$newscategories{$cat}->{'Permission'} = '(AllUsers)';
	$newscategories{$cat}->{'FullName'} = $cat;
	$newscategories{$cat}->{'ModPermission'} = 1;
	
	# HOOK: NewsCategories_CreateCat
	if($Addons{'NewsCategories_CreateCat'}){my $w;foreach $w (@{$Addons{'NewsCategories_CreateCat'}}){my $addon=$w->[2];eval ${$w->[0]};AErr($addon,$@)if $@;};}
	
	NewsCategories_SaveCategoryInfo();
	NewsCategoriesMain($addon);
}
END_SUB

$Subs{'NewsCategories_EditCat'} = <<'END_SUB';
sub NewsCategories_EditCat {
	my $addon = shift;
	my $cat = $in{'cat'};
	CRcough($addon->getMsg("Category [_1] doesn't exist.", $cat)) unless $newscategories{$cat};
	$addon->pageHeader($addon->getMsg("Edit Category [_1]", $cat), 1);
	my $nc = $NC_Permission{$cat};
	my %newsCat;
	$newsCat{'fullname'} = $newscategories{$cat}->{'FullName'};
	$newsCat{'modperm'} = $newscategories{$cat}->{'ModPermission'};
	my $selHeight = (scalar %userdata < 6)?scalar %userdata+2:8;
	my $userselect = qq~<select name="catperm" size="$selHeight" multiple><option value="(AllUsers)" ~ .
		($nc->{'(AllUsers)'} ? 'selected>' : '>(') . $addon->getMsg('All Users') . ')</option><option value="(High)"' .
		($nc->{'(High)'} ? ' selected>(' : '>(') . $addon->getMsg('High Level Users') . ')</option>';
	my $i;
	foreach $i (sort keys %userdata) {
		$userselect .= qq~<option value="$i"~ . 
			($nc->{$i} ? ' selected' : '') .
			">$i</option>";
	}
	$userselect .= '</select>';
	print $addon->form({'action' => 'admin', 'adminarea' => 'editcatsave', 'cat' => $cat});
	my @CategorySettings = (
		['fullname', $addon->getMsg('Display Name'), $addon->getMsg('The display name you want to use for this Category. Default is the category name.')],
		['catperm', $addon->getMsg('Submit Permissions'), $addon->getMsg(qq~Users that you select will be able to submit items to, and modify items in, category &quot;[_1]&quot;. You may select multiple users (by using your CTRL or Option key). Administrator users will always have full access to every category.~, $cat), $userselect],
		['modperm', $addon->getMsg('Allow Modification'), $addon->getMsg('Should it be possible to change the category a newsitem belongs to at the Modify News Page once it has been submitted into this category? Administrator users will always be able to change category for a newsitem.'), 'yn']
	);
	
	# HOOK: NewsCategories_EditCatBottom
	if($Addons{'NewsCategories_EditCatBottom'}){my $w;foreach $w (@{$Addons{'NewsCategories_EditCatBottom'}}){my $addon=$w->[2];eval ${$w->[0]};AErr($addon,$@)if $@;};}

	SettingsEngine_Display(\@CategorySettings, \%newsCat);
			
	print q~<div align="center"><input type="submit" value="~ . $addon->getMsg('Save Changes') . q~" class="inputsubmit"></div></form>~;
	$addon->pageFooter;
}
END_SUB

$Subs{'NewsCategories_EditCatSave'} = <<'END_SUB';
sub NewsCategories_EditCatSave {
	my $addon = shift;
	my $cat = $in{'cat'};
	CRcough($addon->getMsg("Category [_1] doesn't exist.", $cat)) unless $newscategories{$cat};
	$newscategories{$cat}->{'FullName'} = $in{'fullname'};
	$newscategories{$cat}->{'ModPermission'} = $in{'modperm'};
	
	my $i;
	my @permissions = ();
	$NC_Permission{$cat} = {};
	foreach $i (split(/\|x\|/, $in{'catperm'})) {
		$NC_Permission{$cat}->{$i} = 1;
		push(@permissions, $i);
	}
	$newscategories{$cat}->{'Permission'} = join('`|`',@permissions);
	
	# HOOK: NewsCategories_EditCatSave
	if($Addons{'NewsCategories_EditCatSave'}){my $w;foreach $w (@{$Addons{'NewsCategories_EditCatSave'}}){my $addon=$w->[2];eval ${$w->[0]};AErr($addon,$@)if $@;};}
	
	NewsCategories_SaveCategoryInfo();
	NewsCategoriesMain($addon);
}
END_SUB

$Subs{'NewsCategories_DeleteCat'} = <<'END_SUB';
sub NewsCategories_DeleteCat {
	NeedCFG();
	my $addon = shift;
	my $cat = $in{'cat'};
	CRcough($addon->getMsg("Category [_1] doesn't exist.", $cat)) unless $newscategories{$cat};
	CRcough($addon->getMsg("You can't do that!")) if $cat eq '(default)';
	
	# HOOK: NewsCategories_DeleteCat_Check
	if($Addons{'NewsCategories_DeleteCat_Check'}){my $w;foreach $w (@{$Addons{'NewsCategories_DeleteCat_Check'}}){my $addon=$w->[2];eval ${$w->[0]};AErr($addon,$@)if $@;};}
	
	AreYouSure($addon->getMsg("If you delete this category, all items which were previously in this category will be moved into the default category. Are you sure you want to delete it?")) unless $in{'really'};
	delete $newscategories{$cat};
	delete $CConfig{"Category-$cat"};
	delete $NC_Permission{$cat};
	
	my ($fh, $fh2) = EditNewsdat_Start();
	
	NDLOOP: while (<$fh>) {
		chomp($_);
		SplitDataFile($_);
		if ($Category eq $cat) {
			$Category = '(default)';
			my $newsline = JoinDataFile();
			print $fh2 $newsline, "\n";
		}
		else {
			print $fh2 $_, "\n";
		}
	}
	
	close($fh);
	close($fh2);
	
	EditNewsdat_Finish();
	&NewsCategoriesMain($addon);
}
END_SUB

$Subs{'NewsCategories_GetCatSelect'} = <<'END_SUB';
sub NewsCategories_GetCatSelect {
	my ($name, $selected) = @_;
	my $select = qq~<select name="$name">~;
	my $cats;
#	foreach $cats (sort keys %newscategories) {
	foreach $cats (sort {uc($newscategories{$a}->{'FullName'}) cmp uc($newscategories{$b}->{'FullName'})} keys %newscategories) {
		if( NewsCategories_CheckPermission($cats) ) {
			$select .= qq~<option value="$cats"~ . ($cats eq $selected ? ' selected' : '') . ">$newscategories{$cats}->{'FullName'}</option>";
		}
	}
	$select .= '</select>';
	return $select;
}
END_SUB



# Only perform an upgrade of the category DB or patch of the core when needed...
if( ($crcgiBuild < 45) || ($CConfig{'NewsCategoriesBuild'} < 10) ) {
$Subs{'NewsCategories_UpgradePatch'} = <<'END_SUB';
sub NewsCategories_UpgradePatch {
	# Check if Coranto below build 45 is used (Version 1.31) since the BUILDNEWS_SKIPDELETE marker was added then.
	if($crcgiBuild < 45) {
		# Add an extra hook in buildnews so that we can override the normal speedup code
		$search = q~unless \(\$ProfCats\{\$i\}\-\>\{\$in\{'Category'\}\} \|\| \$ProfCats\{\$i\}\-\>\{'AllCategories'\}\)\{(\s)+?delete \$ActiveProfiles\{\$i\};(\s)+?next INITLOOP;~;
		$replace = q~unless ($ProfCats{$i}->{$in{'Category'}} || $ProfCats{$i}->{'AllCategories'}){
			BUILDNEWS_SKIPDELETE:{if($Addons{'BuildNews_Speedup_Code_Delete'}){my $w;foreach $w (@{$Addons{'BuildNews_Speedup_Code_Delete'}}){my $addon=$w->[2];eval ${$w->[0]};AErr($addon,$@)if $@;};}
			delete $ActiveProfiles{$i};
			next INITLOOP;}~;
		$::Subs{'BuildNews'} =~ s/$search/$replace/g;
	}
	
	# Do we need to perform an upgrade of the category database
	unless($CConfig{'NewsCategoriesBuild'} >= 10) {
		%newscategories = ();
		ReadInnerHash('NewsCategories', 'Category', \%newscategories);
		foreach my $id (keys %newscategories) {
			@permissions = ();
			foreach $i (sort keys %userdata) {
				if($newscategories{$id}->{$i}) {
					push(@permissions, $i);
					delete $newscategories{$id}->{$i};
				}
			}
			if($newscategories{$id}->{'(AllUsers)'}) {
				push(@permissions, '(AllUsers)');
				delete $newscategories{$id}->{'(AllUsers)'};
			}
			if($newscategories{$id}->{'(High)'}) {
				push(@permissions, '(High)');
				delete $newscategories{$id}->{'(High)'};
			}
			$newscategories{$id}->{'Permission'} = join('`|`',@permissions);
		}
		NewsCategories_SaveCategoryInfo();		
		$CConfig{'NewsCategoriesBuild'} = $NewsCategory_build;
	}

}
END_SUB
$addon->hook('EarlyHook', 'NewsCategories_UpgradePatch', 10);
}

1;