<?php
/**
 * This file implements the Post Type class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );

/**
 * ItemType Class
 *
 * @package evocore
 */
class ItemType extends DataObject
{
	var $name;
	var $description;
	var $usage;
	var $template_excerpt;
	var $template_normal;
	var $template_full;
	var $template_name;
	var $schema = '';
	var $add_aggregate_rating = 1;
	var $back_instruction = 0;
	var $instruction = '';
	var $text_template;
	var $use_short_title = 'never';
	var $use_title = 'required';
	var $use_url = 'optional';
	var $podcast = 0;
	var $use_parent = 'never';
	var $use_text = 'optional';
	var $allow_html = 1;
	var $allow_breaks = 1;
	var $allow_attachments = 1;
	var $use_excerpt = 'optional';
	var $use_title_tag = 'optional';
	var $use_meta_desc = 'optional';
	var $use_meta_keywds = 'optional';
	var $use_tags = 'optional';
	var $allow_featured = 1;
	var $allow_switchable = 1;
	var $use_country = 'never';
	var $use_region = 'never';
	var $use_sub_region = 'never';
	var $use_city = 'never';
	var $use_coordinates = 'never';
	var $use_comments = 1;
	var $comment_form_msg = '';
	var $allow_comment_form_msg = 0;
	var $allow_closing_comments = 1;
	var $allow_disabling_comments = 0;
	var $use_comment_expiration = 'optional';
	var $perm_level = 'standard';
	var $evobar_link_text = NULL;
	var $skin_btn_text = NULL;
	var $short_title_maxlen = 30;
	var $title_maxlen = 100;
	var $front_order_title       = 10;
	var $front_order_short_title = NULL;
	var $front_order_instruction = NULL;
	var $front_order_attachments = 30;
	var $front_order_text        = 80;
	var $front_order_tags        = NULL;
	var $front_order_excerpt     = NULL;
	var $front_order_url         = NULL;
	var $front_order_location    = NULL;
	var $front_order_workflow    = 50;

	/**
	 * Custom fields
	 *
	 * @var array
	 */
	var $custom_fields = NULL;

	/**
	 * What fields should be updated/inserted/deleted
	 *
	 * @var array
	 */
	var $update_custom_fields = NULL;
	var $insert_custom_fields = NULL;
	var $delete_custom_fields = NULL;


	/**
	 * Constructor
	 *
	 *
	 * @param table Database row
	 */
	function __construct( $db_row = NULL )
	{
		// Call parent constructor:
		parent::__construct( 'T_items__type', 'ityp_', 'ityp_ID' );

		// Allow inseting specific IDs
		$this->allow_ID_insert = true;

		if( $db_row != NULL )
		{
			$this->ID   = $db_row->ityp_ID;
			$this->name = $db_row->ityp_name;
			$this->description = $db_row->ityp_description;
			$this->usage = $db_row->ityp_usage;
			$this->template_excerpt = isset( $db_row->ityp_template_excerpt ) ? $db_row->ityp_template_excerpt : NULL;
			$this->template_normal = isset( $db_row->ityp_template_normal ) ? $db_row->ityp_template_normal : NULL;
			$this->template_full = isset( $db_row->ityp_template_full ) ? $db_row->ityp_template_full : NULL;
			$this->template_name = $db_row->ityp_template_name;
			$this->schema = isset( $db_row->ityp_schema ) ? $db_row->ityp_schema : $this->schema;
			$this->add_aggregate_rating = isset( $db_row->ityp_add_aggregate_rating ) ? $db_row->ityp_add_aggregate_rating : $this->add_aggregate_rating;
			$this->back_instruction = $db_row->ityp_back_instruction;
			$this->instruction = $db_row->ityp_instruction;
			$this->text_template = isset( $db_row->ityp_text_template ) ? $db_row->ityp_text_template : $this->text_template;
			$this->use_short_title = isset( $db_row->ityp_use_short_title ) ? $db_row->ityp_use_short_title : $this->use_short_title;
			$this->use_title = $db_row->ityp_use_title;
			$this->use_url = $db_row->ityp_use_url;
			$this->podcast = $db_row->ityp_podcast;
			$this->use_parent = $db_row->ityp_use_parent;
			$this->use_text = $db_row->ityp_use_text;
			$this->allow_html = $db_row->ityp_allow_html;
			$this->allow_breaks = $db_row->ityp_allow_breaks;
			$this->allow_attachments = $db_row->ityp_allow_attachments;
			$this->use_excerpt = $db_row->ityp_use_excerpt;
			$this->use_title_tag = $db_row->ityp_use_title_tag;
			$this->use_meta_desc = $db_row->ityp_use_meta_desc;
			$this->use_meta_keywds = $db_row->ityp_use_meta_keywds;
			$this->use_tags = $db_row->ityp_use_tags;
			$this->allow_featured = $db_row->ityp_allow_featured;
			$this->allow_switchable = $db_row->ityp_allow_switchable;
			$this->use_country = $db_row->ityp_use_country;
			$this->use_region = $db_row->ityp_use_region;
			$this->use_sub_region = $db_row->ityp_use_sub_region;
			$this->use_city = $db_row->ityp_use_city;
			$this->use_coordinates = $db_row->ityp_use_coordinates;
			$this->use_comments = $db_row->ityp_use_comments;
			$this->comment_form_msg = $db_row->ityp_comment_form_msg;
			$this->allow_comment_form_msg = $db_row->ityp_allow_comment_form_msg;
			$this->allow_closing_comments = $db_row->ityp_allow_closing_comments;
			$this->allow_disabling_comments = $db_row->ityp_allow_disabling_comments;
			$this->use_comment_expiration = $db_row->ityp_use_comment_expiration;
			$this->perm_level = $db_row->ityp_perm_level;
			$this->evobar_link_text = isset( $db_row->ityp_evobar_link_text ) ? $db_row->ityp_evobar_link_text : $this->evobar_link_text;
			$this->skin_btn_text = isset( $db_row->ityp_skin_btn_text ) ? $db_row->ityp_skin_btn_text : $this->skin_btn_text;
			$this->short_title_maxlen = isset( $db_row->ityp_short_title_maxlen ) ? $db_row->ityp_short_title_maxlen : $this->short_title_maxlen;
			$this->title_maxlen = isset( $db_row->ityp_title_maxlen ) ? $db_row->ityp_title_maxlen : $this->title_maxlen;
			$this->front_order_title = isset( $db_row->ityp_front_order_title ) ? $db_row->ityp_front_order_title : NULL;
			$this->front_order_short_title = isset( $db_row->ityp_front_order_short_title ) ? $db_row->ityp_front_order_short_title : NULL;
			$this->front_order_instruction = isset( $db_row->ityp_front_order_instruction ) ? $db_row->ityp_front_order_instruction : NULL;
			$this->front_order_attachments = isset( $db_row->ityp_front_order_attachments ) ? $db_row->ityp_front_order_attachments : NULL;
			$this->front_order_workflow = isset( $db_row->ityp_front_order_workflow ) ? $db_row->ityp_front_order_workflow : NULL;
			$this->front_order_text = isset( $db_row->ityp_front_order_text ) ? $db_row->ityp_front_order_text : NULL;
			$this->front_order_tags = isset( $db_row->ityp_front_order_tags ) ? $db_row->ityp_front_order_tags : NULL;
			$this->front_order_excerpt = isset( $db_row->ityp_front_order_excerpt ) ? $db_row->ityp_front_order_excerpt : NULL;
			$this->front_order_url = isset( $db_row->ityp_front_order_url ) ? $db_row->ityp_front_order_url : NULL;
			$this->front_order_location = isset( $db_row->ityp_front_order_location ) ? $db_row->ityp_front_order_location : NULL;
		}
	}


	/**
	 * Get delete restriction settings
	 *
	 * @return array
	 */
	static function get_delete_restrictions()
	{
		return array(
				array( 'table'=>'T_items__item', 'fk'=>'post_ityp_ID', 'msg'=>T_('%d related items') ), // "Lignes de visit reports"
				array( 'table'=>'T_categories', 'fk'=>'cat_ityp_ID', 'msg'=>T_('%d related categories') ),
			);
	}


	/**
	 * Get delete cascade settings
	 *
	 * @return array
	 */
	static function get_delete_cascades()
	{
		return array(
				array( 'table' => 'T_items__type_coll', 'fk' => 'itc_ityp_ID', 'msg' => T_('%d Post type associations with collections') ),
				array( 'table' => 'T_items__type_custom_field', 'fk' => 'itcf_ityp_ID', 'msg' => T_('%d Custom field definitions') ),
				array( 'table' => 'T_items__status_type', 'fk' => 'its_ityp_ID', 'msg' => T_('%d Item status associations') )
			);
	}


	/**
	 * Load data from Request form fields.
	 *
	 * @return boolean true if loaded data seems valid.
	 */
	function load_from_Request()
	{
		global $admin_url, $current_User;

		// Name
		param_string_not_empty( 'ityp_name', T_('Please enter a name.') );
		$this->set_from_Request( 'name' );

		// Description
		param( 'ityp_description', 'text' );
		$this->set_from_Request( 'description', NULL, true );

		// Permission level
		param( 'ityp_perm_level', 'string' );
		$this->set_from_Request( 'perm_level' );

		// Usage
		param( 'ityp_usage', 'string' );
		$this->set_from_Request( 'usage', NULL, true );

		// Template for Excerpt display
		param( 'ityp_template_excerpt', 'string' );
		$this->set_from_Request( 'template_excerpt', NULL, true );

		// Template for Teaser display
		param( 'ityp_template_normal', 'string' );
		$this->set_from_Request( 'template_normal', NULL, true );

		// Template for Full content display
		param( 'ityp_template_full', 'string' );
		$this->set_from_Request( 'template_full', NULL, true );

		// PHP Template name
		param( 'ityp_template_name', 'string' );
		$this->set_from_Request( 'template_name', NULL, true );

		// Schema
		param( 'ityp_schema', 'string' );
		$this->set_from_Request( 'schema', NULL, true );

		// Add aggregateRating
		param( 'ityp_add_aggregate_rating', 'integer', 0 );
		$this->set_from_Request( 'add_aggregate_rating' );

		// New item link in evobar text
		param( 'ityp_evobar_link_text', 'string' );
		$this->set_from_Request( 'evobar_link_text' );

		// New item button in skin text
		param( 'ityp_skin_btn_text', 'string' );
		$this->set_from_Request( 'skin_btn_text' );

		// Show instruction in back-office
		param( 'ityp_back_instruction', 'integer' );
		$this->set_from_Request( 'back_instruction' );

		// Instructions:
		param( 'ityp_instruction', 'html', NULL );
		param_check_html( 'ityp_instruction', T_('Invalid instruction format.').' '.sprintf( T_('You can loosen this restriction in the <a %s>Group settings</a>.'), 'href='.$admin_url.'?ctrl=groups&amp;action=edit&amp;grp_ID='.$current_User->grp_ID ), '#', 'posting' );
		$this->set_from_Request( 'instruction', NULL, true );

		// Front-Office Order (Instructions)
		param( 'ityp_front_order_instruction', 'integer', NULL );
		$this->set_from_Request( 'front_order_instruction' );

		// Template:
		param( 'ityp_text_template', 'html', NULL );
		param_check_html( 'ityp_text_template', T_('Invalid template.') );
		$this->set_from_Request( 'text_template', NULL, true );

		// Use short title
		param( 'ityp_use_short_title', 'string' );
		$this->set_from_Request( 'use_short_title' );

		// Short title max length
		param( 'ityp_short_title_maxlen', 'integer' );
		$this->set_from_Request( 'short_title_maxlen' );

		// Front-Office Order (Short title)
		param( 'ityp_front_order_short_title', 'integer', NULL );
		$this->set_from_Request( 'front_order_short_title' );

		// Use title
		param( 'ityp_use_title', 'string' );
		$this->set_from_Request( 'use_title' );

		// Title max length
		param( 'ityp_title_maxlen', 'integer' );
		$this->set_from_Request( 'title_maxlen' );

		// Front-Office Order (Title)
		param( 'ityp_front_order_title', 'integer', NULL );
		$this->set_from_Request( 'front_order_title' );

		// Use URL
		param( 'ityp_use_url', 'string' );
		$this->set_from_Request( 'use_url' );

		// Front-Office Order (URL)
		param( 'ityp_front_order_url', 'integer', NULL );
		$this->set_from_Request( 'front_order_url' );

		// Treat as Podcast Media
		param( 'ityp_podcast', 'integer', 0 );
		$this->set_from_Request( 'podcast' );

		// Use Parent ID
		param( 'ityp_use_parent', 'string' );
		$this->set_from_Request( 'use_parent' );

		// Use text
		param( 'ityp_use_text', 'string' );
		$this->set_from_Request( 'use_text' );

		// Front-Office Order (Text)
		param( 'ityp_front_order_text', 'integer', NULL );
		$this->set_from_Request( 'front_order_text' );

		// Allow HTML
		param( 'ityp_allow_html', 'integer', 0 );
		$this->set_from_Request( 'allow_html' );

		// Allow Teaser and Page breaks
		param( 'ityp_allow_breaks', 'integer', 0 );
		$this->set_from_Request( 'allow_breaks' );

		// Allow attachments
		param( 'ityp_allow_attachments', 'integer', 0 );
		$this->set_from_Request( 'allow_attachments' );

		// Front-Office Order (Attachments)
		param( 'ityp_front_order_attachments', 'integer', NULL );
		$this->set_from_Request( 'front_order_attachments' );

		// Front-Office Order (Workflow)
		param( 'ityp_front_order_workflow', 'integer', NULL );
		$this->set_from_Request( 'front_order_workflow' );

		// Use excerpt
		param( 'ityp_use_excerpt', 'string' );
		$this->set_from_Request( 'use_excerpt' );

		// Front-Office Order (Excerpt)
		param( 'ityp_front_order_excerpt', 'integer', NULL );
		$this->set_from_Request( 'front_order_excerpt' );

		// Use title tag
		param( 'ityp_use_title_tag', 'string' );
		$this->set_from_Request( 'use_title_tag' );

		// Use meta description
		param( 'ityp_use_meta_desc', 'string' );
		$this->set_from_Request( 'use_meta_desc' );

		// Use meta keywords
		param( 'ityp_use_meta_keywds', 'string' );
		$this->set_from_Request( 'use_meta_keywds' );

		// Use tags
		param( 'ityp_use_tags', 'string' );
		$this->set_from_Request( 'use_tags' );

		// Front-Office Order (Tags)
		param( 'ityp_front_order_tags', 'integer', NULL );
		$this->set_from_Request( 'front_order_tags' );

		// Allow featured
		param( 'ityp_allow_featured', 'integer', 0 );
		$this->set_from_Request( 'allow_featured' );

		// Allow switchable
		param( 'ityp_allow_switchable', 'integer', 0 );
		$this->set_from_Request( 'allow_switchable' );

		// Use country, region, sub-region, city:
		$use_country = param( 'ityp_use_country', 'string', 'never' );
		$use_region = param( 'ityp_use_region', 'string', 'never' );
		$use_sub_region = param( 'ityp_use_sub_region', 'string', 'never' );
		$use_city = param( 'ityp_use_city', 'string', 'never' );
		if( $use_city == 'required' )
		{ // If city is required - all location fields also are required
			$use_country = $use_region = $use_sub_region = 'required';
		}
		else if( $use_sub_region == 'required' )
		{ // If subregion is required - country & region fields also are required
			$use_country = $use_region = 'required';
		}
		else if( $use_region == 'required' )
		{ // If region is required - country field also is required
			$use_country = 'required';
		}
		$this->set( 'use_country', $use_country );
		$this->set( 'use_region', $use_region );
		$this->set( 'use_sub_region', $use_sub_region );
		$this->set( 'use_city', $use_city );

		// Use coordinates
		param( 'ityp_use_coordinates', 'string' );
		$this->set_from_Request( 'use_coordinates' );

		// Front-Office Order (Location:Country/Region/Sub-region/City)
		param( 'ityp_front_order_location', 'integer', NULL );
		$this->set_from_Request( 'front_order_location' );

		// Use comments
		param( 'ityp_use_comments', 'integer', 0 );
		$this->set_from_Request( 'use_comments' );

		// Message before comment form:
		param( 'ityp_comment_form_msg', 'text' );
		$this->set_from_Request( 'comment_form_msg', NULL, true );

		// Allow custom message for each post
		param( 'ityp_allow_comment_form_msg', 'integer', 0 );
		$this->set_from_Request( 'allow_comment_form_msg' );

		// Allow closing comments
		param( 'ityp_allow_closing_comments', 'integer', 0 );
		$this->set_from_Request( 'allow_closing_comments' );

		// Allow disabling comments
		param( 'ityp_allow_disabling_comments', 'integer', 0 );
		$this->set_from_Request( 'allow_disabling_comments' );

		// Use comment expiration
		param( 'ityp_use_comment_expiration', 'string' );
		$this->set_from_Request( 'use_comment_expiration' );

		// Load custom fields from request
		$this->load_custom_fields_from_Request();

		return ! param_errors_detected();
	}


	/**
	 * Load custom fields from request
	 */
	function load_custom_fields_from_Request()
	{
		global $Messages;

		// Initialize the arrays
		$this->update_custom_fields = array();
		$this->insert_custom_fields = array();
		$this->delete_custom_fields = trim( param( 'deleted_custom_fields', 'string', '' ), ', ' );
		$this->delete_custom_fields = empty( $this->delete_custom_fields ) ? array() : explode( ',', $this->delete_custom_fields );

		// Field names array is used to check the diplicates
		$field_names = array();

		// Get previous computed custom fields:
		$old_computed_custom_fields = $this->get_custom_fields( 'computed' );

		// Empty and Initialize the custom fields from POST data
		$this->custom_fields = array();

		$empty_title_error = false; // use this to display empty title fields error message only ones
		$custom_field_count = param( 'count_custom_fields', 'integer', 0 ); // all custom fields count ( contains even deleted fields )

		if( empty( $custom_field_count ) )
		{	// No custom fields to insert/update:
			return;
		}

		// Decode data of all custom fields which were posted as single JSON encoded hidden input by JavaScript:
		$custom_fields_data = json_decode( param( 'custom_fields_data', 'string' ) );
		set_param( 'custom_fields_data', $custom_fields_data );

		$inputs = array(
			'ID'              => '/^[a-z0-9\-_]+$/',
			'new'             => array( 'integer', 0 ),
			'label'           => 'string',
			'name'            => '/^[a-z0-9\-_]+$/',
			'schema_prop'     => 'string',
			'type'            => 'string',
			'order'           => 'integer',
			'note'            => 'string',
			'required'        => array( 'integer', 0 ),
			'meta'            => array( 'integer', 0 ),
			'public'          => array( 'integer', 0 ),
			'format'          => 'string',
			'formula'         => 'string',
			'disp_condition'  => 'string',
			'header_class'    => 'string',
			'cell_class'      => 'string',
			'link'            => array( 'string', 'nolink' ),
			'link_nofollow'   => 'integer',
			'link_class'      => 'string',
			'line_highlight'  => array( 'string', 'never' ),
			'green_highlight' => array( 'string', 'never' ),
			'red_highlight'   => array( 'string', 'never' ),
			'description'     => 'html',
			'merge'           => array( 'integer', 0 ),
		);

		// Flag to inform user once about changed formula:
		$formula_was_changed = false;

		for( $i = 1 ; $i <= $custom_field_count; $i++ )
		{
			$custom_field_data = array();
			foreach( $inputs as $input_name => $input_data )
			{
				// Get input data:
				$input_type = is_array( $input_data ) ? $input_data[0] : $input_data;
				$input_default_value = is_array( $input_data )  ? $input_data[1] : NULL;
				if( isset( $custom_fields_data->{$input_name.$i} ) )
				{
					if( $input_type == 'string' &&
					    $input_default_value !== NULL &&
					    $custom_fields_data->{$input_name.$i} === '' )
					{	// Use default value for empty string:
						$input_value = $input_default_value;
					}
					else
					{	// Use submitted value:
						$input_value = $custom_fields_data->{$input_name.$i};
					}
				}
				else
				{	// Use default value:
					$input_value = $input_default_value;
				}

				if( $input_value !== $input_default_value )
				{	// Format input value to requested type only when it is not default value:
					if( substr( $input_type, 0, 1 ) == '/' )
					{	// Check value by regexp:
						if( ! empty( $input_value ) && ! preg_match( $input_type, $input_value ) )
						{	// Don't allow wrong value:
							bad_request_die( sprintf( T_('Illegal value received for parameter &laquo;%s&raquo;!'), $input_name ) );
						}
						$input_type = 'string';
					}
					$input_value = param_format( $input_value, $input_type );
				}

				switch( $input_name )
				{
					case 'ID':
						$custom_field_ID = $input_value;
						break;
					case 'new':
						$custom_field_is_new = $input_value;
						break;
					default:
						$custom_field_data[ $input_name ] = $input_value;
						break;
				}
			}

			// Note: this param contains ID of existing custom field from DB
			//       or random value like d63d5d53-df3d-5299-8c85-35f69b77 for new creating field:
			if( empty( $custom_field_ID ) || in_array( $custom_field_ID, $this->delete_custom_fields ) )
			{ // This field was deleted, don't neeed to update
				continue;
			}

			// Add each new/existing custom field in this array
			// in order to see all them on the form when post type is not updated because some errors
			$this->custom_fields[] = array(
					'temp_i'          => $i, // Used only on submit form to know the number of the field on the form
					'ID'              => $custom_field_ID,
					'ityp_ID'         => $this->ID,
				) + $custom_field_data;

			if( empty( $custom_field_data['label'] ) )
			{ // Field title can't be emtpy
				if( ! $empty_title_error )
				{ // This message was not displayed yet
					$Messages->add( T_('Custom field titles can\'t be empty!') );
					$empty_title_error = true;
				}
			}
			elseif( empty( $custom_field_data['name'] ) )
			{ // Field identical name can't be emtpy
				$Messages->add( sprintf( T_('Please enter name for custom field "%s"'), $custom_field_data['label'] ) );
			}
			elseif( in_array( $custom_field_data['name'], $field_names ) )
			{ // Field name must be identical
				$Messages->add( sprintf( T_('The field name "%s" is used more than once. Each field name must be unique.'), $custom_field_data['name'] ) );
			}
			else
			{
				$field_names[] = $custom_field_data['name'];
			}

			// Checks for computed custom fields:
			if( $custom_field_data['type'] == 'computed' )
			{
				if( $custom_field_data['formula'] === '' )
				{	// Formula must be not empty:
					$Messages->add( sprintf( TB_('Please enter formula for computed custom field "%s".'), $custom_field_data['label'] ) );
				}

				if( ! $formula_was_changed &&
				    ! $custom_field_is_new &&
				    isset( $old_computed_custom_fields[ $custom_field_data['name'] ] ) &&
				    $old_computed_custom_fields[ $custom_field_data['name'] ]['formula'] != $custom_field_data['formula'] )
				{	// Inform once user about changed formula:
					$Messages->add( TB_('You changed one or several formulas. All posts need to be re-saved to update the results of these formulas.'), 'warning' );
					$formula_was_changed = true;
				}
			}

			if( $custom_field_is_new )
			{ // Insert custom field
				$this->insert_custom_fields[ $custom_field_ID ] = $custom_field_data;
			}
			else
			{ // Update custom field
				$this->update_custom_fields[ $custom_field_ID ] = $custom_field_data;
			}
		}

		// Check formulas of computed fields:
		$custom_fields = $this->get_custom_fields();
		foreach( $custom_fields as $custom_field )
		{
			if( $custom_field['type'] != 'computed' )
			{	// Skip not computed field:
				continue;
			}

			$formula_has_wrong_field = false;
			if( preg_match_all( '#\$(.+?)\$#', $custom_field['formula'], $formula_fields ) )
			{	// If formula has at least one field:
				foreach( $formula_fields[1] as $formula_field )
				{
					if( ! isset( $custom_fields[ $formula_field ] ) )
					{	// Not found field:
						$Messages->add( sprintf( TB_('The field name %s is not recognized (in the formula %s of the field "%s".'),
								'<code>'.$formula_field.'</code>',
								'<code>'.$custom_field['formula'].'</code>',
								$custom_field['label']
							), 'warning' );
						$formula_has_wrong_field = true;
					}
					elseif( ! in_array( $custom_fields[ $formula_field ]['type'], array( 'double', 'computed' ) ) )
					{	// Field with wrong type is used in formula:
						$Messages->add( sprintf( TB_('Only numeric or computed fields can be used in formulas. Please remove the field %s from the formula %s of the field "%s".'),
								'<code>'.$formula_field.'</code>',
								'<code>'.$custom_field['formula'].'</code>', 
								$custom_field['label']
							), 'warning' );
						$formula_has_wrong_field = true;
					}
				}
			}

			if( $formula_uses_function = preg_match( '#[a-z0-9_]+\s*\(.*?\)#i', $custom_field['formula'] ) )
			{	// Don't allow to use functions in formula:
				$Messages->add( sprintf( T_('Forbidden to use functions in formula, please fix %s of the field "%s".'),
						'<code>'.$custom_field['formula'].'</code>',
						$custom_field['label']
					), 'warning' );
			}

			if( ! $formula_has_wrong_field &&
			    ! $formula_uses_function &&
			    $custom_field['formula'] !== '' )
			{	// Check for correct formula:
				$test_formula = preg_replace( '#\$(.+?)\$#', '1', $custom_field['formula'] );
				try
				{	// Compute value:
					ob_start();
					$test_value = eval( "return $test_formula;" );
					$formula_code_output = ob_get_clean();
					if( ( $formula_code_output !== '' && $formula_code_output !== false ) ||
							! is_numeric( $test_value ) )
					{	// If output buffer contains some text it means there is some error;
						// Don't allow to use not numeric value for the "computed" custom field:
						$test_value = NULL;
					}
				}
				catch( Error $e )
				{	// Set NULL value for wrong formula:
					$test_value = NULL;
				}
				catch( ParseError $e )
				{	// Set NULL value for wrong formula:
					$test_value = NULL;
				}

				if( $test_value === NULL )
				{	// Display warning when formula cannot be executed properly:
					$Messages->add( sprintf( TB_('Please check formula %s of the field "%s" because it cannot be evaluated properly.'),
							'<code>'.$custom_field['formula'].'</code>',
							$custom_field['label']
						), 'warning' );
				}
			}
		}
	}


	/**
	 * Get the name of the ItemType
	 * @return string
	 */
	function get_name()
	{
		return $this->name;
	}


	/**
	 * Insert object into DB based on previously recorded changes.
	 */
	function dbinsert()
	{
		global $DB;

		$DB->begin();

		if( parent::dbinsert() )
		{
			global $Collection, $Blog;

			// Update/Insert/Delete custom fields:
			$this->dbsave_custom_fields();

			if( ! empty( $Blog ) )
			{ // Enable this item type only for selected Blog:
				$DB->query( 'INSERT INTO T_items__type_coll
					       ( itc_ityp_ID, itc_coll_ID )
					VALUES ( '.$this->ID.', '.$Blog->ID.' )' );
			}
		}
		else
		{
			$DB->rollback();
			return false;
		}

		$DB->commit();

		return true;
	}


	/**
	 * Update the DB based on previously recorded changes
	 */
	function dbupdate()
	{
		global $DB;

		$DB->begin();

		parent::dbupdate();

		// Update/Insert/Delete custom fields
		$this->dbsave_custom_fields();

		$DB->commit();

		// BLOCK CACHE INVALIDATION:
		BlockCache::invalidate_key( 'item_type_'.$this->ID, 1 ); // Item Type has changed (useful for compare widget which needs to check several item_IDs, including from different collections)

		return true;
	}


	/**
	 * Update the DB based on previously recorded changes
	 */
	function dbdelete()
	{
		global $DB;

		$DB->begin();

		$item_ID = $this->ID;

		if( parent::dbdelete() )
		{
			// Delete all custom fields of this post type
			$DB->query( 'DELETE FROM T_items__type_custom_field
				WHERE itcf_ityp_ID = '.$item_ID );
		}

		$DB->commit();
	}


	/**
	 * Update/Insert/Delete custom fields
	 */
	function dbsave_custom_fields()
	{
		global $DB;

		if( ! empty( $this->delete_custom_fields ) )
		{	// Delete custom fields:
			$sql_data = array();
			foreach( $this->delete_custom_fields as $itcf_ID )
			{
				$sql_data[] = '( itcf_ityp_ID = '.$DB->quote( $this->ID ).' AND itcf_ID = '.$DB->quote( $itcf_ID ).' )';
			}
			$DB->query( 'DELETE FROM T_items__type_custom_field
				WHERE '.implode( ' OR ', $sql_data ) );
		}

		if( ! empty( $this->insert_custom_fields ) )
		{	// Insert new custom fields:
			$sql_data = array();
			foreach( $this->insert_custom_fields as $itcf_ID => $custom_field )
			{
				$sql_data[] = '( '.$DB->quote( $this->ID ).', '
						.$DB->quote( $custom_field['label'] ).', '
						.$DB->quote( $custom_field['name'] ).', '
						.$DB->quote( $custom_field['schema_prop'] ).', '
						.$DB->quote( $custom_field['type'] ).', '
						.( empty( $custom_field['order'] ) ? 'NULL' : $DB->quote( $custom_field['order'] ) ).', '
						.( empty( $custom_field['note'] ) ? 'NULL' : $DB->quote( $custom_field['note'] ) ).', '
						.$DB->quote( $custom_field['required'] ).', '
						.$DB->quote( $custom_field['meta'] ).', '
						.$DB->quote( $custom_field['public'] ).', '
						.$DB->quote( $custom_field['format'] ).', '
						.$DB->quote( $custom_field['formula'] ).', '
						.$DB->quote( $custom_field['disp_condition'] ).', '
						.$DB->quote( $custom_field['header_class'] ).', '
						.$DB->quote( $custom_field['cell_class'] ).', '
						.$DB->quote( $custom_field['link'] ).', '
						.$DB->quote( $custom_field['link_nofollow'] ).', '
						.$DB->quote( $custom_field['link_class'] ).', '
						.$DB->quote( $custom_field['line_highlight'] ).', '
						.$DB->quote( $custom_field['green_highlight'] ).', '
						.$DB->quote( $custom_field['red_highlight'] ).', '
						.( empty( $custom_field['description'] ) ? 'NULL' : $DB->quote( $custom_field['description'] ) ).', '
						.$DB->quote( $custom_field['merge'] ).' )';
			}
			$DB->query( 'INSERT INTO T_items__type_custom_field ( itcf_ityp_ID, itcf_label, itcf_name, itcf_schema_prop, itcf_type, itcf_order, itcf_note, itcf_required, itcf_meta, itcf_public, itcf_format, itcf_formula, itcf_disp_condition, itcf_header_class, itcf_cell_class, itcf_link, itcf_link_nofollow, itcf_link_class, itcf_line_highlight, itcf_green_highlight, itcf_red_highlight, itcf_description, itcf_merge )
					VALUES '.implode( ', ', $sql_data ) );
		}

		if( ! empty( $this->update_custom_fields ) )
		{	// Update custom fields:
			unset( $this->custom_fields );
			$old_custom_fields = $this->get_custom_fields( 'all', 'ID' );
			foreach( $this->update_custom_fields as $itcf_ID => $custom_field )
			{
				$DB->query( 'UPDATE T_items__type_custom_field
					SET
						itcf_label = '.$DB->quote( $custom_field['label'] ).',
						itcf_name = '.$DB->quote( $custom_field['name'] ).',
						itcf_schema_prop = '.$DB->quote( $custom_field['schema_prop'] ).',
						itcf_order = '.( empty( $custom_field['order'] ) ? 'NULL' : $DB->quote( $custom_field['order'] ) ).',
						itcf_note = '.( empty( $custom_field['note'] ) ? 'NULL' : $DB->quote( $custom_field['note'] ) ).',
						itcf_required = '.$DB->quote( $custom_field['required'] ).',
						itcf_meta = '.$DB->quote( $custom_field['meta'] ).',
						itcf_public = '.$DB->quote( $custom_field['public'] ).',
						itcf_format = '.$DB->quote( $custom_field['format'] ).',
						itcf_formula = '.$DB->quote( $custom_field['formula'] ).',
						itcf_cell_class = '.$DB->quote( $custom_field['cell_class'] ).',
						itcf_disp_condition = '.$DB->quote( $custom_field['disp_condition'] ).',
						itcf_header_class = '.$DB->quote( $custom_field['header_class'] ).',
						itcf_link = '.$DB->quote( $custom_field['link'] ).',
						itcf_link_nofollow = '.$DB->quote( $custom_field['link_nofollow'] ).',
						itcf_link_class = '.$DB->quote( $custom_field['link_class'] ).',
						itcf_line_highlight = '.$DB->quote( $custom_field['line_highlight'] ).',
						itcf_green_highlight = '.$DB->quote( $custom_field['green_highlight'] ).',
						itcf_red_highlight = '.$DB->quote( $custom_field['red_highlight'] ).',
						itcf_description = '.( empty( $custom_field['description'] ) ? 'NULL' : $DB->quote( $custom_field['description'] ) ).',
						itcf_merge = '.$DB->quote( $custom_field['merge'] ).'
					WHERE itcf_ityp_ID = '.$DB->quote( $this->ID ).'
						AND itcf_ID = '.$DB->quote( $itcf_ID ).'
						AND itcf_type = '.$DB->quote( $custom_field['type'] ) );
				if( isset( $old_custom_fields[ $itcf_ID ] ) &&
				    $this->ID > 0 &&
				    $custom_field['name'] != $old_custom_fields[ $itcf_ID ]['name'] )
				{	// Update item setting names of custom field to use new field name:
					$DB->query( 'UPDATE T_items__item_custom_field
						INNER JOIN T_items__item ON post_ID = icfv_item_ID AND post_ityp_ID = '.$DB->quote( $this->ID ).'
						  SET icfv_itcf_name = '.$DB->quote( $custom_field['name'] ).'
						WHERE icfv_itcf_name = '.$DB->quote( $old_custom_fields[ $itcf_ID ]['name'] ) );
				}
			}
		}
	}


	/**
	 * Check if this post type is used for intro posts
	 *
	 * @return boolean
	 */
	function is_intro()
	{
		return in_array( $this->usage, array( 'intro-front', 'intro-main', 'intro-cat', 'intro-tag', 'intro-sub', 'intro-all' ) );
	}


	/**
	 * Returns array, which determinate what post types are defaults of the blogs
	 *
	 * @return array ( key => Blog ID, value => ItemType ID )
	 */
	static function get_default_ids()
	{
		global $DB;

		// Get default value of blog setting "default_post_type"
		load_class( 'collections/model/_collsettings.class.php', 'CollectionSettings' );
		$CollectionSettings = new CollectionSettings();
		$item_types['default'] = $CollectionSettings->get_default( 'default_post_type' );

		// Get default post type of each blog
		$SQL = new SQL();
		$SQL->SELECT( 'cset_coll_ID, cset_value' );
		$SQL->FROM( 'T_coll_settings' );
		$SQL->WHERE( 'cset_name = "default_post_type"' );
		$item_types += $DB->get_assoc( $SQL->get() );

		return $item_types;
	}


	/**
	 * Get the custom fields
	 *
	 * @param string Type of custom field: 'all', 'varchar', 'double', 'text', 'html', 'url', 'image', 'computed', 'separator'. Use comma separator to get several types
	 * @param string Field name that is used as key of array: 'ID', 'ityp_ID', 'label', 'name', 'type', 'order', 'public'
	 * @return array Custom fields
	 */
	function get_custom_fields( $type = 'all', $array_key = 'name' )
	{
		if( ! isset( $this->custom_fields ) )
		{ // Initialize an array only first time
			if( empty( $this->ID ) )
			{ // Set an empty array for new creating post type
				$this->custom_fields = array();
			}
			else
			{ // Get the custom fields from DB
				global $DB;
				$SQL = new SQL( 'Load all custom fields definitions of Item Type #'.$this->ID );
				$SQL->SELECT( '*' );
				$SQL->FROM( 'T_items__type_custom_field' );
				$SQL->WHERE( 'itcf_ityp_ID = '.$DB->quote( $this->ID ) );
				$SQL->ORDER_BY( 'itcf_order, itcf_ID' );
				$custom_fields = $DB->get_results( $SQL, ARRAY_A );
				$this->custom_fields = array();
				foreach( $custom_fields as $c => $custom_field )
				{
					$this->custom_fields[ $c ] = array();
					foreach( $custom_field as $custom_field_key => $custom_field_value )
					{
						$this->custom_fields[ $c ][ substr( $custom_field_key, 5 ) ] = $custom_field_value;
					}
				}
			}
		}

		$custom_fields = array();
		foreach( $this->custom_fields as $custom_field )
		{
			if( $type == 'all' || strpos( $type, $custom_field['type'] ) !== false )
			{
				switch( $array_key )
				{
					case 'name':
						// Use field 'name' as key of array
						if( empty( $custom_field['name'] ) )
						{ // Name can be empty when we are saving it with empty name and page is displayed with error messages
							$field_index = $custom_field['ID'];
						}
						else
						{ // Get field index from name
							$field_index = preg_replace( '/\s+/', '_', strtolower( trim( $custom_field['name'] ) ) );
						}
						break;

					default:
						// Set an array key from other field, or use 'ID' on invalid field name
						$field_index = isset( $custom_field[ $array_key ] ) ? $custom_field[ $array_key ] : $custom_field['ID'];
						break;
				}
				$custom_fields[ $field_index ] = $custom_field;
			}
		}

		return $custom_fields;
	}


	/**
	 * Get associated post status
	 *
	 * @return array IDs of associated post status
	 */
	function get_applicable_post_status()
	{
		global $DB;

		$sql = 'SELECT its_pst_ID FROM T_items__status_type WHERE its_ityp_ID = '.$this->ID;
		$item_status_array = $DB->get_col( $sql );
		$item_status_array = array_map( 'intval', $item_status_array );

		return $item_status_array;
	}


	/**
	 * Get post status not associated with current item type
	 *
	 * @return array IDs of post status not valid for current item type
	 */
	function get_ignored_post_status( )
	{
		global $DB;

		$sql = 'SELECT pst_ID FROM T_items__status WHERE pst_ID NOT IN ( SELECT its_pst_ID FROM T_items__status_type WHERE its_ityp_ID = '.$this->ID.' )';
		/*
		$sql = 'SELECT pst_ID
							FROM T_items__status
							JOIN T_items__type
							LEFT JOIN T_items__status_type ON its_ityp_ID = ityp_ID AND its_pst_ID = pst_ID
							WHERE ityp_ID = '.$this->ID.'	AND its_pst_ID IS NULL';
		*/
		$item_status_array = $DB->get_col( $sql );
		$item_status_array = array_map( 'intval', $item_status_array );

		return $item_status_array;
	}


	/**
	 * Update item statuses associated with this item type
	 */
	function update_item_statuses_from_Request()
	{
		global $DB;

		$allowed_values = array();
		$remove_values = array();

		// Item Types
		$item_status_IDs = param( 'item_status_IDs', 'string', true );
		$item_status_IDs = explode( ',', $item_status_IDs );

		foreach( $item_status_IDs as $loop_status_ID )
		{
			$loop_status_ID = intval( $loop_status_ID );
			$item_status = param( 'status_'.$loop_status_ID, 'integer', 0 );

			if( $item_status )
			{
				$allowed_values[] = "( $this->ID, $loop_status_ID )";
			}
			else
			{
				$remove_values[] = $loop_status_ID;
			}
		}

		if( $allowed_values )
		{
			$DB->query( 'REPLACE INTO T_items__status_type( its_ityp_ID, its_pst_ID )
					VALUES '.implode( ', ', $allowed_values ) );
		}

		if( $remove_values )
		{
			$DB->query( 'DELETE FROM T_items__status_type
					WHERE its_ityp_ID = '.$this->ID.'
					AND its_pst_ID IN ('.implode( ',', $remove_values ).')' );
		}
	}


	/**
	 * Check if this Item Type is enabled for requested collection
	 *
	 * @param integer Collection ID
	 * @return boolean
	 */
	function is_enabled( $coll_ID )
	{
		if( empty( $this->ID ) )
		{	// Item Type is not inserted in DB yet:
			return false;
		}

		if( ! isset( $this->enabled_colls ) )
		{	// Load into cache where this Item Type is enabled for all collections:
			global $DB;
			$SQL = new SQL( 'Load all collections IDs where Item Type #'.$this->ID.' is enabled' );
			$SQL->SELECT( 'itc_coll_ID' );
			$SQL->FROM( 'T_items__type_coll' );
			$SQL->WHERE( 'itc_ityp_ID = '.$this->ID );
			$this->enabled_colls = $DB->get_col( $SQL );
		}

		return in_array( $coll_ID, $this->enabled_colls );
	}


	/**
	 * Get item denomination
	 *
	 * @param string Position where denomination will be used, can be one of the following: 'evobar_new', 'inskin_new_btn', 'title_new', 'title_update'
	 * @param string Default denomination, e.g. when no current collection
	 * @return string Item denomination
	 */
	function get_item_denomination( $position = 'evobar_new', $default_denomination = NULL )
	{
		switch( $position )
		{
			case 'evobar_new':
				if( ! empty( $this->evobar_link_text ) )
				{
					return $this->evobar_link_text;
				}
				break;

			case 'inskin_new_btn':
				if( ! empty( $this->skin_btn_text ) )
				{
					return $this->skin_btn_text;
				}
				break;
		}

		global $Collection, $Blog;

		if( ! empty( $Blog  ) )
		{
			return $Blog->get_item_denomination( $position );
		}

		return $default_denomination;
	}
}

?>