<?php

/**
 * <pre>
 * Invision Power Services
 * IP.CCS fields abstraction layer for database management
 * 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		2nd Sept 2009
 * @version		$Revision: 67 $
 */

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

class ccs_database_fields
{
	/**#@+
	 * Registry objects
	 *
	 * @access	protected
	 * @var		object
	 */	
	protected $registry;
	protected $DB;
	protected $settings;
	protected $request;
	protected $lang;
	protected $member;
	protected $memberData;
	protected $caches;
	protected $cache;
	/**#@-*/
	
	/**
	 * Default field types
	 *
	 * @access	protected
	 * @var		array 		Field types
	 */
	protected $fieldTypes	= array();

	/**
	 * Default field validators
	 *
	 * @access	protected
	 * @var		array 		Field validators
	 */
	protected $fieldValidators	= array();
	
	/**
	 * Field handlers
	 *
	 * @access	protected
	 * @var		array 		Field type to object mapping
	 */
	protected $handlers		= array();
	
	/**
	 * 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();
	}
	
	/**
	 * Retrieve available field types.  Abstracted for plugin functionality.
	 *
	 * @access	public
	 * @return	array
	 */
	public function getTypes()
	{
		if( count($this->fieldTypes) )
		{
			return $this->fieldTypes;
		}
		else
		{
			$_fields	= array();
			
			try
			{
				foreach( new DirectoryIterator( IPSLib::getAppDir('ccs') . '/sources/databases/fields/' ) as $dir )
				{
					if( ! $dir->isDot() && $dir->isFile() )
					{
						//-----------------------------------------
						// PHP 5.1 fix
						// @see http://community.invisionpower.com/tracker/issue-19009-fatal-error-call-to-undefined-method-directoryiteratorgetbasename/
						//-----------------------------------------
						
						if( method_exists( $dir, 'getBasename' ) )
						{
							$className	= 'fields_' . $dir->getBasename('.php');
						}
						else
						{
							$filename	= $dir->getFilename();
							$filename	= substr( $filename, 0, strrpos( $filename, '.' ) );
							
							$className	= 'fields_' . $filename;
						}

						if( strpos( $className, '.' ) === false )
						{
							require_once( $dir->getPathname() );
							
							if( class_exists($className) )
							{
								$fieldClass	= new $className( $this->registry );
								$_fields	= array_merge( $_fields, $fieldClass->getTypes() );
							}
						}
					}
				}
			} catch ( Exception $e ) {}
			
			$this->fieldTypes	= $_fields;
			
			return $this->fieldTypes;
		}
	}
	
	/**
	 * Retrieve all available default validators.
	 *
	 * @access	public
	 * @return	array
	 */
	public function getValidators()
	{
		if( count($this->fieldValidators) )
		{
			return $this->fieldValidators;
		}
		else
		{
			try
			{
				foreach( new DirectoryIterator( IPSLib::getAppDir('ccs') . '/sources/databases/validators/' ) as $dir )
				{
					if( ! $dir->isDot() && $dir->isFile() )
					{
						$CONFIG	= array();
						
						require_once( $dir->getPathname() );
						
						if( is_array($CONFIG) AND count($CONFIG) )
						{
							$this->fieldValidators[ $CONFIG['key'] ]	= $CONFIG;
						}
					}
				}
			} catch ( Exception $e ) {}

			return $this->fieldValidators;
		}
	}
	
	/**
	 * Get the object references for the field types
	 *
	 * @access	public
	 * @return	bool
	 */
	public function getHandlers()
	{
		if( count($this->handlers) )
		{
			return true;
		}
		else
		{
			try
			{
				foreach( new DirectoryIterator( IPSLib::getAppDir('ccs') . '/sources/databases/fields/' ) as $dir )
				{
					$_fields	= array();
					$_types		= array();
					
					if( ! $dir->isDot() && $dir->isFile() )
					{
						if( method_exists( $dir, 'getBasename' ) )
						{
							$className	= 'fields_' . $dir->getBasename('.php');
						}
						else
						{
							$filename	= $dir->getFilename();
							$filename	= substr( $filename, 0, strrpos( $filename, '.' ) );
							
							$className	= 'fields_' . $filename;
						}

						if( strpos( $className, '.' ) === false )
						{
							require_once( $dir->getPathname() );
							
							if( class_exists($className) )
							{
								$fieldClass	= new $className( $this->registry );
								$_fields	= $fieldClass->getTypes();
								$_types		= array_merge( $_types, $_fields );
								
								if( is_array($_fields) AND count($_fields) )
								{
									foreach( $_fields as $_field )
									{
										$this->handlers[ $_field[0] ]	= $fieldClass;
									}
								}
							}
						}
					}
				}
			} catch ( Exception $e ) {}

			$this->fieldTypes	= $_types;
			
			return true;
		}
	}
	
	/**
	 * Get an ACP field
	 *
	 * @access	public
	 * @param	array		Field data
	 * @param	mixed		Default value
	 * @return	string		HTML field
	 */
	public function getAcpField( $field, $default='' )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			return $this->handlers[ $field['field_type'] ]->getAcpField( $field, $default );
		}
		else
		{
			return '';
		}
	}
	
	/**
	 * Get an ACP field
	 *
	 * @access	public
	 * @param	array		Field data
	 * @param	mixed		Default value
	 * @return	string		HTML field
	 */
	public function getPublicField( $field, $default='' )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			return $this->handlers[ $field['field_type'] ]->getPublicField( $field, $default );
		}
		else
		{
			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 )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			$_return	= $this->handlers[ $field['field_type'] ]->processInput( $field );
			
			if( $error = $this->handlers[ $field['field_type'] ]->getError() )
			{
				$this->error	= $error;
			}
			
			//-----------------------------------------
			// Validate?
			//-----------------------------------------
			
			if( !$this->error AND $field['field_validator'] )
			{
				$validators	= $this->getValidators();
				
				$_validator	= explode( ';_;', $field['field_validator'] );
				
				if( $_validator[0] == 'custom' AND $_validator[1] )
				{
					if( !preg_match( $_validator[1], $_return ) )
					{
						$this->error	= $_validator[2] ? $_validator[2] : sprintf( $thisValidator['error'], $field['field_name'] );
					}
				}
				else
				{
					$thisValidator	= $validators[ $_validator[0] ];
					
					if( $thisValidator['regex'] )
					{
						if( !preg_match( $thisValidator['regex'], $_return ) )
						{
							$this->error	= sprintf( $thisValidator['error'], $field['field_name'] );
						}
					}
					else if( $thisValidator['callback'] )
					{
						if( !call_user_func( $thisValidator['callback'], $_return ) )
						{
							$this->error	= sprintf( $thisValidator['error'], $field['field_name'] );
						}
					}
				}
			}

			return $_return;
		}
		else
		{
			return '';
		}
	}
	
	/**
	 * Process a field after record has been saved - useful if you need ID available
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @param	int			Record ID
	 * @return	bool		Saved successfully or not
	 */
	public function postProcessInput( $field, $record_id )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			return $this->handlers[ $field['field_type'] ]->postProcessInput( $field, $record_id );
		}
		else
		{
			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 )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			return $this->handlers[ $field['field_type'] ]->postProcessDelete( $field, $record );
		}
		else
		{
			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 )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			return $this->handlers[ $field['field_type'] ]->getFieldValue( $field, $record, $truncate );
		}
		else
		{
			return '';
		}
	}
	
	/**
	 * Process the field and return a display value when previewing
	 *
	 * @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 getFieldValuePreview( $field, $record=array(), $truncate=0 )
	{
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			if( method_exists( $this->handlers[ $field['field_type'] ], 'getFieldValuePreview' ) )
			{
				return $this->handlers[ $field['field_type'] ]->getFieldValuePreview( $field, $record, $truncate );
			}
			else
			{
				return $this->handlers[ $field['field_type'] ]->getFieldValue( $field, $record, $truncate );
			}
		}
		else
		{
			return '';
		}
	}
	
	/**
	 * Get search 'where' clause
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @param	string		Search string
	 * @return	string		Where clause for query
	 */
	public function getSearchWhere( $fields, $search='' )
	{
		$search	= trim($search);
		
		if( !$search )
		{
			return '';
		}
		
		$_where	= array();
		
		foreach( $fields as $_field )
		{
			$_thisSearch	= $this->handlers[ $_field['field_type'] ]->getSearchWhere( $_field, $search );
			
			if( $_thisSearch )
			{
				$_where[]	= $_thisSearch;
			}
		}

		if( count($_where) )
		{
			return implode( ' OR ', $_where );
		}
		else
		{
			return '';
		}
	}
	
	/**
	 * 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( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			$_return	= $this->handlers[ $field['field_type'] ]->compareRevision( $field, $current, $previous );
			
			if( $error = $this->handlers[ $field['field_type'] ]->getError() )
			{
				$this->error	= $error;
			}
			
			return $_return;
		}
		else
		{
			return '';
		}
	}
	
	/**
	 * Allow field handlers to modify data to be saved
	 *
	 * @access	public
	 * @param	array 		Field data
	 * @return	array		Field data
	 */
	public function preSaveField( $field )
	{
		$this->getHandlers();
		
		if( array_key_exists( $field['field_type'], $this->handlers ) )
		{
			if( method_exists( $this->handlers[ $field['field_type'] ], 'preSaveField' ) )
			{
				return $this->handlers[ $field['field_type'] ]->preSaveField( $field );
			}
		}
		
		return $field;
	}
}