<?php
/**
 * This file implements the Site Menu class.
 *
 * This file is part of the b2evolution/evocms project - {@link http://b2evolution.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/}.
*
 * @license http://b2evolution.net/about/license.html GNU General Public License (GPL)
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

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


/**
 * Menu Class
 *
 * @package evocore
 */
class SiteMenu extends DataObject
{
	var $name;
	var $locale;
	var $translates_menu_ID;

	/**
	 * @var array Site Menu Entries
	 */
	var $entries = NULL;

	/**
	 * @var integer Translation menu count
	 */
	var $count_translation_menus = NULL;

	/**
	 * @var array Localized menus
	 */
	var $localized_menus = NULL;

	/**
	 * Constructor
	 *
	 * @param object table Database row
	 */
	function __construct( $db_row = NULL )
	{
		// Call parent constructor:
		parent::__construct( 'T_menus__menu', 'menu_', 'menu_ID' );

		if( $db_row != NULL )
		{	// Get menu data from DB:
			$this->ID = $db_row->menu_ID;
			$this->translates_menu_ID = $db_row->menu_translates_menu_ID;
			$this->name = $db_row->menu_name;
			$this->locale = $db_row->menu_locale;
		}
	}


	/**
	 * Get delete cascade settings
	 *
	 * @return array
	 */
	static function get_delete_cascades()
	{
		return array(
				array( 'table' => 'T_menus__menu', 'fk' => 'menu_translates_menu_ID', 'msg' => T_('%d child menus') ),
				array( 'table' => 'T_menus__entry', 'fk' => 'ment_menu_ID', 'msg' => T_('%d menu entries') ),
			);
	}


	/**
	 * Load data from Request form fields.
	 *
	 * @return boolean true if loaded data seems valid.
	 */
	function load_from_Request()
	{
		// Name:
		param( 'menu_name', 'string' );
		param_check_not_empty( 'menu_name', T_('Please enter a name for the menu.') );
		$this->set_from_Request( 'name' );

		// Locale:
		param( 'menu_locale', 'string' );
		$this->set_from_Request( 'locale' );

		// Translation of Menu:
		$menu_translates_menu_ID = param( 'menu_translates_menu_ID', 'integer', NULL );
		if( isset( $menu_translates_menu_ID ) && $this->has_translations() )
		{	// Display error message if we want make the meta category from category with posts
			global $Messages;
			$Messages->add( sprintf( T_('This menu cannot become a translation of another because it has %d translations itself.'), $this->count_translation_menus ) );
		}
		$this->set_from_Request( 'translates_menu_ID' );

		// Store auto menu entries in temp var, they will be inserted in SiteMenu::dbinsert():
		$this->insert_menu_entries = param( 'menu_entries', 'array' );

		return ! param_errors_detected();
	}


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

		$DB->begin();

		if( $r = parent::dbinsert() )
		{	// If Menu has been inserted successfully:
			if( ! empty( $this->insert_menu_entries ) )
			{
				$entry_sections = array();
				$root_order = 10;
				$prev_parent = NULL;
				foreach( $this->insert_menu_entries as $menu_entry_key => $menu_entry_text )
				{
					$SiteMenuEntry = new SiteMenuEntry();
					$SiteMenuEntry->set( 'menu_ID', $this->ID );
					if( $menu_entry_key == '#contact#' )
					{	// Special "Contact" entry:
						$SiteMenuEntry->set( 'text', T_('Contact') );
						$SiteMenuEntry->set( 'type', 'ownercontact' );
						$SiteMenuEntry->set( 'order', $root_order );
						$root_order += 10;
						$SiteMenuEntry->dbinsert();
					}
					elseif( preg_match( '/^([a-z]+)_(\d+)(_(\d+))?$/', $menu_entry_key, $m ) )
					{	// Section or Collection entry:
						switch( $m[1] )
						{
							case 'sec':
								// Section entry:
								$SiteMenuEntry->set( 'text', $menu_entry_text );
								$SiteMenuEntry->set( 'type', 'text' );
								$SiteMenuEntry->set( 'order', $root_order );
								$root_order += 10;
								if( $SiteMenuEntry->dbinsert() )
								{
									$entry_sections[ $m[2] ] = $SiteMenuEntry->ID;
								}
								break;
							case 'coll':
								// Collection entry:
								$SiteMenuEntry->set( 'text', $menu_entry_text );
								$SiteMenuEntry->set( 'coll_ID', $m[2] );
								$SiteMenuEntry->set( 'type', 'home' );
								if( isset( $m[4], $entry_sections[ $m[4] ] ) )
								{
									if( $prev_parent != $entry_sections[ $m[4] ] )
									{
										$sub_order = 10;
									}
									$SiteMenuEntry->set( 'parent_ID', $entry_sections[ $m[4] ] );
									$prev_parent = $entry_sections[ $m[4] ];
									$SiteMenuEntry->set( 'order', $sub_order );
									$sub_order += 10;
								}
								else
								{
									$SiteMenuEntry->set( 'order', $root_order );
									$root_order += 10;
								}
								$SiteMenuEntry->dbinsert();
								break;
						}
					}
				}
			}
		}

		if( $r )
		{	
			$DB->commit();
		}
		else
		{
			$DB->rollback();
		}

		return $r;
	}


	/**
	 * Duplicate menu
	 * 
	 * @return boolean True if duplication was successfull, false otherwise
	 */
	function duplicate()
	{
		global $DB;

		$DB->begin();

		$duplicated_menu_ID = $this->ID;
		$this->ID = 0;

		// Fields that should not be duplicated must be included in the array below:
		$skipped_fields = array( 'ID' );

		// Get all fields of the duplicated menu:
		$source_fields_SQL = new SQL( 'Get all fields of the duplicated menu #'.$duplicated_menu_ID );
		$source_fields_SQL->SELECT( '*' );
		$source_fields_SQL->FROM( 'T_menus__menu' );
		$source_fields_SQL->WHERE( 'menu_ID = '.$DB->quote( $duplicated_menu_ID ) );
		$source_fields = $DB->get_row( $source_fields_SQL, ARRAY_A );

		// Use field values of duplicated collection by default:
		foreach( $source_fields as $source_field_name => $source_field_value )
		{
			// Cut prefix "menu_" of each field:
			$source_field_name = substr( $source_field_name, 5 );
			if( in_array( $source_field_name, $skipped_fields ) )
			{ // Do not duplicate skipped fields
				continue;
			}
			if( isset( $this->$source_field_name ) )
			{	// Unset current value in order to assign new below, especially to update this in array $this->dbchanges:
				unset( $this->$source_field_name );
			}
			$this->set( $source_field_name, $source_field_value );
		}

		// Call this firstly to find all possible errors before inserting:
		// Also to set new values from submitted form:
		if( ! $this->load_from_Request() )
		{	// Error on handle new values from form:
			$this->ID = $duplicated_menu_ID;
			$DB->rollback();
			return false;
		}

		// Try insert new collection in DB:
		if( ! $this->dbinsert() )
		{	// Error on insert collection in DB:
			$this->ID = $duplicated_menu_ID;
			$DB->rollback();
			return false;
		}

		// Copy all menu entries linked to the menu:
		$menu_entry_fields = array( 'ment_ID', 'ment_menu_ID', 'ment_parent_ID', 'ment_order', 'ment_user_pic_size', 'ment_text', 'ment_type',
				'ment_coll_logo_size', 'ment_coll_ID', 'ment_item_ID', 'ment_url', 'ment_visibility', 'ment_access', 'ment_show_badge', 'ment_highlight', 'ment_class' );

		$menu_entries_SQL = 'SELECT '.implode( ', ', $menu_entry_fields ).'
							 FROM T_menus__entry
							 WHERE ment_menu_ID = '.$DB->quote( $duplicated_menu_ID ).'
							 ORDER BY ment_parent_ID ASC, ment_order ASC';

		$menu_entries = $DB->get_results( $menu_entries_SQL, ARRAY_A );

		$entries = array();
		foreach( $menu_entries as $menu_entry )
		{
			$loop_SiteMenuEntry = new SiteMenuEntry();
			foreach( $menu_entry_fields as $entry_field )
			{
				if( $entry_field == 'ment_ID')
				{
					continue;
				}
				elseif( $entry_field == 'ment_menu_ID' )
				{
					$loop_SiteMenuEntry->set( 'menu_ID', $this->ID );
				}
				elseif( $entry_field == 'ment_parent_ID' && ! empty( $menu_entry['ment_parent_ID'] ) && isset( $entries[$menu_entry['ment_parent_ID']] ) )
				{
					$loop_SiteMenuEntry->set( 'parent_ID', $entries[$menu_entry['ment_parent_ID']]);
				}
				else
				{
					$property = substr( $entry_field, 5 );
					$loop_SiteMenuEntry->set( $property, $menu_entry[$entry_field] );
				}
			}
			$loop_SiteMenuEntry->dbinsert();
			$entries[$menu_entry['ment_ID']] = $loop_SiteMenuEntry->ID;
		}

		// Duplication is successful, commit all above changes:
		$DB->commit();

		// Commit changes in cache:
		$SiteMenuCache = & get_SiteMenuCache();
		$SiteMenuCache->add( $this );

		return true;
	}


	/**
	 * Get name of Menu Entry
	 *
	 * @return string Menu Entry
	 */
	function get_name()
	{
		return $this->get( 'name' );
	}


	/**
	 * Get menu entries
	 *
	 * @return array Objects of Site Menu Entries
	 */
	function get_entries()
	{
		if( $this->entries !== NULL )
		{	// Use already loaded menu entries:
			return $this->entries;
		}

		$SiteMenuEntryCache = & get_SiteMenuEntryCache();
		$SiteMenuEntryCache->clear();
		$entries_SQL = $SiteMenuEntryCache->get_SQL_object();
		$entries_SQL->WHERE( 'ment_menu_ID = '.$this->ID );
		$entries_SQL->WHERE_and( 'ment_parent_ID IS NULL' );
		$entries_SQL->ORDER_BY( 'ment_order, ment_ID' );
		$SiteMenuEntryCache->load_by_sql( $entries_SQL );

		$this->entries = $SiteMenuEntryCache->cache;

		return $this->entries;
	}


	/**
	 * Get max order of menu entries
	 *
	 * @param integer Parent Menu Entry ID
	 * @return integer
	 */
	function get_max_order( $parent_ID = NULL )
	{
		if( empty( $this->ID ) )
		{
			return 0;
		}

		global $DB;
		$SQL = new SQL( 'Get max order of entries in Menu #'.$this->ID.' for parent Menu Entry #'.intval( $parent_ID ) );
		$SQL->SELECT( 'MAX( ment_order )' );
		$SQL->FROM( 'T_menus__entry' );
		$SQL->WHERE( 'ment_menu_ID = '.$DB->quote( $this->ID ) );
		$SQL->WHERE_and( 'ment_parent_ID '.( empty( $parent_ID ) ? 'IS NULL' : '= '.$DB->quote( $parent_ID ) ) );

		return intval( $DB->get_var( $SQL ) );
	}


	/**
	 * Get localized menus
	 * 
	 * @param string Locale
	 * @return array Array of SiteMenu objects
	 */
	function get_localized_menus( $locale )
	{
		global $DB;

		if( ! isset( $this->localized_menus[$locale] ) )
		{
			$SiteMenuCache = & get_SiteMenuCache();
			$SiteMenuCache->clear( true );
			$where = 'menu_translates_menu_ID = '.$DB->quote( $this->ID ).' AND menu_locale = '.$DB->quote( $locale );
			$this->localized_menus[$locale] = $SiteMenuCache->load_where( $where );
		}

		return $this->localized_menus[$locale];
	}


	/**
	 * Check if this menu has at least one post
	 *
	 * @return boolean
	 */
	function has_translations()
	{
		global $DB;

		if( $this->ID == 0 )
		{	// New menu has no translation menus:
			return false;
		}

		if( !isset( $this->count_translation_menus ) )
		{
			$SQL = new SQL( 'Check if menu has translations menus' );
			$SQL->SELECT( 'COUNT( menu_translates_menu_ID )' );
			$SQL->FROM( 'T_menus__menu' );
			$SQL->WHERE( 'menu_translates_menu_ID = '.$DB->quote( $this->ID ) );
			$this->count_translation_menus = $DB->get_var( $SQL );
		}

		return ( $this->count_translation_menus > 0 );
	}
}

?>
