Index: branches/5.2.x/units/links/links_event_handler.php
===================================================================
--- branches/5.2.x/units/links/links_event_handler.php	(revision 14896)
+++ branches/5.2.x/units/links/links_event_handler.php	(revision 14897)
@@ -1,528 +1,597 @@
 <?php
 /**
 * @version	$Id$
 * @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 ( !$this->Application->isAdminUser ) {
+				$object->addFilter('expire_filter', '(Expire > ' . adodb_mktime() . ' OR Expire IS NULL)');
 			}
 
-			if (substr($event->Special, 0, 10) == 'duplicates') {
+			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() );
+				$grouping = $link_helper->getGrouping($event->getPrefixSpecial());
+
 				switch ($event->Special) {
 					case 'duplicates':
 						foreach ($grouping as $group_field) {
-							$object->AddGroupByField($object->TableName.'.'.$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');
+						$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)) );
+							$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');
-			}
 
+				$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() );
+			$grouping = $link_helper->getGrouping($event->getPrefixSpecial());
 
 			$ids = $this->StoreSelectedIDs($event);
-			if (!$ids) {
-				return ;
+			if ( !$ids ) {
+				return;
 			}
 
 			// check, that user has not selected multiple links from same group
-			$primary_links = Array();
+			$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).')';
+					FROM ' . $table_name . '
+					WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ')';
 			$links = $this->Conn->Query($sql, $id_field);
 
 			$groping_error = false;
-			foreach ($links as $link_id => $link_data) {
+
+			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 .= '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])) {
+				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');
+			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';
+				$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();
+					$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];
+						$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) {
+					$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 .= '(' . $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;
+						$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);
+		}
 	}
\ No newline at end of file