#[BOFN]###############################################################################
#
#
#Pagenews - a free script to publish news on websites
#Copyright (C) 2004,2005,2006,2007,2008 Philipp Kindt
#
#This file is part of Pagenews.
#
# 	 This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#[EOFN]###############################################################################


#this package is the newsfeed interface for donkvparser sources

package dnfeeds_dkvp;

#this constructor ist actually called only via dnfeeds::new()
#it must get the following param: $_[1] must contain the donkvparser object
#$_[2] must be the feedname;
sub new{
	my $classname = shift();
	my $self = {};
	bless($self,$classname);
	my $dkvp = shift();
	my $feedname = shift();
	$self->{'dkvp'} = $dkvp;
	my $lastindex = $#{$self->{'dkvp'}->{'data'}};
	for(my $cnt = 0; $cnt <= $lastindex; $cnt++){
		#set posting ID
		$self->{'dkvp'}->{'data'}->[$cnt]->{'POST_ID'} = $self->{'dkvp'}->{'data'}->[$cnt]->{'DKVP_BID'};
		#set feedname
		$self->{'dkvp'}->{'data'}->[$cnt]->{'feedname'} = $feedname;
	};

	return $self;	
};
########################################################################################################
#this function creates a new posting - data must be in hash reference $_[1]
sub create_posting{
	my $self = $_[0];
	my $newid = $self->{'dkvp'}->add_block();
	$self->check_posting($_[1],"new");
	$self->{'dkvp'}->set_block($newid, $_[1]);
};

########################################################################################################
#returns a single posting having the id $_[1] as a hash reference;
sub get_posting_by_id{
	my $self = $_[0];
	my $id = $_[1];
	my $index = $self->{'dkvp'}->get_index($id);
	
	my $post = $self->{'dkvp'}->{'data'}->[$index];
	$post->{'POST_INDEX'} = $index;
	return $post;
};

########################################################################################################
#adds the missing fields of an existing posting (at least ID must exist) represented by the posting 
#hash reference in $_[2]
#the ID of the original posting must be in $_[1]
#missing fields mean: KV-Pairs that exist in the original posting but not in the $_[2] - data  
#returnes complete newdata hash even though parameters are given by reference
#ignores DKVP_BID
sub set_missing_fields{
	my $self = $_[0];
	my $new_data = $_[2];
	my $original_posting = $self->get_posting_by_id($_[1]);
	
	#compare every key - add if key not set
	foreach my $key (keys(%{$original_posting})){
		if(!(exists($new_data->{$key}))){
		if($key ne "DKVP_BID"){
			$new_data->{$key} = $original_posting->{$key};
			};
		};
	};
	return $new_data;
};

########################################################################################################
#modifies posting with id $_[1] ; the hash REFERENCE $_[2] containing the posting data will be stored
sub modify_posting{
	my $self = $_[0];
	$self->set_missing_fields($_[1],$_[2]);
	$self->check_posting($_[2],"modified");				#check if posting data is correct
	$self->{'dkvp'}->set_block($_[1],$_[2]);
};  

########################################################################################################
#deletes the posting with id $_[1]
sub delete_posting{
	my $self = $_[0];
	$self->{'dkvp'}->delete_block($_[1]);
};

########################################################################################################
#moves posting with id $_[1] to position $_[2]
#the latest posting has position 0
sub move_posting_to_pos{
	my $self = $_[0];
	
	#positions must be swapped due to the way of storaging of the dkv-parser
	my $swappedindex = $#{$self->{'dkvp'}->{'data'}} - $_[2];
	$self->{'dkvp'}->move_BID_to_index($_[1],$swappedindex);
};

########################################################################################################
#updates the database after modifications
sub update_db(){
	my $self = shift();
	my $lastindex = $#{$self->{'dkvp'}->{'data'}};
	for(my $cnt = 0; $cnt <= $lastindex; $cnt++){
		#delete posting ID field
		delete $self->{'dkvp'}->{'data'}->[$cnt]->{'POST_ID'};
		#delete feedname field
		delete $self->{'dkvp'}->{'data'}->[$cnt]->{'feedname'};
	};	
	$self->{'dkvp'}->write();	
};
########################################################################################################
#This function is designed to give back a couple of postings to show it on a news page.
#it returns an array of references to hashes containing the post data.
#the function uses posting INDEXES for identification. Index means the number of the posting in the
#order to be shown (=in the database file) counting from the latest posting(the last block in the db file) 
#this is NOT SAVE FOR IDENTIFIENG A CERTAIN POSTING and should only be used to display a couple of postings
#as done on newspages.
#
#NEVER USE THIS FUNCTION FOR TASKS THAT MIGHT IN ANY WAY MODIFY ANYTHING IN THE POSTING! 
#the positions of news might be changed (for example if a posting is deleted)
#params: 
#$_[1] is the start index
#$_[2] is the number of postings to be returned.
#
#IDs would not be suitable for identification as IDs are not stored in a particular order
#if $_[3] is not empty, only postings whose postdata $_[3] has the value $_[4] are returned.

sub get_postings_by_start_order{
	my $self = $_[0];
	my $latest = $_[1];
	my $numberofpostings = $_[2];
	
	my $lastindex = $#{$self->{'dkvp'}->{'data'}};
	
	#check position for errors
	if($latest < 0){
		$latest = 0;
	};
	if($latest > $lastindex){
		$latest = $lastindex;
	};
	
	#calculate the number of the oldest posting to be returned and check it for errors
	#this case is quite likely, for example when the database is smaller than $numberofpostings
	my $oldest = $latest + $numberofpostings -1;
	if($oldest >= $lastindex){
		$oldest = $lastindex;
	};
	
	my @retarray;
	
	
	#the dkvp file is allready sorted: latest blocks are at the bottom => the order must be turned arround
	for(my $cnt = $lastindex - $latest; $cnt >= $lastindex - $oldest; $cnt--){
		if(($_[3] eq "")||($self->{'dkvp'}->{'data'}->[$cnt]->{$_[3]} eq $_[4])){
			push(@retarray, $self->{'dkvp'}->{'data'}->[$cnt]);
		
		};
	};
	
	return @retarray;
	
};

########################################################################################################
#returns number of postings
sub get_number_of_postings{
	my $self = $_[0];
	return ($self->{'dkvp'}->{'info'}->{'data'}->{'nrofblocks'});
};

########################################################################################################
#returns an array containing posting IDs.
sub get_list_of_ids{
	my $self = $_[0];
	my @list;
	for(my $cnt = 0; $cnt < $self->{'dkvp'}->{'info'}->{'data'}->{'nrofblocks'}; $cnt++){
		push(@list, $self->{'dkvp'}->{'data'}->[$cnt]->{'DKVP_BID'});
	};
	return @list;
};

########################################################################################################
#returns number of postings where key $_[1] has value $_[2]
sub get_number_of_postings_with_condition{
	my $self = $_[0];
	my @postings = $self->get_postings_by_start_order(0,$self->get_number_of_postings());
	my $cnt = 0;	
	foreach $posting (@postings){
		if($posting->{$_[1]} eq $_[2]){
			$cnt++;
		};
	};
	return $cnt;
};


########################################################################################################
#checks the posting which is given as a hash reference in $_[1] for 
#the minimum required fields.
#it sets params like the time and the id if necessary
#$_[2] indiceates, if the posting is to be newly created ("new" or emty) or to be modified ("modified")
sub check_posting{
	my $self = $_[0];
	my $createflag;
	if($_[2] eq "modified"){
		$createflag = 1;
	}else{
		$createflag = 0;
	};
	
		if(($_[1]->{'poster'} eq "")||($_[1]->{'subject'} eq "")||($_[1]->{'headline'} eq "")||($_[1]->{'content'} eq "")){
			#donstdlib::error("dnfeeds_dkvp_postvaluesnotset");
		};
	if($createflag == 0){
		$_[1]->{'timestamp'} = time;
		$_[1]->{'time'} = localtime;
		delete $_[1]->{'POST_ID'};
	}else{
		#keep/set Block ID
		$_[1]->{'DKVP_BID'} = $_[1]->{'POST_ID'};
		delete $_[1]->{'POST_ID'};
	};
};

1;

########################################################################################################
#creates a new newsfeed. the feed name must be given in $_[1], the path in $_[2]
#the reference of a feed is stored in $self->{'dkvp'}.
#it is safe to create a new dnfeeds_dkvp object which has no reference on a donkvparser object and 
#create a new feed right after object initialization.
#IT WON'T WORK IF A FEED IS ALLREADY SET.
sub create_feed{
	my $self = $_[0];
	my $filename = $_[2]."/".$_[1].".dnfeed";
	if(ref($self->{'dkvp'}) ne ""){
		return 0;	
	};

	if(donstdlib::make_sure_that_exists($filename,"checkmode") == 1){
		donstdlib::error("dnfeeds_dkvp_feedallreadyexists",$filename);
	};

	$self->{'dkvp'} = donkvparser->new();
	my $bid = $self->{'dkvp'}->add_block("header");
	$self->{'dkvp'}->set_value($bid,"PAGENEWS FEEDTYPE","PAGENEWS NATIVE DKVP","header");
	$self->{'dkvp'}->set_value($bid,"writable","yes","header");
	$self->{'dkvp'}->set_value($bid,"feed_title","","header");

	$self->{'dkvp'}->set_filename($filename);
	$self->{'feedname'} = $_[1];
	$self->{'feedpath'} = $_[2];

	$self->{'dkvp'}->write();
	return $self;
};

########################################################################################################
#gets feed header value $_[1]
sub get_feed_property{
	my $self = $_[0];
	return $self->{'dkvp'}->get_value(0,$_[1],"header");
}
########################################################################################################
#returns full feed header as a hash
sub get_feed_properties{
	my $self = $_[0];
	my %rv;
	foreach my $key (keys(%{$self->{'dkvp'}->{'header'}->[0]})){
		$rv{$key} = $self->get_feed_property($key);
	}	
	return %rv;
}

########################################################################################################
#sets feed header value $_[1]. to $_[2]
#After that, a rewrite is necessary
sub set_feed_property{
	my $self = $_[0];
	$self->{'dkvp'}->set_value(0,$_[1],$_[2],"header");
}


########################################################################################################
#comment-dependent functions
########################################################################################################
#returns number of comments for posting id $_[1].
#(does not create a new comment feed in case of non-existance, do not worry!)
sub get_number_of_comments{
	my $self = $_[0];
	if(dnfeeds_common::check_for_comment_feed_existance($self->{'feedname'},$_[1]) == 0){
		return 0;
	}else{;
		my $comment_feed = dnfeeds->new($self->{'feedname'}, $_[1]); 	
		return $comment_feed->get_number_of_postings();
	};
};



########################################################################################################
#search-engine related functions
########################################################################################################
#returns postings that fit the search pattern $_[1]
#$_[2] can contain a colour hex string (#rrggbb).
#all matches are highlighted with this background
sub search_feed{
	my $self = $_[0];
	my $nrofblocks = $self->{'dkvp'}->{'info'}->{'data'}->{'nrofblocks'};
	my @retarray; 
	for(my $cnt = 0; $cnt < $nrofblocks; $cnt++){
		#go through each key
		foreach my $key (keys(%{$self->{'dkvp'}->{'data'}->[$cnt]})){
			#exclude some keys
			if(($key ne "DKVP_BID")&&($key ne "feedname")&&($key ne "timestamp")&&($key ne "flag_convert_before_edit")&&($key ne "POST_ID")){
				#strip html
				my $searchtxt = doncgitools::strip_html_tags($self->{'dkvp'}->{'data'}->[$cnt]->{$key});
				#search; if found, push to return array;
				if($searchtxt =~ m/$_[1]/i){
					$found = 1;
					if($_[2] ne ""){
						$self->{'dkvp'}->{'data'}->[$cnt]->{$key} =~ s/$_[1]/\<font color \= \"$_[2]\"\>$_[1]\<\/font\>/gi;
					};
	

					push @retarray, $self->{'dkvp'}->{'data'}->[$cnt];
					goto PROCEED;
				};		
			};
		};
		PROCEED:

	};
return @retarray;
};

