<?php
/*********************************************************************************
 * SugarCRM is a customer relationship management program developed by
 * SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * 
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "Powered by
 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
 * technical reasons, the Appropriate Legal Notices must display the words
 * "Powered by SugarCRM".
 * *******************************************************************************/
require_once('include/MVC/View/SugarView.php');
require_once('modules/Administration/updater_utils.php');


/**
 * Map case sensitive filenames to action.  This is used for linux/unix systems
 * where filenames are case sensitive
 */
$action_case_file = array('editview'=>'EditView', 'detailview'=>'DetailView', 'listview'=>'ListView');

class SugarController{
	/**
	 * remap actions in here 
	 * e.g. make all detail views go to edit views
	 * $action_remap = array('detailview'=>'editview');
	 */
	var $action_remap = array('index'=>'listview');
	/**
	 * The name of the current module.
	 */
	var $module = 'Home';
	/**
	 * The name of the current action.
	 */
	var $action = 'index';
	/**
	 * The id of the current record.
	 */
	var $record = '';
	/**
	 * The name of the return module.
	 */
	var $return_module = null;
	/**
	 * The name of the return action.
	 */
	var $return_action = null;
	/**
	 * The id of the return record.
	 */
	var $return_id = null;
	/**
	 * If the action was remapped it will be set to do_action and then we will just 
	 * use do_action for the actual action to perform.
	 */
	var $do_action = 'index';
	/**
	 * If a bean is present that set it.
	 */
	var $bean = null;
	/**
	 * url to redirect to
	 */
	var $redirect_url = '';
	/**
	 * any subcontroller can modify this to change the view
	 */
	var $view = 'classic';
	/**
	 * this array will hold the mappings between a key and an object for use within the view.
	 */
	var $view_object_map = array();
	
	/**
	 * This array holds the methods that handleAction() will invoke, in sequence. 
	 */
	var $tasks = array(
					   'pre_action', 
					   'do_action', 
					   'post_action'
					   );
	/**
	 * Map an action directly to a file. This will be loaded from action_file_map.php
	 */
	var $action_file_map = array();
	/**
	 * Map an action directly to a view. This will be loaded from action_view_map.php
	 */
	var $action_view_map = array();
	
	/**
	 * This can be set from the application to tell us whether we have authorization to 
	 * process the action. If this is set we will default to the noaccess view.
	 */
	var $hasAccess = true;
		
	/**
	 * Constructor. This ie meant tot load up the module, action, record as well
	 * as the mapping arrays.
	 */
	function SugarController(){
	}

	function setup($module = ''){
		if(empty($module) && !empty($_REQUEST['module']))
			$module = $_REQUEST['module'];
		//set the module
		if(!empty($module))
			$this->setModule($module);
		//set properties on the controller from the $_REQUEST
		$this->loadPropertiesFromRequest();
		//load the mapping files
		$this->loadMappings();
	}
	/**
	 * Set the module on the Controller
	 *
	 * @param object $module
	 */
	function setModule($module){
		$this->module = $module;
	}
	
	/**
	 * Set properties on the Controller from the $_REQUEST
	 *
	 */
	function loadPropertiesFromRequest(){
		if(!empty($_REQUEST['action']))
			$this->action = $_REQUEST['action'];
		if(!empty($_REQUEST['record']))
			$this->record = $_REQUEST['record'];
		if(!empty($_REQUEST['view']))
			$this->view = $_REQUEST['view'];
		if(!empty($_REQUEST['return_module']))
			$this->return_module = $_REQUEST['return_module'];
		if(!empty($_REQUEST['return_action']))
			$this->return_action = $_REQUEST['return_action'];
		if(!empty($_REQUEST['return_id']))
			$this->return_id = $_REQUEST['return_id'];
	}
	
	/**
	 * Load map files for use within the Controller
	 *
	 */
	function loadMappings(){
		$this->loadMapping('action_view_map');
		$this->loadMapping('action_file_map');
	}

	/**
	 * Given a record id load the bean. This bean is accessible from any sub controllers.
	 */
	function loadBean(){
		if(!empty($GLOBALS['beanList'][$this->module])){
			$class = $GLOBALS['beanList'][$this->module];
			if(!empty($GLOBALS['beanFiles'][$class])){
				require_once($GLOBALS['beanFiles'][$class]);
				$this->bean = new $class();
				if(!empty($this->record)){
					$this->bean->retrieve($this->record);
					if($this->bean)
						$GLOBALS['FOCUS'] = $this->bean;
				}
			}
		}
	}
	
	/**
	 * Generic load method to load mapping arrays.
	 */
	function loadMapping($var){
		$$var = sugar_cache_retrieve("CONTROLLER_". $var . "_".$this->module);
		if(!$$var){
			$$var = array();
			if(file_exists('include/MVC/Controller/'. $var . '.php')){		
				require('include/MVC/Controller/'. $var . '.php');
			}
			if(file_exists('modules/'.$this->module.'/'. $var . '.php')){		
				require('modules/'.$this->module.'/'. $var . '.php');
			}
			if(file_exists('custom/modules/'.$this->module.'/'. $var . '.php')){		
				require('custom/modules/'.$this->module.'/'. $var . '.php');
			}
			if(file_exists('custom/include/MVC/Controller/'. $var . '.php')){		
				require('custom/include/MVC/Controller/'. $var . '.php');
			}
			
			sugar_cache_put("CONTROLLER_". $var . "_".$this->module, $$var);
		}
		$this->$var = $$var;
	}
			
	/**
	 * This method is called from SugarApplication->execute and it will bootstrap the entire controller process
	 */
	function execute(){
		$this->process();        
		if(!empty($this->view)){
			$this->processView();
		}elseif(!empty($this->redirect_url)){
			$this->redirect();
		}
	}
	
	function processView(){
		$view = ViewFactory::loadView($this->view, $this->module, $this->bean, $this->view_object_map);
		if(!empty($this->bean) && !$this->bean->ACLAccess($view->type) && $view->type != 'list'){
			ACLController::displayNoAccess(true);
			sugar_cleanup(true);
		}
		$view->process();
	}
	
	function preProcess()
	{}

	/**
	 * if we have a function to support the action use it otherwise use the default action
	 * 
	 * 1) check for file
	 * 2) check for action
	 */
	function process(){
		$GLOBALS['action'] = $this->action;
		$GLOBALS['module'] = $this->module;
		//check to ensure we have access to the module.
		if($this->hasAccess){
			$this->do_action = $this->action;
			
			$file = $this->getActionFilename($this->do_action);

			// TRACKER HOOK - log this view
			// this is the first point we can make use of the bean->get_summary_text() method to find the human readable title for this record
			// the human readable title is required for the breadcrumbs
			// However, the flow may never reach this point! This will happen if there is either a custom controller that overrides process() (currently there are none) or if there is a custom action file in the module directory
			// For now we just ignore these cases, as they are both quite unlikely
			// Later we may do two stages of activity tracking (the first to capture some info at least before the flow split) or we might require that custom stuff provides a name entry some other way than through a bean
			// First, we must load a bean to obtain the short-form text description of this item. This must come from the bean as the description is built by the module from the particulars of the item we're about to track
			
			$this->loadBean();
	        if (isset($GLOBALS['FOCUS'])) {
			 	$GLOBALS['FOCUS']->track_view($GLOBALS['current_user']->id, $this->module, strtolower($_REQUEST['action']));
			}
			
			if(file_exists('modules/' . $this->module . '/'. $file . '.php') || file_exists('custom/modules/' . $this->module . '/'. $file . '.php')){
				// A 'classic' module, using the old pre-MVC display files
				// We should now discard the bean we just obtained for tracking as the pre-MVC module will instantiate its own
				unset($GLOBALS['FOCUS']);
				$GLOBALS['log']->debug('Module:' . $this->module . ' using file: '. $file);
				$this->action_default();
				
			}else{
			    
				if(!empty($this->action_remap[$this->do_action])){
					$this->action = $this->action_remap[$this->do_action];
					$this->do_action = $this->action;
				}
				if(!$this->handle_action()){
					$GLOBALS['log']->debug('Module:' . $this->module . ' action does not exist: '. $this->do_action);
					if(!empty($this->action_file_map[strtolower($this->do_action)])){
						$this->view = '';
						$GLOBALS['log']->debug('Using Action File Map:' . $this->action_file_map[strtolower($this->do_action)]);
						require_once($this->action_file_map[strtolower($this->do_action)]);
					}elseif(!empty($this->action_view_map[strtolower($this->do_action)])){
						$GLOBALS['log']->debug('Using Action View Map:' . $this->action_view_map[strtolower($this->do_action)]);
						$this->view = $this->action_view_map[strtolower($this->do_action)];
					}else
						$this->no_action();
				}
			}
	
			$this->redirect();
		}else{
			$this->no_access();
		}
	}
	
	/**
	 * This method is called from the process method. I could also be called within an action_* method.
	 * It allows a developer to override any one of these methods contained within,
	 * or if the developer so chooses they can override the entire action_* method.
	 * 
	 * @return true if any one of the pre_, do_, or post_ methods have been defined,
	 * false otherwise.  This is important b/c if none of these methods exists, then we will run the
	 * action_default() method.
	 */
	function handle_action(){
		$processed = false;
		foreach($this->tasks as $task){
			$processed = ($this->$task() || $processed);
		}
		return ($processed);
	}
		
	/**
	 * Perform an action prior to the specified action.
	 * This can be overridde in a sub-class
	 */
	function pre_action(){
		$function = 'pre_' . $this->action;
		if($this->hasFunction($function)){
			$GLOBALS['log']->debug('Performing pre_action');
			$this->$function();
			return true;
		}
		return false;
	}
	
	/**
	 * Perform the specified action. 
	 * This can be overridde in a sub-class
	 */
	function do_action(){
		$function =  'action_'. strtolower($this->do_action);
		if($this->hasFunction($function)){
			$GLOBALS['log']->debug('Performing action: '.$function.' MODULE: '.$this->module);
			$this->$function();
			return true;
		}
		return false;
	}
	
	/**
	 * Perform an action after to the specified action has occurred.
	 * This can be overridde in a sub-class
	 */
	function post_action(){
		$function = 'post_' . $this->action;
		if($this->hasFunction($function)){
			$GLOBALS['log']->debug('Performing post_action');
			$this->$function();
			return true;
		}
		return false;
	}
	
	/**
	 * If there is no action found then display an error to the user.
	 */
	function no_action(){
		sugar_die("There is no action by that name.");
	}
	
	/**
	 * The default action handler for instances where we do not have access to process.
	 */
	function no_access(){
		$this->view = 'noaccess';
	}
	
	///////////////////////////////////////////////
	/////// HELPER FUNCTIONS
	///////////////////////////////////////////////
	
	/**
	 * Determine if a given function exists on the objects
	 * @param function - the function to check
	 * @return true if the method exists on the object, false otherwise
	 */
	function hasFunction($function){
		return method_exists($this, $function);
	}
	
	
	/**
	 * Set the url to which we will want to redirect
	 * 
	 * @param string url - the url to which we will want to redirect
	 */
	function set_redirect($url){
		$this->redirect_url = $url;
	}
	
	/**
	 * Perform redirection based on the redirect_url
	 * 
	 */
	function redirect(){
	
		if(!empty($this->redirect_url))
			SugarApplication::redirect($this->redirect_url);
	}
	
	////////////////////////////////////////////////////////
	////// DEFAULT ACTIONS
	///////////////////////////////////////////////////////

	/*
	 * Save a bean
	 */
	 
	/**
	 * Do some processing before saving the bean to the database.
	 */
	function pre_save(){
		if(!empty($_POST['assigned_user_id']) && $_POST['assigned_user_id'] != $this->bean->assigned_user_id && $_POST['assigned_user_id'] != $GLOBALS['current_user']->id && empty($GLOBALS['sugar_config']['exclude_notifications'][$this->bean->module_dir])){
			$this->bean->notify_on_save = true;
		}
		$GLOBALS['log']->debug("SugarController:: performing pre_save.");
		foreach($this->bean->field_defs as $field => $properties) {
			if(isset($_POST[$field])) {
				if(is_array($_POST[$field]) && !empty($properties['isMultiSelect'])) {
					if(empty($_POST[$field][0])) {
						unset($_POST[$field][0]);
					}
					if(!empty($_POST[$field][0])) {
						$_POST[$field] = implode('^,^', $_POST[$field]);	
					} else {
							continue;	
					}
				}	
				$this->bean->$field = $_POST[$field];
			}else if(!empty($properties['type']) && $properties['type'] == 'link'){
				//remove this relationship since we did not find it in the $_POST
			}
		}
		foreach($this->bean->relationship_fields as $field=>$link){
			if(!empty($_POST[$field])){
				$this->bean->$field = $_POST[$field];	
			}
		}
		if(!$this->bean->ACLAccess('save')){
			ACLController::displayNoAccess(true);
			sugar_cleanup(true);
		}
		$this->bean->unformat_all_fields();
	}
	
	/**
	 * Perform the actual save
	 */
	function action_save(){
		$this->bean->save(!empty($this->bean->notify_on_save));
	}
	
	/**
	 * Specify what happens after the save has occurred.
	 */
	function post_save(){
		$module = (!empty($this->return_module) ? $this->return_module : $this->module);
		$action = (!empty($this->return_action) ? $this->return_action : 'DetailView');
		$id = (!empty($this->return_id) ? $this->return_id : $this->bean->id);
		
		$url = "index.php?module=".$module."&action=".$action."&record=".$id;
		$this->set_redirect($url);
	}
	
	function action_ajaxformsave(){
		$this->pre_save();
		$this->bean->save();
		$this->view = 'multiedit';
	}
	
	/*
	 * Delete a bean
	 */

	/**
	 * Perform the actual deletion.
	 */
	function action_delete(){
		//do any pre delete processing
		//if there is some custom logic for deletion.
		if(!empty($_REQUEST['record'])){
			if(!$this->bean->ACLAccess('Delete')){
				ACLController::displayNoAccess(true);
				sugar_cleanup(true);
			}
			$this->bean->mark_deleted($_REQUEST['record']);	
		}else{
			sugar_die("A record number must be specified to delete");
		}
	}
	
	/**
	 * Specify what happens after the deletion has occurred.
	 */
	function post_delete(){
		$return_module = isset($_REQUEST['return_module']) ?
			$_REQUEST['return_module'] :
			$GLOBALS['sugar_config']['default_module'];
		$return_action = isset($_REQUEST['return_action']) ? 
			$_REQUEST['return_action'] : 
			$GLOBALS['sugar_config']['default_action'];
		$return_id = isset($_REQUEST['return_id']) ?
			$_REQUEST['return_id'] :
			'';
		$url = "index.php?module=".$return_module."&action=".$return_action."&record=".$return_id;
		$this->set_redirect($url);
	}
	
	/**
	 * Perform the listview action
	 */
	function action_listview(){
		$this->view_object_map['bean'] = $this->bean;
		$this->view = 'list';
	}
		
/*
	
	//THIS IS HANDLED IN ACTION_REMAP WHERE INDEX IS SET TO LISTVIEW 
	function action_index(){
	}
*/
	
	/**
	 * Action to handle when using a file as was done in previous versions of Sugar.
	 */
	function action_default(){
		$this->view = 'classic';
	}
	
	/**
	 * this method id used within a Dashlet when performing an ajax call
	 */
	function action_callmethoddashlet(){
		if(!empty($_REQUEST['id'])) {
		    $id = $_REQUEST['id'];
		    $requestedMethod = $_REQUEST['method'];
		    $dashletDefs = $GLOBALS['current_user']->getPreference('dashlets', 'Home'); // load user's dashlets config
		    if(!empty($dashletDefs[$id])) {
		        require_once($dashletDefs[$id]['fileLocation']);
		    
		        $dashlet = new $dashletDefs[$id]['className']($id, (isset($dashletDefs[$id]['options']) ? $dashletDefs[$id]['options'] : array()));
		        
		        if(method_exists($dashlet, $requestedMethod)) { 
		            echo $dashlet->$requestedMethod();    
		        } 
		        else {
		            echo 'no method';
		        }
		    }
		}
	}
	
	/**
	 * this method is used within a Dashlet when the options configuration is posted
	 */
	function action_configuredashlet(){
		global $current_user, $mod_strings;
		
		if(!empty($_REQUEST['id'])) {
		    $id = $_REQUEST['id'];
		    $dashletDefs = $current_user->getPreference('dashlets', $_REQUEST['module']); // load user's dashlets config
		    require_once($dashletDefs[$id]['fileLocation']);
		
		    $dashlet = new $dashletDefs[$id]['className']($id, (isset($dashletDefs[$id]['options']) ? $dashletDefs[$id]['options'] : array()));
		    if(!empty($_REQUEST['configure']) && $_REQUEST['configure']) { // save settings
		        $dashletDefs[$id]['options'] = $dashlet->saveOptions($_REQUEST);
		        $current_user->setPreference('dashlets', $dashletDefs, 0, $_REQUEST['module']);    
		    } 
		    else { // display options
		        $json = getJSONobj();
		        return 'result = ' . $json->encode((array('header' => $dashlet->title . ' : ' . $mod_strings['LBL_OPTIONS'],
		                                                 'body'  => $dashlet->displayOptions())));
		
		    }
		}
		else {
		    return '0';
		}	
	}
	
	/**
	 * getActionFilename
	 */
	function getActionFilename($action) {
	   global $action_case_file;
	   if(isset($action_case_file[$action])) {
	   	  return $action_case_file[$action];
	   }	
	   return $action;
	}
}
?>
