<?php

/**
 * <pre>
 * Invision Power Services
 * Cross-database relational field
 * Last Updated: $Date: 2009-08-23 21:54:28 -0400 (Sun, 23 Aug 2009) $
 * </pre>
 *
 * @author 		$Author: bfarber $
 * @copyright	(c) 2001 - 2009 Invision Power Services, Inc.
 * @license		http://www.invisionpower.com/community/board/license.html
 * @package		IP.Content
 * @link		http://www.invisionpower.com
 * @since		18 Nov 2009
 * @version		$Revision: 67 $
 */

if ( ! defined( 'IN_ACP' ) )
{
	print "<h1>Incorrect access</h1>You cannot access this file directly.";
	exit();
}

class fields_relational
{
	/**#@+
	 * Registry objects
	 *
	 * @access	protected
	 * @var		object
	 */	
	protected $registry;
	protected $DB;
	protected $settings;
	protected $request;
	protected $lang;
	protected $member;
	protected $memberData;
	protected $caches;
	protected $cache;
	/**#@-*/
	
	/**
	 * Error string stored from last process
	 *
	 * @access	protected
	 * @var		string
	 */
	protected $error		= '';

	/**
	 * Constructor
	 *
	 * @access	public
	 * @param	object		Registry
	 * @return	void
	 */
	public function __construct( ipsRegistry $registry )
	{
		/* Make object */
		$this->registry		= $registry;
		$this->DB			= $this->registry->DB();
		$this->settings		=& $this->registry->fetchSettings();
		$this->request		=& $this->registry->fetchRequest();
		$this->lang			= $this->registry->getClass('class_localization');
		$this->member		= $this->registry->member();
		$this->memberData	=& $this->registry->member()->fetchMemberData();
		$this->cache		= $this->registry->cache();
		$this->caches		=& $this->registry->cache()->fetchCaches();
	}

	/**
	 * Return default field types
	 *
	 * @access	public
	 * @param	string		Table name
	 * @return	bool
	 */
	public function getTypes()
	{
		return array(
					array( 'relational', $this->lang->words['field_type__relational'] ),
					);
	}
	
	/**
	 * Get an ACP field
	 *
	 * @access	public
	 * @param	array		Field data
	 * @param	mixed		Default value
	 * @return	string		HTML field
	 */
	public function getAcpField( $field, $default='' )
	{
		$id			= $field['field_id'];
		$type		= $field['field_type'];
		$options	= explode( ',', $field['field_extra'] );		// Database,Field,Type
		$_defaults	= $options[2] == 'dropdown' ? array() : explode( ',', IPSText::cleanPermString( $default ) );
		
		if( $type == 'relational' )
		{
			$return	= '';
			
			$database	= intval(trim($options[0]));
			$field		= intval(trim($options[1]));
			$_options	= array();

			$this->DB->build( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'order' => $this->caches['ccs_databases'][ $database ]['database_field_sort'] . ' ' . $this->caches['ccs_databases'][ $database ]['database_field_direction'] ) );
			$this->DB->execute();
			
			while( $r = $this->DB->fetch() )
			{
				$_options[]	= array( $r['primary_id_field'], $r[ $this->caches['ccs_databases'][ $database ]['database_field_title'] ] );
			}
			
			if( $options[2] == 'dropdown' )
			{
				array_unshift( $_options, array( 0, $this->lang->words['select_one_rel'] ) );
				
				return $this->registry->output->formDropdown( 'field_' . $id, $_options, $default );
			}
			else
			{
				return $this->registry->output->formMultiDropdown( 'field_' . $id . '[]', $_options, $_defaults );
			}
		}
		
		return '';
	}
	
	/**
	 * Get a public field
	 *
	 * @access	public
	 * @param	array		Field data
	 * @param	mixed		Default value
	 * @return	string		HTML field
	 */
	public function getPublicField( $field, $default='' )
	{
		$id			= $field['field_id'];
		$type		= $field['field_type'];
		$options	= explode( ',', $field['field_extra'] );		// Database,Field,Type
		$_defaults	= $options[2] == 'dropdown' ? array() : explode( ',', IPSText::cleanPermString( $default ) );
		
		if( $type == 'relational' )
		{
			$database	= intval(trim($options[0]));
			$field		= intval(trim($options[1]));
			$_options	= array();

			$this->DB->build( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'where' => 'record_approved=1', 'order' => $this->caches['ccs_databases'][ $database ]['database_field_sort'] . ' ' . $this->caches['ccs_databases'][ $database ]['database_field_direction'] ) );
			$this->DB->execute();
			
			while( $r = $this->DB->fetch() )
			{
				$_options[]	= array( $r['primary_id_field'], $r[ $this->caches['ccs_databases'][ $database ]['database_field_title'] ] );
			}
			
			if( $options[2] == 'dropdown' )
			{
				$_html	= "<select name='field_{$id}' id='field_{$id}'><option value='0'>{$this->lang->words['select_one_rel']}</option>";
			}
			else
			{
				$_html	= "<select name='field_{$id}[]' id='field_{$id}' multiple='multiple' size='5'>";
			}
			
			foreach( $_options as $_items )
			{
				$_selected	= '';
				
				if( $options[2] == 'dropdown' )
				{
					if( $_items[0] == $default )
					{
						$_selected	= "selected='selected'";
					}
				}
				else
				{
					if( in_array( $_items[0], $_defaults ) )
					{
						$_selected	= "selected='selected'";
					}
				}
				
				$_items[1]	= strip_tags($_items[1]);
				
				$_html	.= "<option value='{$_items[0]}'{$_selected}>{$_items[1]}</option>";
			}
			
			$_html	.= "</select>";
			
			return $_html;
		}
		
		return '';
	}
	
	/**
	 * Get error, if set
	 *
	 * @access	public
	 * @return	mixed		Error lang key, or false
	 */
	public function getError()
	{
		return $this->error ? $this->error : false;
	}
	
	/**
	 * Process the input and return normalized value to store
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @return	string		Data to store in DB
	 */
	public function processInput( $field )
	{
		$value	= '';
		
		if( $field['field_type'] == 'relational' )
		{
			//-----------------------------------------
			// Validate value
			//-----------------------------------------
			
			$options	= explode( ',', $field['field_extra'] );		// Database,Field,Type
			$database	= intval(trim($options[0]));
			$_field		= intval(trim($options[1]));
			
			if( $options[2] == 'dropdown' )
			{
				$value	= intval( $this->request['field_' . $field['field_id'] ] );
			}
			else
			{
				$value	= ',' . implode( ',', IPSLib::cleanIntArray( $this->request['field_' . $field['field_id'] ] ) ) . ',';
			}

			if( $field['field_required'] AND !$value )
			{
				$this->error	= sprintf( $this->lang->words['dbfield_required'], $field['field_name'] );
			}

			if( $value )
			{
				if( $options[2] == 'dropdown' )
				{
					$record	= $this->DB->buildAndFetch( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'where' => 'primary_id_field=' . $value ) );
					
					if( !$record['primary_id_field'] )
					{
						$this->error	= sprintf( $this->lang->words['dbfield_required'], $field['field_name'] );
					}
				}
				else
				{
					if( IPSText::cleanPermString( $value ) )
					{
						$this->DB->build( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'where' => 'primary_id_field IN(' . IPSText::cleanPermString( $value ) . ')' ) );
						$this->DB->execute();
						
						while( $record = $this->DB->fetch() )
						{
							if( !$record['primary_id_field'] )
							{
								$this->error	= sprintf( $this->lang->words['dbfield_required'], $field['field_name'] );
							}
						}
					}
				}
			}
			
			return $value;
		}

		return '';
	}
	
	/**
	 * Process input after data has been saved to database
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @return	bool		Error or not
	 */
	public function postProcessInput( $field, $record_id=0 )
	{
		return false;
	}
	
	/**
	 * Record deletion callback
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @param	array		Record data
	 * @return	bool		Handled successfully or not
	 */
	public function postProcessDelete( $field, $record )
	{
		return false;
	}
	
	/**
	 * Process the field and return a display value
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @param	array		Record data
	 * @param	int			Number of characters to truncate at (0 means no truncating)
	 * @return	string		Data to display
	 */
	public function getFieldValue( $field, $record=array(), $truncate=0 )
	{
		$options	= explode( ',', $field['field_extra'] );		// Database,Field,Type
		$database	= intval(trim($options[0]));
		$_field		= intval(trim($options[1]));

		if( $record['field_' . $field['field_id'] ] )
		{
			if( $options[2] == 'dropdown' )
			{
				if( $this->caches['records'][ $record['field_' . $field['field_id'] ] ] )
				{
					return $this->caches['records'][ $record['field_' . $field['field_id'] ] ][ $this->caches['ccs_databases'][ $database ]['database_field_title'] ];
				}
				
				if( !intval($record['field_' . $field['field_id'] ]) )
				{
					return '';
				}

				$_record	= $this->DB->buildAndFetch( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'where' => 'primary_id_field=' . intval($record['field_' . $field['field_id'] ]) ) );
				
				$this->caches['records'][ $record['field_' . $field['field_id'] ] ]	= $_record;
				
				return $_record[ $this->caches['ccs_databases'][ $database ]['database_field_title'] ];
			}
			else
			{
				$_value		= IPSText::cleanPermString( $record['field_' . $field['field_id'] ] ) ? explode( ',', IPSText::cleanPermString( $record['field_' . $field['field_id'] ] ) ) : array();
				$_return	= array();
				$_still		= array();
				
				foreach( $_value as $_val )
				{
					if( $this->caches['records'][ $_val ] )
					{
						$_return[]	= $this->caches['records'][ $_val ][ $this->caches['ccs_databases'][ $database ]['database_field_title'] ];
					}
					else
					{
						$_still[]	= $_val;
					}
				}
				
				if( count($_still) )
				{
					$this->DB->build( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'where' => 'primary_id_field IN(' . implode( ',', $_still ) . ')' ) );
					$this->DB->execute();
					
					while( $_r = $this->DB->fetch() )
					{
						$this->caches['records'][ $record['field_' . $field['field_id'] ] ]	= $_r;
						
						$_return[]	= $_r[ $this->caches['ccs_databases'][ $database ]['database_field_title'] ];
					}
				}
				
				//-----------------------------------------
				// Sort
				//-----------------------------------------
				
				natcasesort($_return);
				
				return implode( ', ', $_return );
			}
		}
			
		return '';
	}
	
	/**
	 * Produce where clause based on search string
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @param	string		Supplid value
	 * @return	string		Data to display
	 */
	public function getSearchWhere( $field, $search='' )
	{
		if( !$search )
		{
			return '';
		}

		if( $field['field_type'] == 'relational' )
		{
			if( $search )
			{
				$options	= explode( ',', $field['field_extra'] );		// Database,Field,Type
				$database	= intval(trim($options[0]));
				$_field		= intval(trim($options[1]));

				$this->DB->build( array( 'select' => '*', 'from' => $this->caches['ccs_databases'][ $database ]['database_database'], 'where' => 'record_approved=1 AND ' . $_field . " LIKE '%{$search}%'" ) );
				$this->DB->execute();
				
				while( $r = $this->DB->fetch() )
				{
					$potentialIds[]	= $r['primary_id_field'];
				}
			
				if( count($potentialIds) )
				{
					if( $options[2] == 'dropdown' )
					{
						return 'field_' . $field['field_id'] . " IN (" . implode( ',', $potentialIds ) . ")";
					}
					else
					{
						$_return	= array();
						
						foreach( $potentialIds as $_id )
						{
							$_return[]	= 'field_' . $field['field_id'] . " LIKE '%," . $_id . ",%'";
						}
						
						return '(' . implode( ' OR ', $_return ) . ')';
					}
				}
			}
		}

		return '';
	}
	
	/**
	 * Get a true difference report
	 *
	 * @access	public
	 * @param	string		Current text
	 * @param	string		Previous text
	 * @param	string		HTML to display
	 */
	public function getDifferenceReport( $current, $previous )
	{
		if( !is_object($this->differences) )
		{
			//-----------------------------------------
			// Get Diff library
			//-----------------------------------------
			
			require_once( IPS_KERNEL_PATH . 'classDifference.php' );
			$this->differences         = new classDifference();
			$this->differences->method = 'PHP';
		}
		
		$result	= $this->differences->getDifferences( IPSText::br2nl( $previous ), IPSText::br2nl( $current ) );
		
		if( $this->differences->diff_found )
		{
			$result	= str_replace( "\n", "<br>", $result );
			$result	= str_replace( "&gt;&lt;", "&gt;\n&lt;", $result );
			$result	= preg_replace( "#(?<!(\<del|\<ins)) {1}(?!:style)#i", "&nbsp;", $result );
			$result	= str_replace( "\t", "&nbsp; &nbsp; ", $result );
			//$result	= str_replace( "&lt;br&nbsp;/&gt;", "<br />", $result );
		}
		else
		{
			$result	= $current;
		}
		
		return $result;
	}
	
	/**
	 * Compare two versions of a particular field
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @param	string		Current data in the field
	 * @param	string		Previous data in the field
	 * @return	string		HTML revisions differences
	 */
	public function compareRevision( $field, $current, $previous )
	{
		if( $field['field_type'] == 'relational' )
		{
			$key	= 'field_' . $field['field_id'];
			return $current == $previous ? $this->getFieldValue( $field, array( $key => $current ) ) : "<ins>" . $this->getFieldValue( $field, array( $key => $current ) ) . "</ins> <del>" . $this->getFieldValue( $field, array( $key => $previous ) ) . "</del>";
		}

		return '';
	}
	
	/**
	 * Modify field data to be saved via ACP
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @return	array 		Field data
	 */
	public function preSaveField( $field )
	{
		if( $field['field_type'] == 'relational' )
		{
			$field['field_extra']	= intval($this->request['field_database']) . ',' . intval($this->request['field_fields']) . ',' . $this->request['field_rel_type'];
		}

		return $field;
	}
}