<?php
/**
* @version	$Id: articles_event_handler.php 12737 2009-10-20 19:35:58Z alex $
* @package	In-News
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class ArticlesEventHandler extends kCatDBEventHandler {

		/**
		 * Filters out archived articles
		 *
		 * @param kEvent $event
		 */
		function SetCustomQuery(&$event)
		{
			parent::SetCustomQuery($event);

			$object =& $event->getObject();

			if (!$this->Application->isAdminUser) {
				$where_clause = '(Archived = 0) AND (StartDate < '.adodb_mktime().' OR StartDate = 0) AND (EndOn > '.adodb_mktime().' OR EndOn IS NULL)';
				$object->addFilter('archived_filter', $where_clause);
			}
		}

		/**
		 * Return type clauses for list bulding on front
		 *
		 * @param kEvent $event
		 * @return Array
		 */
		function getTypeClauses(&$event)
		{
			$type_clauses = parent::getTypeClauses($event);

			$type_clauses['site_lead']['include']='%1$s.LeadStory = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
			$type_clauses['site_lead']['except']='%1$s.LeadStory <> 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
			$type_clauses['site_lead']['having_filter'] = false;

			$type_clauses['cat_lead']['include']='%1$s.LeadCatStory = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
			$type_clauses['cat_lead']['except']='%1$s.LeadCatStory <> 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
			$type_clauses['cat_lead']['having_filter'] = false;

			return $type_clauses;
		}


		/**
		 * [REGULAR EVENT] Deletes expired articles + update existing articles from rss feed with new data (key - article url)
		 *
		 * @param kEvent $event
		 */
		function OnUpdateRSSAtricles(&$event)
		{
			$category_table = $this->Application->getUnitOption('c', 'TableName');
			$custom_table = $this->Application->getUnitOption('c-cdata', 'TableName');

			$category_custom_fields = $this->getCustomColumns('c');
			$article_custom_fields = $this->getCustomColumns($event->Prefix);


			// update categories which sould be updated
			$sql = 'SELECT cd.*, c.CategoryId
					FROM '.$category_table.' c
					LEFT JOIN '.$custom_table.' cd ON c.ResourceId = cd.ResourceId
					WHERE 	(IF(cd.'.$category_custom_fields['RssLastUpdated'].' IS NULL, 0, cd.'.$category_custom_fields['RssLastUpdated'].') +
							cd.'.$category_custom_fields['RssUpdateInterval'].' * cd.'.$category_custom_fields['RssUpdateIntervalType'].' <=
							'.adodb_mktime().') AND (LENGTH('.$category_custom_fields['RssSource'].') > 0)';
			$categories = $this->Conn->Query($sql, 'CategoryId');
			if ($categories) {
				$resource_ids = Array();
				foreach ($categories as $category_id => $category_data) {
					$resource_ids[] = $category_data['ResourceId'];
					$event->setEventParam('source_url', $category_data[ $category_custom_fields['RssSource'] ]);
					$event->setEventParam('category_id', $category_id);
					$event->setEventParam('custom_fields', $article_custom_fields);
					$event->setEventParam('life_time',  $category_data[ $category_custom_fields['RssDefaultExpiration'] ] * $category_data[ $category_custom_fields['RssDefaultExpirationType'] ]);
					$this->parseFeed($event);
				}

				$sql = 'UPDATE '.$custom_table.'
						SET '.$category_custom_fields['RssLastUpdated'].' = '.adodb_mktime().'
						WHERE ResourceId IN ('.implode(',', $resource_ids).')';
				$this->Conn->Query($sql);
			}

			// delete expired articles from feed categories
			$sql = 'SELECT c.CategoryId, c.ResourceId
					FROM '.$category_table.' c
					LEFT JOIN '.$custom_table.' cd ON c.ResourceId = cd.ResourceId
					WHERE (	IF(cd.'.$category_custom_fields['RssLastExpired'].' IS NULL, 0, cd.'.$category_custom_fields['RssLastExpired'].') +
							cd.'.$category_custom_fields['RssExpireInterval'].' * cd.'.$category_custom_fields['RssExpireIntervalType'].' <=
							'.adodb_mktime().') AND (cd.'.$category_custom_fields['RssDeleteExpired'].' = 1)';

			$categories = $this->Conn->GetCol($sql, 'ResourceId');

			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table = $this->Application->getUnitOption($event->Prefix, 'TableName');
			$ci_table = $this->Application->getUnitOption($event->Prefix.'-ci', 'TableName');

			if ($categories) {
				$article_custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');

				$sql = 'SELECT main_table.'.$id_field.'
						FROM '.$table.' main_table
						LEFT JOIN '.$ci_table.' ci ON main_table.ResourceId = ci.ItemResourceId
						LEFT JOIN '.$article_custom_table.' cd ON main_table.ResourceId = cd.ResourceId
						WHERE 	(ci.PrimaryCat = 1) AND
								(ci.CategoryId IN ('.implode(',', $categories).')) AND
								(main_table.EndOn < '.adodb_mktime().' AND main_table.EndOn IS NOT NULL) AND
								(LENGTH(cd.'.$article_custom_fields['RssOriginalURL'].') > 0)';
				$article_ids = $this->Conn->GetCol($sql);
				if ($article_ids) {
					$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
					$temp->DeleteItems($event->Prefix, $event->Special, $article_ids);
				}

				$sql = 'UPDATE '.$custom_table.'
						SET '.$category_custom_fields['RssLastExpired'].' = '.adodb_mktime().'
						WHERE ResourceId IN ('.implode(',', array_keys($categories)).')';
				$this->Conn->Query($sql);
			}
		}

		/**
		 * Returns article ids & crc, that are created during feed import
		 *
		 * @param kEvent $event
		 * @return Array
		 */
		function getFeedArticles(&$event)
		{
			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table = $this->Application->getUnitOption($event->Prefix, 'TableName');
			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');

			$crc_field = $event->getEventParam('custom_fields', 'RssArticleCRC');

			$sql = 'SELECT main_table.'.$id_field.', cd.'.$crc_field.'
					FROM '.$table.' main_table
					LEFT JOIN '.$custom_table.' cd ON cd.ResourceId = main_table.ResourceId
					WHERE LENGTH(cd.'.$crc_field.') > 0';
			return $this->Conn->GetCol($sql, $crc_field);
		}

		/**
		 * Creates new, updates existing articles from feed url specified
		 *
		 * @param kEvent $event
		 */
		function parseFeed(&$event)
		{
			$source_urls = explode(',', $event->getEventParam('source_url'));
			if (count($source_urls) > 1) {
				foreach ($source_urls as $source_url) {
					$event->setEventParam('source_url', $source_url);
					$this->parseFeed($event);
				}
				return true;
			}

			$curl_helper =& $this->Application->recallObject('CurlHelper');
			/* @var $curl_helper kCurlHelper */

			$curl_helper->followLocation = true;
			$curl_helper->setOptions( Array (CURLOPT_USERAGENT => 'Wget/1.10.2') ); // otherwise FeedBurner will return HTML

			$xml_data = $curl_helper->Send($event->getEventParam('source_url'));

			if (!$xml_data) {
				return false;
			}

			$xml_helper =& $this->Application->recallObject('kXMLHelper');
			/* @var $xml_helper kXMLHelper */

			$root_node =& $xml_helper->Parse($xml_data, XML_WITH_TEXT_NODES);

			$feed_types = Array (
				'rss_2.0' => 'channel', 'atom' => 'feed',
			);

			foreach ($feed_types as $feed_type => $node_name) {
				$article_node =& $root_node->FindChild($node_name);
				if (is_object($article_node)) {
					break;
				}
			}

			if (!$article_node) {
				return false;
			}

			$category_id = $event->getEventParam('category_id');
			$backup_category_id = $this->Application->GetVar('m_cat_id');
			$this->Application->SetVar('m_cat_id', $category_id);

			switch ($feed_type) {
				case 'rss_2.0':
					$this->parseRssFeed($article_node, $event);
					break;

				case 'atom':
					$this->parseAtomFeed($article_node, $event);
					break;
			}

			$this->Application->SetVar('m_cat_id', $backup_category_id);
		}

		/**
		 * Returns ML field names for article record
		 *
		 * @return Array
		 */
		function _getMLFields()
		{
			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
			/* @var $ml_formatter kMultiLanguage */

			$title_field = $ml_formatter->LangFieldName('Title');
			$body_field = $ml_formatter->LangFieldName('Body');

			return Array ('Title', 'Body');
			return Array ($title_field, $body_field);
		}

		/**
		 * Parses RSS 2.0 feed
		 *
		 * @param kXMLNode $root_node
		 * @param kEvent $event
		 */
		function parseRssFeed(&$root_node, &$event)
		{
			$current_node = $root_node->firstChild;
			$feed_articles = $this->getFeedArticles($event);

			$object =& $this->Application->recallObject($event->Prefix.'.-item', null, Array('skip_autoload' => true));
			/* @var $object kDBItem */

			list ($title_field, $body_field) = $this->_getMLFields();

			do {
				// IMAGE is information about channel and is not useful here
				if ($current_node->Name != 'ITEM') continue;
				// collect item data
				$data = Array();
				$sub_node =& $current_node->firstChild;
				/* @var $sub_node kXMLNode */

				do {
					if ($sub_node->Name == 'ATOM:SUMMARY') {
						$data[$sub_node->Name] = $this->getNodeContent($sub_node);
					} else {
						if ($sub_node->Children) {
							foreach ($sub_node->Children as $child_node) {
								$data[$sub_node->Name].= $child_node->Data; // was  $sub_node->Data;
							}
						}
					}

				} while ( ($sub_node =& $sub_node->NextSibling()) );

				// create/update article
				$article_crc = crc32($data['LINK'].$data['TITLE']);
				$article_id = getArrayValue($feed_articles, $article_crc);
				if ($article_id) {
					$object->Load($article_id);
				}
				else {
					$object->Clear();
				}

				$object->SetDBField($title_field, $data['TITLE']);
				$object->SetDBField('cust_RssOriginalURL', $data['LINK']);
				$object->SetDBField('cust_RssArticleCRC', $article_crc);
				$object->SetDBField($body_field, !array_key_exists('DESCRIPTION', $data) ? $data['ATOM:SUMMARY'] : $data['DESCRIPTION']);
				$expiration_time = adodb_mktime() + $event->getEventParam('life_time');
				$object->SetDBField('EndOn_date', $expiration_time);
				$object->SetDBField('EndOn_time', $expiration_time);
				$object->SetDBField('Status', STATUS_ACTIVE);
				$object->SetDBField('Author', 'root');
				$object->SetDBField('CreatedById', -1);

				$status = $object->isLoaded() ? $object->Update() : $object->Create();
			} while (($current_node =& $current_node->NextSibling()));
		}

		/**
		 * Returns parsed node content
		 *
		 * @param kXMLNode $node
		 * @return string
		 */
		function getNodeContent(&$node)
		{
			$content_type = array_key_exists('TYPE', $node->Attributes) ? $node->Attributes['TYPE'] : false;

			switch ($content_type) {
				case 'xhtml':
					$data = $node->GetXML(true);
					break;

				case 'html':
					$data = unhtmlentities($node->firstChild->Data); // $node->Data
					break;

				default:
					$data = $node->firstChild->Data; // $node->Data; also for 'text'
					break;
			}

			return trim($data);
		}

		/**
		 * Parses ATOM feed
		 *
		 * @param kXMLNode $root_node
		 * @param kEvent $event
		 */
		function parseAtomFeed(&$root_node, &$event)
		{
			$current_node = $root_node->firstChild;
			$feed_articles = $this->getFeedArticles($event);

			$object =& $this->Application->recallObject($event->Prefix.'.-item', null, Array('skip_autoload' => true));
			/* @var $object kDBItem */

			list ($title_field, $body_field) = $this->_getMLFields();

			do {
				if ($current_node->Name != 'ENTRY') continue;
				// collect item data
				$data = Array();
				$sub_node =& $current_node->firstChild;
				/* @var $sub_node kXMLNode */

				do {
					if ($sub_node->Name == 'LINK') {
						if ($sub_node->Attributes['REL'] == 'alternate') {
							$data[$sub_node->Name] = $sub_node->Attributes['HREF'];
						}
					}
					elseif ($sub_node->Name == 'CONTENT' || $sub_node->Name == 'SUMMARY' || $sub_node->Name == 'TITLE') {
						$data[$sub_node->Name] = $this->getNodeContent($sub_node);
					}
					else {
						$data[$sub_node->Name] = $sub_node->firstChild->Data; // $sub_node->Data
					}
				} while ( ($sub_node =& $sub_node->NextSibling()) );

				// create/update article
				$article_crc = crc32($data['LINK'].$data['TITLE']);
				$article_id = getArrayValue($feed_articles, $article_crc);
				if ($article_id) {
					$object->Load($article_id);
				}
				else {
					$object->Clear();
				}

				$object->SetDBField($title_field, $data['TITLE']);
				$object->SetDBField('cust_RssOriginalURL', $data['LINK']);
				$object->SetDBField('cust_RssArticleCRC', $article_crc);
				$object->SetDBField($body_field, !array_key_exists('CONTENT', $data) ? $data['SUMMARY'] : $data['CONTENT']);
				$expiration_time = adodb_mktime() + $event->getEventParam('life_time');
				$object->SetDBField('EndOn_date', $expiration_time);
				$object->SetDBField('EndOn_time', $expiration_time);
				$object->SetDBField('Status', STATUS_ACTIVE);
				$object->SetDBField('Author', 'root');
				$object->SetDBField('CreatedById', -1);

				$status = $object->isLoaded() ? $object->Update() : $object->Create();
			} while (($current_node =& $current_node->NextSibling()));
		}

		function getCustomColumns($prefix)
		{
			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');

			$custom_fields = array_flip($this->Application->getUnitOption($prefix, 'CustomFields'));
			foreach ($custom_fields as $custom_name => $custom_id) {
				$custom_fields[$custom_name] = $ml_formatter->LangFieldName('cust_'.$custom_id);
			}

			return $custom_fields;
		}

		/**
		 * Create missing excerpt
		 *
		 * @param kEvent $event
		 */
		function OnBeforeItemUpdate(&$event)
		{
			parent::OnBeforeItemUpdate($event);

			$this->createExcerpt($event);
			$this->cacheItemOwner($event, 'CreatedById', 'Author');
		}

		/**
		 * Create missing excerpt
		 *
		 * @param kEvent $event
		 */
		function OnBeforeItemCreate(&$event)
		{
			parent::OnBeforeItemCreate($event);

			$this->createExcerpt($event);
			$this->cacheItemOwner($event, 'CreatedById', 'Author');
		}

		/**
		 * Create excerpt if missing
		 *
		 * @param kEvent $event
		 */
		function createExcerpt(&$event)
		{
			$object =& $event->getObject();

			if (!$object->GetField('Excerpt') || $this->Application->GetVar('generate_excerpt')) {
				$excerpt = strip_tags( $object->GetField('Body') );


				$length = mb_strlen($excerpt);
				if ($length > 100) {
					$excerpt = mb_substr(strip_tags($excerpt), 0, 100);
					if (mb_substr($excerpt,-1) != ' ') {
						$pos = mb_strrpos($excerpt, ' ');
						if ($pos) {
							$excerpt = mb_substr($excerpt, 0, $pos);
						}
					}
					$excerpt .= '...';
				}

				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
				/* @var $ml_formatter kMultiLanguage */

				$object->SetDBField($ml_formatter->LangFieldName('Excerpt'), $excerpt);
			}
		}

		/**
		 * [HOOK] Updates category custom fields options in config
		 *
		 * @param kEvent $event
		 */
		function OnUpdateCategoryCustomFields(&$event)
		{
			$new_virtual_fields =	Array(
											'cust_RssSource'				=>	Array('type' => 'string', 'default' => ''),
											'cust_RssDefaultExpiration'		=>	Array('type' => 'int', 'not_null' => 1, 'default' => ''),
											'cust_RssDefaultExpirationType'	=>	Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'use_phrases' => 1, 'options' => Array(60 => 'la_opt_min', 3600 => 'la_opt_hour', 86400 => 'la_opt_day', 2419200 => 'la_opt_month', 29030400 => 'la_opt_year'), 'default' => 60),
											'cust_RssExpireInterval'		=>	Array('type' => 'int', 'not_null' => 1, 'default' => ''),
											'cust_RssExpireIntervalType'	=>	Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'use_phrases' => 1, 'options' => Array(60 => 'la_opt_min', 3600 => 'la_opt_hour', 86400 => 'la_opt_day', 2419200 => 'la_opt_month'), 'default' => 60),
											'cust_RssDeleteExpired'			=>	Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'use_phrases' => 1, 'options' => Array(1 => 'la_Yes', 0 => 'la_No'), 'default' => 0),
											'cust_RssUpdateInterval'		=>	Array('type' => 'int', 'not_null' => 1, 'default' => ''),
											'cust_RssUpdateIntervalType'	=>	Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'use_phrases' => 1, 'options' => Array(60 => 'la_opt_min', 3600 => 'la_opt_hour', 86400 => 'la_opt_day', 2419200 => 'la_opt_month'), 'default' => 60),
											'cust_RssLastUpdated'			=>	Array('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => ''),
											'cust_RssLastExpired'			=>	Array('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => ''),
									);
			$virtual_fields = $this->Application->getUnitOption('c', 'VirtualFields');
			$virtual_fields = array_merge_recursive2($virtual_fields, $new_virtual_fields);
			$this->Application->setUnitOption('c', 'VirtualFields', $virtual_fields);
		}

		/**
		 * Sets default expiration based on module setting
		 *
		 * @param kEvent $event
		 */
		function OnPreCreate(&$event)
		{
			parent::OnPreCreate($event);

			if ($event->status == erSUCCESS) {
				$object =& $event->getObject();

				$archive_days = $this->Application->ConfigValue('News_Archive');
				if ($archive_days) {
					$expire_date = adodb_mktime() + $archive_days * 3600 * 24;
					$object->SetDBField('EndOn_date', $expire_date);
					$object->SetDBField('EndOn_time', $expire_date);
				}
			}
		}
	}