<?php
/**
* @version	$Id: links_event_handler.php 14897 2011-12-21 15:21:13Z alex $
* @package	In-Link
* @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 LinksEventHandler extends kCatDBEventHandler {

		/**
		 * Allows to override standard permission mapping
		 *
		 */
		function mapPermissions()
		{
			parent::mapPermissions();
			$permissions = Array(
				'OnContactFormSubmit' => Array('self' => true),
				'OnProcessReciprocalLinks' => Array('self' => true),
				'OnSetGrouping' => Array('self' => 'view'),
				'OnStoreSelected' => Array('self' => 'view'),
				'OnMerge' => Array('self' => 'edit'),
			);
			$this->permMapping = array_merge($this->permMapping, $permissions);
		}

		/**
		 * Apply any custom changes to list's sql query
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 * @see kDBEventHandler::OnListBuild()
		 */
		protected function SetCustomQuery(&$event)
		{
			parent::SetCustomQuery($event);

			$object =& $event->getObject();
			/* @var $object kDBList */

			if ( !$this->Application->isAdminUser ) {
				$object->addFilter('expire_filter', '(Expire > ' . adodb_mktime() . ' OR Expire IS NULL)');
			}

			if ( substr($event->Special, 0, 10) == 'duplicates' ) {
				$object->removeFilter('category_filter');

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

				$grouping = $link_helper->getGrouping($event->getPrefixSpecial());

				switch ($event->Special) {
					case 'duplicates':
						foreach ($grouping as $group_field) {
							$object->AddGroupByField($object->TableName . '.' . $group_field);
						}

						$object->addFilter('has_dupes_filter', 'DupeCount > 1', kDBList::AGGREGATE_FILTER);
						break;

					case 'duplicates-sub':
						$main_object =& $this->Application->recallObject($event->Prefix . '.duplicates');
						/* @var $main_object kDBItem */

						foreach ($grouping as $field_index => $group_field) {
							$object->addFilter('dupe_filter_' . $field_index, '%1$s.`' . $group_field . '` = ' . $this->Conn->qstr($main_object->GetDBField($group_field)));
						}
						break;
				}

				$object->addFilter('primary_filter', TABLE_PREFIX . 'CategoryItems.PrimaryCat = 1');
			}
		}

		/**
		 * Set groping fields for link duplicate checker
		 *
		 * @param kEvent $event
		 */
		function OnSetGrouping(&$event)
		{
			$this->Application->LinkVar($event->getPrefixSpecial(true).'_dupe_fields', $event->getPrefixSpecial().'_dupe_fields');
		}

		/**
		 * Merge duplicate links together (only categories) & delete duplicates
		 *
		 * @param kEvent $event
		 */
		function OnMerge(&$event)
		{
			$link_helper =& $this->Application->recallObject('LinkHelper');
			/* @var $link_helper LinkHelper */

			$grouping = $link_helper->getGrouping($event->getPrefixSpecial());

			$ids = $this->StoreSelectedIDs($event);
			if ( !$ids ) {
				return;
			}

			// check, that user has not selected multiple links from same group
			$primary_links = Array ();

			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
			$sql = 'SELECT *
					FROM ' . $table_name . '
					WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ')';
			$links = $this->Conn->Query($sql, $id_field);

			$groping_error = false;

			foreach ($links as $link_data) {
				$group_key = '';
				foreach ($grouping as $grouping_field) {
					$group_key .= 'main_table.`' . $grouping_field . '` = ' . $this->Conn->qstr($link_data[$grouping_field]) . ' AND ';
				}
				$group_key = substr($group_key, 0, -5);

				if ( isset($primary_links[$group_key]) ) {
					$groping_error = true;
					break;
				}
				else {
					$primary_links[$group_key] = $link_data['ResourceId'];
				}
			}

			if ( !$groping_error ) {
				$temp_handler =& $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
				/* @var $temp_handler kTempTablesHandler */

				$categories_sql = '	SELECT main_table.ResourceId, ci.CategoryId, main_table.' . $id_field . '
									FROM ' . $table_name . ' main_table
									LEFT JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON main_table.ResourceId = ci.ItemResourceId
									WHERE %s';

				foreach ($primary_links as $group_key => $primary_resource_id) {
					$categories = Array ();
					$group_links = Array ();
					$group_categories = $this->Conn->Query(sprintf($categories_sql, $group_key));

					foreach ($group_categories as $category_data) {
						$group_links[$category_data['ResourceId']] = $category_data[$id_field];
						$categories[$category_data['ResourceId'] == $primary_resource_id ? 'remove' : 'add'][] = $category_data['CategoryId'];
					}

					unset($group_links[$primary_resource_id]);
					$categories = array_unique(array_diff($categories['add'], $categories['remove']));

					if ( $categories ) {
						// add link to other link categories
						$values_sql = '';
						foreach ($categories as $category_id) {
							$values_sql .= '(' . $category_id . ',' . $primary_resource_id . ',0),';
						}
						$values_sql = substr($values_sql, 0, -1);
						$insert_sql = 'INSERT INTO ' . TABLE_PREFIX . 'CategoryItems (CategoryId,ItemResourceId,PrimaryCat) VALUES ' . $values_sql;
						$this->Conn->Query($insert_sql);
					}

					// delete all links from group except primary
					$temp_handler->DeleteItems($event->Prefix, $event->Special, array_values($group_links));
				}
			}
			else {
				$event->status = kEvent::erFAIL;
				$event->redirect = false;
				$this->Application->SetVar($event->getPrefixSpecial().'_error', 1);
			}
		}

		/**
		 * Stores ids, that were selected in duplicate checker
		 *
		 * @param kEvent $event
		 */
		function OnStoreSelected(&$event)
		{
			$this->StoreSelectedIDs($event);

			$event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial());
		}

		/**
		 * Allows to enhance link after creation
		 *
		 * @param kEvent $event
		 */
		function OnCreate(&$event)
		{
			parent::OnCreate($event);

			if ($event->status == kEvent::erSUCCESS) {
				$object =& $event->getObject();
				/* @var $object kDBItem */

				// replace 0 id in post with actual created id (used in enhancement process)
				$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
				kUtil::array_rename_key($items_info, 0, $object->GetID());
				$this->Application->SetVar($event->getPrefixSpecial(true), $items_info);

				// listing was created -> enhance it right away
				$enhancement_event = new kEvent('ls:OnRequestEnhancement');
				$this->Application->HandleEvent($enhancement_event);
				if (($enhancement_event->status == kEvent::erSUCCESS) && strlen($enhancement_event->redirect)) {
					$event->SetRedirectParam('next_template', $event->redirect);
					$event->redirect = $enhancement_event->redirect;
				}
			}
		}

		/**
		 * Adds free listing option to listing type selection
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterConfigRead(kEvent &$event)
		{
			parent::OnAfterConfigRead($event);

			if (defined('IS_INSTALL') && IS_INSTALL) {
				return ;
			}

			$free_listings = $this->Application->ConfigValue('Link_AllowFreeListings');

			$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
			$virtual_fields['ListingTypeId']['options'] = $free_listings ? Array (0 => 'lu_free_listing') : Array ();

			$language_id = $this->Application->GetVar('m_lang');
			$duplicate_options = array_flip($virtual_fields['DuplicateCheckFields']['options']);
			$duplicate_options['NAME'] = 'l' . $language_id . '_Name';
			$virtual_fields['DuplicateCheckFields']['options'] = array_flip($duplicate_options);
			$default = $virtual_fields['DuplicateCheckFields']['default'];
			$virtual_fields['DuplicateCheckFields']['default'] = str_replace('|Name|', '|l' . $language_id . '_Name|', $default);

			$this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields);

			if (!$this->Application->isAdminUser) {
				// for now only on Front-End
				$this->Application->setUnitOption($event->Prefix, 'PopulateMlFields', true);
			}
		}

		/**
		 * contact us form submitted on link details page
		 *
		 * @param kEvent $event
		 */
		function OnContactFormSubmit(&$event)
		{
			$fields = Array (
				'ContactFormFullName', 'ContactFormEmail', 'ContactFormSubject', 'ContactFormBody', 'ContactFormCaptcha'
			);

			// reset errors var
			$this->Application->SetVar('ContactForm_HasErrors', '');

			// 1. validate form fields
			$required_fields = $this->Application->GetVar('FormRequiredFields');
			foreach ($fields as $field_name) {
				$field_value = trim($this->Application->GetVar($field_name));
				if (in_array($field_name, $required_fields)) {
					// custom captcha validation
					if ($field_name == 'ContactFormCaptcha') {
						if (!strlen($field_value) || ($field_value != $this->Application->RecallVar($event->Prefix . '_captcha_code'))) {
							$this->Application->SetVar('error_'.$field_name, 1);

							$captcha_helper =& $this->Application->recallObject('CaptchaHelper');
							/* @var $captcha_helper kCaptchaHelper */
							$this->Application->StoreVar($event->Prefix . '_captcha_code', $captcha_helper->GenerateCaptchaCode());

							$event->status = kEvent::erFAIL;
							$event->redirect = false;
						}
					}
					// email validation
					elseif (!strlen($field_value) || ($field_name == 'ContactFormEmail' && !preg_match('/'.REGEX_EMAIL_USER.'@'.REGEX_EMAIL_DOMAIN.'/', $field_value))) {
						$this->Application->SetVar('error_'.$field_name, 1);
						$event->status = kEvent::erFAIL;
						$event->redirect = false;
					}

				}
			}

			if ($event->status != kEvent::erSUCCESS) {
				// set errors var
				$this->Application->SetVar('ContactForm_HasErrors', 1);
				return ;
			}

			$object =& $event->getObject(); // get link object
			/* @var $object kDBItem */

			$send_params = Array(
				'from_name' => $this->Application->GetVar('ContactFormFullName'),
				'from_email' => $this->Application->GetVar('ContactFormEmail'),
				'from_subject' => $this->Application->GetVar('ContactFormSubject'),
				'message' => $this->Application->GetVar('ContactFormBody'),
				'to_linkname' => $object->GetField('Name'),
			);

			$email_event =& $this->Application->EmailEventUser('LINK.CONTACTFORM', $object->GetDBField('CreatedById'), $send_params);

			if ($email_event->status == kEvent::erSUCCESS) {
				$event->redirect = $this->Application->GetVar('success_template');

				$redirect_params = Array (
					'opener' => 's',
					'pass' => 'all',
					'thankyou_header' => $this->Application->GetVar('success_label_header'),
					'thankyou_text' => $this->Application->GetVar('success_label_body')
				);
				$event->setRedirectParams($redirect_params);

				$this->Application->EmailEventAdmin('LINK.CONTACTFORM', null, $send_params);
			}
			else {
				$this->Application->SetVar('error_ContactFormEmail', 1);
				$event->status = kEvent::erFAIL;
				$event->redirect = false;
			}
		}

		/**
		 * Makes reciprocal check on link, when it is created
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemCreate(&$event)
		{
			parent::OnBeforeItemCreate($event);

			$this->_checkLink($event);
		}

		/**
		 * Makes reciprocal check on link, when it is updated
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeItemUpdate(&$event)
		{
			parent::OnBeforeItemUpdate($event);

			$this->_checkLink($event);
		}

		/**
		 * Makes reciprocal check on link & saves results
		 *
		 * @param kEvent $event
		 */
		function _checkLink(&$event)
		{
			if (!$this->Application->ConfigValue('ReciprocalLinkChecking')) {
				return ;
			}

			$object =& $event->getObject();
			/* @var $object kDBItem */

			if ($object->GetDBField('Url') != $object->GetOriginalField('Url')) {
				// check only when url was changed

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

				$link_checked = $link_helper->CheckReciprocalURL($object->GetDBField('Url'));

				$object->SetDBField('ReciprocalLinkFound', $link_checked ? LINK_IS_RECIPROCAL : LINK_IS_NOT_RECIPROCAL);

				if (!$link_checked) {
					$this->Application->EmailEventAdmin('LINK.RECIPROCAL.CHECK.FAILED');
				}
			}
		}

		/**
		 * Update links status by their reciprocal status
		 *
		 * @param kEvent $event
		 */
		function OnProcessReciprocalLinks(&$event)
		{
			if (!$this->Application->ConfigValue('ReciprocalLinkChecking')) {
				return ;
			}

			$object =& $event->getObject( Array('skip_autoload' => true) );
			/* @var $object kDBItem */

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

			// 1. verify all links, that were not verified previously
			$sql = 'SELECT ' . $id_field . '
					FROM ' . $table_name . '
					WHERE (ReciprocalLinkFound = 0)';
			$not_checked_links = $this->Conn->GetCol($sql);

			foreach ($not_checked_links as $link_id) {
				$object->Load($link_id);

				$link_checked = $link_helper->CheckReciprocalURL($object->GetDBField('Url'));

				$object->SetDBField('ReciprocalLinkFound', $link_checked ? LINK_IS_RECIPROCAL : LINK_IS_NOT_RECIPROCAL);
				$object->Update();

				if ($link_checked) {
					$object->ApproveChanges();
				}
				else {
					$object->DeclineChanges();
					$this->Application->EmailEventAdmin('LINK.RECIPROCAL.CHECK.FAILED');
				}
			}

			// 2. approve all links, that have succeeded in reciprocal check (during adding/changing on front-end)
			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');

			$sql = 'SELECT ' . $id_field . '
					FROM ' . $table_name . '
					WHERE (ReciprocalLinkFound = ' . LINK_IS_RECIPROCAL . ') AND (Status <> ' . STATUS_ACTIVE . ')';
			$verified_links = $this->Conn->GetCol($sql);

			foreach ($verified_links as $link_id) {
				$object->Load($link_id);
				$object->ApproveChanges();
			}

			// 3. decline all links, that failed in reciprocal check (during adding/changing on front-end)
			$sql = 'SELECT ' . $id_field . '
					FROM ' . $table_name . '
					WHERE (ReciprocalLinkFound = ' . LINK_IS_NOT_RECIPROCAL . ') AND (Status <> ' . STATUS_DISABLED . ')';
			$not_verified_links = $this->Conn->GetCol($sql);

			foreach ($not_verified_links as $link_id) {
				$object->Load($link_id);
				$object->DeclineChanges();
			}
		}

		/**
		 * Allows to load duplicate link by special id
		 *
		 * @param kEvent $event
		 * @return int
		 */
		function getPassedID(&$event)
		{
			$id = parent::getPassedID($event);

			if (($event->Special == 'duplicates') && !is_numeric($id)) {
				$load_keys = unserialize( base64_decode($id) );
				// can't return $load_keys as $id, because "kCatDBItem::GetKeyClause" will ignore them

				foreach ($load_keys as $field => $value) {
					$load_keys[$field] = $field . ' = ' . $this->Conn->qstr($value);
				}

				$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
						FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
						WHERE (' . implode(') AND (', $load_keys) . ')';
				$id = $this->Conn->GetOne($sql);
			}

			return $id;
		}

		/**
		 * Returns events, that require item-based (not just event-name based) permission check
		 *
		 * @return Array
		 */
		function _getMassPermissionEvents()
		{
			$events = parent::_getMassPermissionEvents();
			$events[] = 'OnMerge';

			return $events;
		}

		/**
		 * [HOOK] Allows to add cloned subitem to given prefix
		 *
		 * @param kEvent $event
		 */
		function OnCloneSubItem(&$event)
		{
			parent::OnCloneSubItem($event);

			if ($event->MasterEvent->Prefix == 'rev') {
				$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
				$subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;

				$clones[$subitem_prefix]['ConfigMapping'] = Array (
					'PerPage'				=>	'Perpage_LinkReviews',
					'ShortListPerPage'		=>	'Perpage_LinkReviews_Short',
					'DefaultSorting1Field'	=>	'Link_ReviewsSort',
					'DefaultSorting2Field'	=>	'Link_ReviewsSort2',
					'DefaultSorting1Dir'	=>	'Link_ReviewsOrder',
					'DefaultSorting2Dir'	=>	'Link_ReviewsOrder2',

					'ReviewDelayInterval'	=>	'link_ReviewDelay_Interval',
					'ReviewDelayValue'		=>	'link_ReviewDelay_Value',
				);

				$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
			}
		}

		/**
		 * Occurs before original item of item in pending editing got deleted (for hooking only)
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnBeforeDeleteOriginal(kEvent &$event)
		{
			parent::OnBeforeDeleteOriginal($event);

			$object =& $event->getObject();
			/* @var $object kDBItem */

			$link_id = $event->getEventParam('original_id');
			$new_resource_id = $object->GetDBField('ResourceId');

			$sql = 'SELECT ResourceId
					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
					WHERE ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '=' . $link_id;
			$old_resource_id = $this->Conn->GetOne($sql);

			$this->Application->SetVar('original_resource_id', $old_resource_id);
			$this->changeResourceId('rel', 'TargetId', $old_resource_id, $new_resource_id);
		}

		/**
		 * Occurs after original item of item in pending editing got deleted
		 *
		 * @param kEvent $event
		 * @return void
		 * @access protected
		 */
		protected function OnAfterDeleteOriginal(kEvent &$event)
		{
			parent::OnAfterDeleteOriginal($event);

			$object =& $event->getObject();
			/* @var $object kDBItem */

			$old_resource_id = $this->Application->GetVar('original_resource_id');
			$new_resource_id = $object->GetDBField('ResourceId');

			$this->changeResourceId('ls', 'ItemResourceId', $old_resource_id, $new_resource_id);
		}

		/**
		 * Changes item resource id in one field
		 *
		 * @param string $prefix
		 * @param string $field
		 * @param string $old_resource_id
		 * @param string $new_resource_id
		 * @return void
		 * @access protected
		 */
		protected function changeResourceId($prefix, $field, $old_resource_id, $new_resource_id)
		{
			$fields_hash = Array ($field => $new_resource_id);
			$table_name = $this->Application->getUnitOption($prefix, 'TableName');

			$this->Conn->doUpdate($fields_hash, $table_name, $field . ' = ' . $old_resource_id);
		}
	}