<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
 * SugarCRM Community Edition is a customer relationship management program developed by
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.

 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero 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 Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero 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 Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "Powered by
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
 ********************************************************************************/


/*
 * ModuleInstaller - takes an installation package from files in the custom/Extension/X directories, and moves them into custom/X to install them.
 * If a directory has multiple files they are concatenated together.
 * Relevant directories (X) are Layoutdefs, Vardefs, Include (bean stuff), Language, TableDictionary (relationships)
 *
 * Installation steps that involve more than just copying files:
 * 1. installing custom fields - calls bean->custom_fields->addField
 * 2. installing relationships - calls createTableParams to build the relationship table, and createRelationshipMeta to add the relationship to the relationship table
 * 3. rebuilding the relationships - at almost the last step in install(), calls modules/Administration/RebuildRelationship.php
 * 4. repair indices - uses "modules/Administration/RepairIndex.php";
 */



require_once('include/utils/progress_bar_utils.php');

require_once('ModuleInstall/ModuleScanner.php');
define('DISABLED_PATH', 'Disabled');

class ModuleInstaller{
	var $modules = array();
	var $silent = false;
	var $base_dir  = '';
	var $modulesInPackage = array();
	public $disabled_path = DISABLED_PATH;
    public $id_name;
	function __construct(){
		$this->ms = new ModuleScanner();
		$this->modules = get_module_dir_list();
		$this->db = DBManagerFactory::getInstance();
        include("ModuleInstall/extensions.php");
        $this->extensions = $extensions;
	}

    /**
     * @deprecated deprecated since version 7.6, PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code, use __construct instead
     */
    function ModuleInstaller(){
        $deprecatedMessage = 'PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code';
        if(isset($GLOBALS['log'])) {
            $GLOBALS['log']->deprecated($deprecatedMessage);
        }
        else {
            trigger_error($deprecatedMessage, E_USER_DEPRECATED);
        }
        self::__construct();
    }


   /*
    * ModuleInstaller->install includes the manifest.php from the base directory it has been given. If it has been asked to do an upgrade it checks to see if there is
    * an upgrade_manifest defined in the manifest; if not it errors. It then adds the bean into the custom/Extension/application/Ext/Include/<module>.php - sets beanList, beanFiles
    * and moduleList - and then calls ModuleInstaller->merge_files('Ext/Include', 'modules.ext.php', '', true) to merge the individual module files into a combined file
    * /custom/Extension/application/Ext/Include/modules.ext.php (which now contains a list of all $beanList, $beanFiles and $moduleList for all extension modules) -
    * this file modules.ext.php is included at the end of modules.php.
    *
    * Finally it runs over a list of defined tasks; then install_beans, then install_custom_fields, then clear the Vardefs, run a RepairAndClear, then finally call rebuild_relationships.
    */
	function install($base_dir, $is_upgrade = false, $previous_version = ''){
		if(defined('TEMPLATE_URL'))SugarTemplateUtilities::disableCache();
        if ((defined('MODULE_INSTALLER_PACKAGE_SCAN') && MODULE_INSTALLER_PACKAGE_SCAN)
            || !empty($GLOBALS['sugar_config']['moduleInstaller']['packageScan'])) {
			$this->ms->scanPackage($base_dir);
			if($this->ms->hasIssues()){
				$this->ms->displayIssues();
				sugar_cleanup(true);
			}
		}

        // workaround for bug 45812 - refresh vardefs cache before unpacking to avoid partial vardefs in cache
        global $beanList;
        foreach ($this->modules as $module_name) {
            if (!empty($beanList[$module_name])) {
                $objectName = BeanFactory::getObjectName($module_name);
                VardefManager::loadVardef($module_name, $objectName);
            }
        }

		global $app_strings, $mod_strings;
		$this->base_dir = $base_dir;
		$total_steps = 5; //minimum number of steps with no tasks
		$current_step = 0;
		$tasks = array(
			'pre_execute',
			'install_copy',
		    'install_extensions',
			'install_images',
			'install_dcactions',
			'install_dashlets',
			'install_connectors',
			'install_layoutfields',
			'install_relationships',
            'enable_manifest_logichooks',
			'post_execute',
			'reset_opcodes',
		);

		$total_steps += count($tasks);
		if(file_exists($this->base_dir . '/manifest.php')){
				if(!$this->silent){
					$current_step++;
					display_progress_bar('install', $current_step, $total_steps);
					echo '<div id ="displayLoglink" ><a href="#" onclick="document.getElementById(\'displayLog\').style.display=\'\'">'
						.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
				}

				include($this->base_dir . '/manifest.php');
				if($is_upgrade && !empty($previous_version)){
					//check if the upgrade path exists
					if(!empty($upgrade_manifest)){
						if(!empty($upgrade_manifest['upgrade_paths'])){
							if(!empty($upgrade_manifest['upgrade_paths'][$previous_version])){
								$installdefs = 	$upgrade_manifest['upgrade_paths'][$previous_version];
							}else{
								$errors[] = 'No Upgrade Path Found in manifest.';
								$this->abort($errors);
							}//fi
						}//fi
					}//fi
				}//fi
				$this->id_name = $installdefs['id'];
				$this->installdefs = $installdefs;
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
				}

				foreach($tasks as $task){
					$this->$task();
					if(!$this->silent){
						$current_step++;
						update_progress_bar('install', $current_step, $total_steps);
					}
				}
				$this->install_beans($this->installed_modules);
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $total_steps, $total_steps);
				}
				if(isset($installdefs['custom_fields'])){
					$this->log(translate('LBL_MI_IN_CUSTOMFIELD'));
					$this->install_custom_fields($installdefs['custom_fields']);
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
					echo '</div>';
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
					echo '</div>';
				}
				$selectedActions = array(
			'clearTpls',
			'clearJsFiles',
			'clearDashlets',
			'clearVardefs',
			'clearJsLangFiles',
			'rebuildAuditTables',
			'repairDatabase',
		);
				VardefManager::clearVardef();
				global $beanList, $beanFiles, $moduleList;
				if (file_exists('custom/application/Ext/Include/modules.ext.php'))
				{
				    include('custom/application/Ext/Include/modules.ext.php');
				}
				require_once("modules/Administration/upgrade_custom_relationships.php");
            	upgrade_custom_relationships($this->installed_modules);
				$this->rebuild_all(true);
				require_once('modules/Administration/QuickRepairAndRebuild.php');
				$rac = new RepairAndClear();
				$rac->repairAndClearAll($selectedActions, $this->installed_modules,true, false);
				$this->rebuild_relationships();
				UpdateSystemTabs('Add',$this->tab_modules);
                //Clear out all the langauge cache files.
                clearAllJsAndJsLangFilesWithoutOutput();
                $cache_key = 'app_list_strings.'.$GLOBALS['current_language'];
                sugar_cache_clear($cache_key );
                sugar_cache_reset();

				//clear the unified_search_module.php file
	            require_once('modules/Home/UnifiedSearchAdvanced.php');
	            UnifiedSearchAdvanced::unlinkUnifiedSearchModulesFile();

				$this->log('<br><b>' . translate('LBL_MI_COMPLETE') . '</b>');
		}else{
			die("No \$installdefs Defined In $this->base_dir/manifest.php");
		}

	}

	function install_user_prefs($module, $hide_from_user=false){
		UserPreference::updateAllUserPrefs('display_tabs', $module, '', true, !$hide_from_user);
		UserPreference::updateAllUserPrefs('hide_tabs', $module, '', true, $hide_from_user);
		UserPreference::updateAllUserPrefs('remove_tabs', $module, '', true, $hide_from_user);
	}
	function uninstall_user_prefs($module){
		UserPreference::updateAllUserPrefs('display_tabs', $module, '', true, true);
		UserPreference::updateAllUserPrefs('hide_tabs', $module, '', true, true);
		UserPreference::updateAllUserPrefs('remove_tabs', $module, '', true, true);
	}

	function pre_execute(){
		require_once($this->base_dir . '/manifest.php');
		if(isset($this->installdefs['pre_execute']) && is_array($this->installdefs['pre_execute'])){
			foreach($this->installdefs['pre_execute'] as $includefile){
				require_once(str_replace('<basepath>', $this->base_dir, $includefile));
			}
		}
	}

	function post_execute(){
		require_once($this->base_dir . '/manifest.php');
		if(isset($this->installdefs['post_execute']) && is_array($this->installdefs['post_execute'])){
			foreach($this->installdefs['post_execute'] as $includefile){
				require_once(str_replace('<basepath>', $this->base_dir, $includefile));
			}
		}
	}

	function pre_uninstall(){
		require_once($this->base_dir . '/manifest.php');
		if(isset($this->installdefs['pre_uninstall']) && is_array($this->installdefs['pre_uninstall'])){
			foreach($this->installdefs['pre_uninstall'] as $includefile){
				require_once(str_replace('<basepath>', $this->base_dir, $includefile));
			}
		}
	}

	function post_uninstall(){
		require_once($this->base_dir . '/manifest.php');
		if(isset($this->installdefs['post_uninstall']) && is_array($this->installdefs['post_uninstall'])){
			foreach($this->installdefs['post_uninstall'] as $includefile){
				require_once(str_replace('<basepath>', $this->base_dir, $includefile));
			}
		}
	}

	/*
     * ModuleInstaller->install_copy gets the copy section of installdefs in the manifest and calls copy_path to copy each path (file or directory) to its final location
     * (specified as from and to in the manifest), replacing <basepath> by the base_dir value passed in to install.
     */
	function install_copy(){
		if(isset($this->installdefs['copy'])){
			/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:11 PM */
			$backup_path = clean_path( remove_file_extension(urldecode($_REQUEST['install_file']))."-restore" );
			/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
			foreach($this->installdefs['copy'] as $cp){
				$GLOBALS['log']->debug("Copying ..." . $cp['from'].  " to " .$cp['to'] );
				/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:11 PM */
				//$this->copy_path($cp['from'], $cp['to']);
				$this->copy_path($cp['from'], $cp['to'], $backup_path);
				/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
			}
			//here we should get the module list again as we could have copied something to the modules dir
			$this->modules = get_module_dir_list();
		}
	}
	function uninstall_copy(){
		if(!empty($this->installdefs['copy'])){
					foreach($this->installdefs['copy'] as $cp){
						$cp['to'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['to']));
						$cp['from'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['from']));
						$GLOBALS['log']->debug('Unlink ' . $cp['to']);
				/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:11 PM */
						//rmdir_recursive($cp['to']);

						$backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore/".$cp['to'] );
						$this->uninstall_new_files($cp, $backup_path);
						$this->copy_path($backup_path, $cp['to'], $backup_path, true);
				/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
					}
					$backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore");
					if(file_exists($backup_path))
						rmdir_recursive($backup_path);
				}
	}


	/**
	 * Removes any files that were added by the loaded module. If the files already existed prior to install
	 * it will be handled by copy_path with the uninstall parameter.
	 *
	 */
	function uninstall_new_files($cp, $backup_path){
		$zip_files = $this->dir_get_files($cp['from'],$cp['from']);
		$backup_files = $this->dir_get_files($backup_path, $backup_path);
		foreach($zip_files as $k=>$v){
			//if it's not a backup then it is probably a new file but we'll check that it is not in the md5.files first
			if(!isset($backup_files[$k])){
				$to = $cp['to'] . $k;
				//if it's not a sugar file then we remove it otherwise we can't restor it
				if(!$this->ms->sugarFileExists($to)){
					$GLOBALS['log']->debug('ModuleInstaller[uninstall_new_file] deleting file ' . $to);
					if(file_exists($to)) {
					    unlink($to);
					}
				}else{
					$GLOBALS['log']->fatal('ModuleInstaller[uninstall_new_file] Could not remove file ' . $to . ' as no backup file was found to restore to');
				}
			}
		}
		//lets check if the directory is empty if it is we will delete it as well
		$files_remaining = $this->dir_file_count($cp['to']);
		if(file_exists($cp['to']) && $files_remaining == 0){
			$GLOBALS['log']->debug('ModuleInstaller[uninstall_new_file] deleting directory ' . $cp['to']);
			rmdir_recursive($cp['to']);
		}

	}

	/**
	 * Get directory where module's extensions go
	 * @param string $module Module name
	 */
    public function getExtDir($module)
    {
	    if($module == 'application') {
            return "custom/Extension/application/Ext";
        } else {
			return "custom/Extension/modules/$module/Ext";
        }
    }

	/**
	 * Install file(s) into Ext/ part
	 * @param string $section Name of the install file section
	 * @param string $extname Name in Ext directory
	 * @param string $module This extension belongs to a specific module
	 */
	public function installExt($section, $extname, $module = '')
	{
        if(isset($this->installdefs[$section])){
			$this->log(sprintf(translate("LBL_MI_IN_EXT"), $section));
			foreach($this->installdefs[$section] as $item){
			    if(isset($item['from'])) {
				    $from = str_replace('<basepath>', $this->base_dir, $item['from']);
			    } else {
			        $from = '';
			    }
				if(!empty($module)) {
				    $item['to_module'] = $module;
				}
				$GLOBALS['log']->debug("Installing section $section from $from for " .$item['to_module'] );
                if($item['to_module'] == 'application') {
                    $path = "custom/Extension/application/Ext/$extname";
                } else {
				    $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
                }
				if(!file_exists($path)){
					mkdir_recursive($path, true);
				}
				if(isset($item["name"])) {
				    $target = $item["name"];
				} else if (!empty($from)){
                    $target = basename($from, ".php");
                } else {
				    $target = $this->id_name;
				}
				if(!empty($from)) {
				    copy_recursive($from , "$path/$target.php");
				}
			}
		}
	}

	/**
	 * Uninstall file(s) into Ext/ part
	 * @param string $section Name of the install file section
	 * @param string $extname Name in Ext directory
	 * @param string $module This extension belongs to a specific module
	 */
	public function uninstallExt($section, $extname, $module = '')
	{
        if(isset($this->installdefs[$section])){
			$this->log(sprintf(translate("LBL_MI_UN_EXT"), $section));
			foreach($this->installdefs[$section] as $item){
			    if(isset($item['from'])) {
				    $from = str_replace('<basepath>', $this->base_dir, $item['from']);
			    } else {
			        $from = '';
			    }
			    if(!empty($module)) {
				    $item['to_module'] = $module;
				}
				$GLOBALS['log']->debug("Uninstalling section $section from $from for " .$item['to_module'] );
                if($item['to_module'] == 'application') {
                    $path = "custom/Extension/application/Ext/$extname";
                } else {
				    $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
                }
				if(isset($item["name"])) {
				    $target = $item["name"];
				} else if (!empty($from)){
                    $target = basename($from, ".php");
                } else {
				    $target = $this->id_name;
				}
				$disabled_path = $path.'/'.DISABLED_PATH;
				if (file_exists("$path/$target.php")) {
				    rmdir_recursive("$path/$target.php");
                } else if (file_exists("$disabled_path/$target.php")) {
                    rmdir_recursive("$disabled_path/$target.php");
				} else if (!empty($from) && file_exists($path . '/'. basename($from))) {
				    rmdir_recursive( $path . '/'. basename($from));
                } else if (!empty($from) && file_exists($disabled_path . '/'. basename($from))) {
					rmdir_recursive( $disabled_path . '/'. basename($from));
				}
		    }
	    }
	}

	/**
     * Rebuild generic extension
     * @param string $ext Extension directory
     * @param string $filename Target filename
     */
	public function rebuildExt($ext, $filename)
	{
            $this->log(translate('LBL_MI_REBUILDING') . " $ext...");
			$this->merge_files("Ext/$ext/", $filename);
	}

	/**
	 * Disable generic extension
	 * @param string $section Install file section name
	 * @param string $extname Extension directory
 	 * @param string $module This extension belongs to a specific module
	 */
	public function disableExt($section, $extname, $module = '')
	{
		if(isset($this->installdefs[$section])) {
			foreach($this->installdefs[$section] as $item) {
			    if(isset($item['from'])) {
				    $from = str_replace('<basepath>', $this->base_dir, $item['from']);
			    } else {
			        $from = '';
			    }
				if(!empty($module)) {
				    $item['to_module'] = $module;
				}
				$GLOBALS['log']->debug("Disabling $extname ... from $from for " .$item['to_module']);
                if($item['to_module'] == 'application') {
                    $path = "custom/Extension/application/Ext/$extname";
                } else {
				    $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
                }
				if(isset($item["name"])) {
				    $target = $item["name"];
				} else if (!empty($from)){
                    $target = basename($from, ".php");
                }else {
				    $target = $this->id_name;
				}
				$disabled_path = $path.'/'.DISABLED_PATH;
                if (file_exists("$path/$target.php")) {
					mkdir_recursive($disabled_path, true);
					rename("$path/$target.php", "$disabled_path/$target.php");
				} else if (!empty($from) && file_exists($path . '/'. basename($from))) {
					mkdir_recursive($disabled_path, true);
				    rename( $path . '/'. basename($from), $disabled_path.'/'. basename($from));
				}
			}
		}
	}

	/**
	 * Enable generic extension
	 * @param string $section Install file section name
	 * @param string $extname Extension directory
 	 * @param string $module This extension belongs to a specific module
	 */
	public function enableExt($section, $extname, $module = '')
	{
		if(isset($this->installdefs[$section])) {
			foreach($this->installdefs[$section] as $item) {
			    if(isset($item['from'])) {
				    $from = str_replace('<basepath>', $this->base_dir, $item['from']);
			    } else {
			        $from = '';
			    }
			    if(!empty($module)) {
				    $item['to_module'] = $module;
				}
				$GLOBALS['log']->debug("Enabling $extname ... from $from for " .$item['to_module']);

                if($item['to_module'] == 'application') {
                    $path = "custom/Extension/application/Ext/$extname";
                } else {
				    $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
                }
				if(isset($item["name"])) {
				    $target = $item["name"];
				} else if (!empty($from)){
                    $target = basename($from, ".php");
                } else {
				    $target = $this->id_name;
				}
				if(!file_exists($path)) {
				    mkdir_recursive($path, true);
				}
                $disabled_path = $path.'/'.DISABLED_PATH;
				if (file_exists("$disabled_path/$target.php")) {
					rename("$disabled_path/$target.php",  "$path/$target.php");
				}
				if (!empty($from) && file_exists($disabled_path . '/'. basename($from))) {
					rename($disabled_path.'/'. basename($from),  $path . '/'. basename($from));
				}
			}
		}
    }

    /**
     * Method removes module from global search configurations
     *
     * return bool
     */
    public function uninstall_global_search()
    {
        if (empty($this->installdefs['beans']))
        {
            return true;
        }

        if (is_file('custom/modules/unified_search_modules_display.php') == false)
        {
            return true;
        }

        $user = new User();
        $users = get_user_array();
        $unified_search_modules_display = array();
        require('custom/modules/unified_search_modules_display.php');

        foreach($this->installdefs['beans'] as $beanDefs)
        {
            if (array_key_exists($beanDefs['module'], $unified_search_modules_display) == false)
            {
                continue;
            }
            unset($unified_search_modules_display[$beanDefs['module']]);
            foreach($users as $userId => $userName)
            {
                if (empty($userId))
                {
                    continue;
                }
                $user->retrieve($userId);
                $prefs = $user->getPreference('globalSearch', 'search');
                if (array_key_exists($beanDefs['module'], $prefs) == false)
                {
                    continue;
                }
                unset($prefs[$beanDefs['module']]);
                $user->setPreference('globalSearch', $prefs, 0, 'search');
                $user->savePreferencesToDB();
            }
        }

        if (write_array_to_file("unified_search_modules_display", $unified_search_modules_display, 'custom/modules/unified_search_modules_display.php') == false)
        {
            global $app_strings;
            $msg = string_format($app_strings['ERR_FILE_WRITE'], array('custom/modules/unified_search_modules_display.php'));
            $GLOBALS['log']->error($msg);
            throw new Exception($msg);
            return false;
        }
        return true;
    }

    /**
     * Method enables module in global search configurations by disabled_module_visible key
     *
     * return bool
     */
    public function enable_global_search()
    {
        if (empty($this->installdefs['beans']))
        {
            return true;
        }

        if (is_file('custom/modules/unified_search_modules_display.php') == false)
        {
            return true;
        }

        $unified_search_modules_display = array();
        require('custom/modules/unified_search_modules_display.php');

        foreach($this->installdefs['beans'] as $beanDefs)
        {
            if (array_key_exists($beanDefs['module'], $unified_search_modules_display) == false)
            {
                continue;
            }
            if (isset($unified_search_modules_display[$beanDefs['module']]['disabled_module_visible']) == false)
            {
                continue;
            }
            $unified_search_modules_display[$beanDefs['module']]['visible'] = $unified_search_modules_display[$beanDefs['module']]['disabled_module_visible'];
            unset($unified_search_modules_display[$beanDefs['module']]['disabled_module_visible']);
        }

        if (write_array_to_file("unified_search_modules_display", $unified_search_modules_display, 'custom/modules/unified_search_modules_display.php') == false)
        {
            global $app_strings;
            $msg = string_format($app_strings['ERR_FILE_WRITE'], array('custom/modules/unified_search_modules_display.php'));
            $GLOBALS['log']->error($msg);
            throw new Exception($msg);
            return false;
        }
        return true;
    }

    /**
     * Method disables module in global search configurations by disabled_module_visible key
     *
     * return bool
     */
    public function disable_global_search()
    {
        if (empty($this->installdefs['beans']))
        {
            return true;
        }

        if (is_file('custom/modules/unified_search_modules_display.php') == false)
        {
            return true;
        }

        $unified_search_modules_display = array();
        require('custom/modules/unified_search_modules_display.php');

        foreach($this->installdefs['beans'] as $beanDefs)
        {
            if (array_key_exists($beanDefs['module'], $unified_search_modules_display) == false)
            {
                continue;
            }
            if (isset($unified_search_modules_display[$beanDefs['module']]['visible']) == false)
            {
                continue;
            }
            $unified_search_modules_display[$beanDefs['module']]['disabled_module_visible'] = $unified_search_modules_display[$beanDefs['module']]['visible'];
            $unified_search_modules_display[$beanDefs['module']]['visible'] = false;
        }

        if (write_array_to_file("unified_search_modules_display", $unified_search_modules_display, 'custom/modules/unified_search_modules_display.php') == false)
        {
            global $app_strings;
            $msg = string_format($app_strings['ERR_FILE_WRITE'], array('custom/modules/unified_search_modules_display.php'));
            $GLOBALS['log']->error($msg);
            throw new Exception($msg);
            return false;
        }
        return true;
    }

    public function install_extensions()
	{
	    foreach($this->extensions as $extname => $ext) {
	        $install = "install_$extname";
	        if(method_exists($this, $install)) {
	            // non-standard function
                $this->$install();
	        } else {
	            if(!empty($ext["section"])) {
	                $module = isset($ext['module'])?$ext['module']:'';
	                $this->installExt($ext["section"], $ext["extdir"], $module);
	            }
	        }
	    }
	    $this->rebuild_extensions();
	}

	public function uninstall_extensions()
	{
	    foreach($this->extensions as $extname => $ext) {
	        $func = "uninstall_$extname";
	        if(method_exists($this, $func)) {
	            // non-standard function
                $this->$func();
	        } else {
	            if(!empty($ext["section"])) {
	                $module = isset($ext['module'])?$ext['module']:'';
	                $this->uninstallExt($ext["section"], $ext["extdir"], $module);
	            }
	        }
	    }
	    $this->rebuild_extensions();
	}

	public function rebuild_extensions()
	{
	    foreach($this->extensions as $extname => $ext) {
	        $func = "rebuild_$extname";
	        if(method_exists($this, $func)) {
	            // non-standard function
                $this->$func();
	        } else {
    	        $this->rebuildExt($ext["extdir"], $ext["file"]);
	        }
	    }
	}

	public function disable_extensions()
	{
	    foreach($this->extensions as $extname => $ext) {
	        $func = "disable_$extname";
	        if(method_exists($this, $func)) {
	            // non-standard install
                $this->$func();
	        } else {
	            if(!empty($ext["section"])) {
	                $module = isset($ext['module'])?$ext['module']:'';
	                $this->disableExt($ext["section"], $ext["extdir"], $module);
	            }
	        }
	    }
	    $this->rebuild_extensions();
	}

	public function enable_extensions()
	{
	    foreach($this->extensions as $extname => $ext) {
	        $func = "enable_$extname";
	        if(method_exists($this, $func)) {
	            // non-standard install
                $this->$func();
	        } else {
	            if(!empty($ext["section"])) {
	                $module = isset($ext['module'])?$ext['module']:'';
	                $this->enableExt($ext["section"], $ext["extdir"], $module);
	            }
	        }
	    }
	    $this->rebuild_extensions();
	}

	function install_dashlets()
	{
        if(isset($this->installdefs['dashlets'])){
			foreach($this->installdefs['dashlets'] as $cp){
				$this->log(translate('LBL_MI_IN_DASHLETS') . $cp['name']);
				$cp['from'] = str_replace('<basepath>', $this->base_dir, $cp['from']);
				$path = 'custom/modules/Home/Dashlets/' . $cp['name'] . '/';
				$GLOBALS['log']->debug("Installing Dashlet " . $cp['name'] . "..." . $cp['from'] );
				if(!file_exists($path)){
					mkdir_recursive($path, true);
				}
				copy_recursive($cp['from'] , $path);
			}
			include('modules/Administration/RebuildDashlets.php');

		}
	}

	function uninstall_dashlets(){
        if(isset($this->installdefs['dashlets'])){
			foreach($this->installdefs['dashlets'] as $cp){
				$this->log(translate('LBL_MI_UN_DASHLETS') . $cp['name']);
				$path = 'custom/modules/Home/Dashlets/' . $cp['name'];
				$GLOBALS['log']->debug('Unlink ' .$path);
				if (file_exists($path))
					rmdir_recursive($path);
			}
			include('modules/Administration/RebuildDashlets.php');
		}
	}


	function install_images(){
        if(isset($this->installdefs['image_dir'])){
			$this->log( translate('LBL_MI_IN_IMAGES') );
			$this->copy_path($this->installdefs['image_dir'] , 'custom/themes');

		}
	}

	function install_dcactions(){
		if(isset($this->installdefs['dcaction'])){
			$this->log(translate('LBL_MI_IN_MENUS'));
			foreach($this->installdefs['dcaction'] as $action){
				$action['from'] = str_replace('<basepath>', $this->base_dir, $action['from']);
				$GLOBALS['log']->debug("Installing DCActions ..." . $action['from']);
				$path = 'custom/Extension/application/Ext/DashletContainer/Containers';
				if(!file_exists($path)){
					mkdir_recursive($path, true);
				}
				copy_recursive($action['from'] , $path . '/'. $this->id_name . '.php');
			}
			$this->rebuild_dashletcontainers();
		}
	}

	function uninstall_dcactions(){
        if(isset($this->installdefs['dcaction'])){
			$this->log(translate('LBL_MI_UN_MENUS'));
			foreach($this->installdefs['dcaction'] as $action){
				$action['from'] = str_replace('<basepath>', $this->base_dir, $action['from']);
				$GLOBALS['log']->debug("Uninstalling DCActions ..." . $action['from'] );
				$path = 'custom/Extension/application/Ext/DashletContainer/Containers';
				if (is_file($path . '/'. $this->id_name . '.php'))
				{
					rmdir_recursive( $path . '/'. $this->id_name . '.php');
				}
				else if (is_file($path . '/'. DISABLED_PATH . '/'. $this->id_name . '.php'))
				{
					rmdir_recursive( $path . '/'. DISABLED_PATH . '/'. $this->id_name . '.php');
				}
			}
			$this->rebuild_dashletcontainers();
		}
	}

	function install_connectors(){
        if(isset($this->installdefs['connectors'])){
        	foreach($this->installdefs['connectors'] as $cp){
				$this->log(translate('LBL_MI_IN_CONNECTORS') . $cp['name']);
				$dir = str_replace('_','/',$cp['name']);
				$cp['connector'] = str_replace('<basepath>', $this->base_dir, $cp['connector']);
				$source_path = 'custom/modules/Connectors/connectors/sources/' . $dir. '/';
				$GLOBALS['log']->debug("Installing Connector " . $cp['name'] . "..." . $cp['connector'] );
				if(!file_exists($source_path)){
					mkdir_recursive($source_path, true);
				}
				copy_recursive($cp['connector'] , $source_path);

				//Install optional formatter code if it is specified
				if(!empty($cp['formatter'])) {
					$cp['formatter'] = str_replace('<basepath>', $this->base_dir, $cp['formatter']);
					$formatter_path = 'custom/modules/Connectors/connectors/formatters/' . $dir. '/';
					if(!file_exists($formatter_path)){
						mkdir_recursive($formatter_path, true);
					}
					copy_recursive($cp['formatter'] , $formatter_path);
				}
			}
			require_once('include/connectors/utils/ConnectorUtils.php');
			ConnectorUtils::installSource($cp['name']);
		}

	}
	function uninstall_connectors(){
    	if(isset($this->installdefs['connectors'])){
    		foreach($this->installdefs['connectors'] as $cp){
				$this->log(translate('LBL_MI_UN_CONNECTORS') . $cp['name']);
				$dir = str_replace('_','/',$cp['name']);
				$source_path = 'custom/modules/Connectors/connectors/sources/' . $dir;
				$formatter_path = 'custom/modules/Connectors/connectors/formatters/' . $dir;
				$GLOBALS['log']->debug('Unlink ' .$source_path);
				rmdir_recursive($source_path);
				rmdir_recursive($formatter_path);
			}
			require_once('include/connectors/utils/ConnectorUtils.php');
			//ConnectorUtils::getConnectors(true);
			ConnectorUtils::uninstallSource($cp['name']);
		}
	}

	function install_vardef($from, $to_module)
	{
			$GLOBALS['log']->debug("Installing Vardefs ..." . $from .  " for " .$to_module);
			$path = 'custom/Extension/modules/' . $to_module. '/Ext/Vardefs';
			if($to_module == 'application'){
				$path ='custom/Extension/' . $to_module. '/Ext/Vardefs';
			}
			if(!file_exists($path)){
				mkdir_recursive($path, true);
			}
			copy_recursive($from , $path.'/'. basename($from));
	}

	function install_layoutdef($from, $to_module){
			$GLOBALS['log']->debug("Installing Layout Defs ..." . $from .  " for " .$to_module);
			$path = 'custom/Extension/modules/' . $to_module. '/Ext/Layoutdefs';
			if($to_module == 'application'){
				$path ='custom/Extension/' . $to_module. '/Ext/Layoutdefs';
			}
			if(!file_exists($path)){
				mkdir_recursive($path, true);
			}
			copy_recursive($from , $path.'/'. basename($from));
	}

    // Non-standard - needs special rebuild call
	function install_languages()
	{
        $languages = array();
        if(isset($this->installdefs['language']))
        {
            $this->log(translate('LBL_MI_IN_LANG') );
            foreach($this->installdefs['language'] as $packs)
            {
                $modules[]=$packs['to_module'];
                $languages[$packs['language']] = $packs['language'];
				$packs['from'] = str_replace('<basepath>', $this->base_dir, $packs['from']);
				$GLOBALS['log']->debug("Installing Language Pack ..." . $packs['from']  .  " for " .$packs['to_module']);
                $path = $this->getInstallLanguagesPath($packs);
                if (!file_exists(dirname($path))) {
                    mkdir_recursive(dirname($path), true);
                }
                copy_recursive($packs['from'], $path);

			}
			$this->rebuild_languages($languages, $modules);

		}
	}

    /**
     * Function return path to file where store label
     *
     * @param $packs
     * @return string
     */
    protected function getInstallLanguagesPath($packs)
    {
        $path = 'custom/Extension/modules/' . $packs['to_module']. '/Ext/Language';
        if($packs['to_module'] == 'application'){
            $path ='custom/Extension/' . $packs['to_module']. '/Ext/Language';
        }
        $path .= '/'.$packs['language'].'.'. $this->id_name . '.php';
        return $path;
    }

    // Non-standard, needs special rebuild
	function uninstall_languages(){
        $languages = array();
				if(isset($this->installdefs['language'])){
					$this->log(translate('LBL_MI_UN_LANG') );
					foreach($this->installdefs['language'] as $packs){
						$modules[]=$packs['to_module'];
						$languages[$packs['language']] = $packs['language'];
						$packs['from'] = str_replace('<basepath>', $this->base_dir, $packs['from']);
						$GLOBALS['log']->debug("Uninstalling Language Pack ..." . $packs['from']  .  " for " .$packs['to_module']);
						$path = 'custom/Extension/modules/' . $packs['to_module']. '/Ext/Language';
						if($packs['to_module'] == 'application'){
							$path ='custom/Extension/' . $packs['to_module']. '/Ext/Language';
						}
						if (is_file($path.'/'.$packs['language'].'.'. $this->id_name . '.php')) {
							rmdir_recursive( $path.'/'.$packs['language'].'.'. $this->id_name . '.php');
						} else if (is_file($path.'/'.DISABLED_PATH.'/'.$packs['language'].'.'. $this->id_name . '.php')) {
							rmdir_recursive($path.'/'.DISABLED_PATH.'/'.$packs['language'].'.'. $this->id_name . '.php');
						}
					}
					$this->rebuild_languages($languages, $modules);

				}
	}

    // Non-standard, needs special rebuild
	public function disable_languages()
	{
		if(isset($this->installdefs['language'])) {
		    $languages = $modules = array();
			foreach($this->installdefs['language'] as $item) {
				$from = str_replace('<basepath>', $this->base_dir, $item['from']);
				$GLOBALS['log']->debug("Disabling Language {$item['language']}... from $from for " .$item['to_module']);
				$modules[]=$item['to_module'];
				$languages[$item['language']] = $item['language'];
				if($item['to_module'] == 'application') {
                    $path = "custom/Extension/application/Ext/Language";
                } else {
				    $path = "custom/Extension/modules/{$item['to_module']}/Ext/Language";
                }
				if(isset($item["name"])) {
				    $target = $item["name"];
				} else {
				    $target = $this->id_name;
				}
				$target = "{$item['language']}.$target";

				$disabled_path = $path.'/'.DISABLED_PATH;
                if (file_exists("$path/$target.php")) {
					mkdir_recursive($disabled_path, true);
					rename("$path/$target.php", "$disabled_path/$target.php");
				} else if (file_exists($path . '/'. basename($from))) {
					mkdir_recursive($disabled_path, true);
				    rename( $path . '/'. basename($from), $disabled_path.'/'. basename($from));
				}
			}
			$this->rebuild_languages($languages, $modules);
		}
	}

    // Non-standard, needs special rebuild
	public function enable_languages()
	{
		if(isset($this->installdefs['language'])) {
			foreach($this->installdefs['language'] as $item) {
				$from = str_replace('<basepath>', $this->base_dir, $item['from']);
				$GLOBALS['log']->debug("Enabling Language {$item['language']}... from $from for " .$item['to_module']);
				$modules[]=$item['to_module'];
				$languages[$item['language']] = $item['language'];
				if(!empty($module)) {
				    $item['to_module'] = $module;
				}

                if($item['to_module'] == 'application') {
                    $path = "custom/Extension/application/Ext/Language";
                } else {
				    $path = "custom/Extension/modules/{$item['to_module']}/Ext/Language";
                }
				if(isset($item["name"])) {
				    $target = $item["name"];
				} else {
				    $target = $this->id_name;
				}
				$target = "{$item['language']}.$target";

				if(!file_exists($path)) {
				    mkdir_recursive($path, true);
				}
                $disabled_path = $path.'/'.DISABLED_PATH;
				if (file_exists("$disabled_path/$target.php")) {
					rename("$disabled_path/$target.php",  "$path/$target.php");
				}
				if (file_exists($disabled_path . '/'. basename($from))) {
					rename($disabled_path.'/'. basename($from),  $path . '/'. basename($from));
				}
			}
			$this->rebuild_languages($languages, $modules);
		}
    }

    // Functions for adding and removing logic hooks from uploaded files
    // Since one class/file can be used by multiple logic hooks, I'm not going to touch the file labeled in the logic_hook entry
    /* The module hook definition should look like this:
     $installdefs = array(
     ... blah blah ...
         'logic_hooks' => array(
             array('module'      => 'Accounts',
                   'hook'        => 'after_save',
                   'order'       => 99,
                   'description' => 'Account sample logic hook',
                   'file'        => 'modules/Sample/sample_account_logic_hook_file.php',
                   'class'       => 'SampleLogicClass',
                   'function'    => 'accountAfterSave',
             ),
         ),
     ... blah blah ...
     );
     */
    function enable_manifest_logichooks() {
        if(empty($this->installdefs['logic_hooks']) || !is_array($this->installdefs['logic_hooks'])) {
           return;
        }



        foreach($this->installdefs['logic_hooks'] as $hook ) {
            check_logic_hook_file($hook['module'], $hook['hook'], array($hook['order'], $hook['description'],  $hook['file'], $hook['class'], $hook['function']));
        }
    }

    function disable_manifest_logichooks() {
        if(empty($this->installdefs['logic_hooks']) || !is_array($this->installdefs['logic_hooks'])) {
            return;
        }

        foreach($this->installdefs['logic_hooks'] as $hook ) {
            remove_logic_hook($hook['module'], $hook['hook'], array($hook['order'], $hook['description'],  $hook['file'], $hook['class'], $hook['function']));
        }
    }

    /**
     * Check labels inside label files and remove them
     *
     * @param $basePath - path to files with labels
     * @param array $labelDefinitions - format like output from AbstractRelationship buildLabels()
     */
    public function uninstallLabels($basePath, $labelDefinitions)
    {

        foreach ($labelDefinitions as $definition) {

            $filename = $basePath . "{$definition['module']}.php";

            if (!file_exists($filename)) {
                continue;
            }

            $uninstalLabes = $this->getLabelsToUninstall($labelDefinitions);
            $this->uninstallLabel($uninstalLabes, $definition, $filename);
        }

    }

    /**
     * Check labels inside label file and remove them
     *
     * @param $uninstalLabes
     * @param $definition
     * @param $filename
     */
    protected function uninstallLabel($uninstalLabes, $definition, $filename)
    {
        $app_list_strings = array();
        $mod_strings = array();
        $stringsName = $definition['module'] == 'application' ? 'app_list_strings' : 'mod_strings';

        include($filename);
        if ('app_list_strings' == $stringsName) {
            $strings = $app_list_strings;
        } else {
            $strings = $mod_strings;
        }

        foreach ($uninstalLabes AS $label) {
            if (isset($strings[$label])) {
                unset($strings[$label]);
            }
        }

        if (count($strings)) {
            $this->saveContentToFile($filename, $stringsName, $strings);
        } else {
            unlink($filename);
        }
    }

    /**
     * Save labels that not need be uninstalled at this case
     *
     * @param $filename
     * @param $stringsName
     * @param $strings
     */
    protected function saveContentToFile($filename, $stringsName, $strings)
    {
        $fileContent = "<?php\n//THIS FILE IS AUTO GENERATED, DO NOT MODIFY\n";
        foreach ($strings as $key => $val) {
            $fileContent .= override_value_to_string_recursive2($stringsName, $key, $val);
        }
        sugar_file_put_contents($filename, $fileContent);
    }

    /**
     * Uninstall extend labels
     *
     * @param $labelDefinitions
     */
    public function uninstallExtLabels($labelDefinitions)
    {
        foreach ($labelDefinitions as $definition) {
            if (!isset($GLOBALS['sugar_config']['languages']) || !is_array($GLOBALS['sugar_config']['languages'])) {
                continue;
            }

            foreach (array_keys($GLOBALS['sugar_config']['languages']) AS $language) {
                $pathDef = array(
                    'language' => $language,
                    'to_module' => $definition['module']
                );
                $path = $this->getInstallLanguagesPath($pathDef);
                if (file_exists($path)) {
                    unlink($path);
                }
            }
        }

    }

    /**
     * Returns the names of the label(key 'system_label') from a multi-dimensional array $labelDefinitions
     *
     * @param $labelDefinitions
     * @return array of labels
     */
    protected function getLabelsToUninstall($labelDefinitions)
    {
        $labels = array();
        foreach($labelDefinitions AS $definition){
            $labels[] = $definition['system_label'];
        }
        return $labels;
    }

/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
	function copy_path($from, $to, $backup_path='', $uninstall=false){
	//function copy_path($from, $to){
/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
		$to = str_replace('<basepath>', $this->base_dir, $to);

		if(!$uninstall) {
		$from = str_replace('<basepath>', $this->base_dir, $from);
		$GLOBALS['log']->debug('Copy ' . $from);
		}
		else {
			$from = str_replace('<basepath>', $backup_path, $from);
			//$GLOBALS['log']->debug('Restore ' . $from);
		}
		$from = clean_path($from);
		$to = clean_path($to);

		$dir = dirname($to);
		//there are cases where if we need to create a directory in the root directory
		if($dir == '.' && is_dir($from)){
			$dir = $to;
		}
		if(!is_dir($dir))
			mkdir_recursive($dir, true);
/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
		if(empty($backup_path)) {
/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
		if(!copy_recursive($from, $to)){
			die('Failed to copy ' . $from. ' ' . $to);
		}
/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
		}
		elseif(!$this->copy_recursive_with_backup($from, $to, $backup_path, $uninstall)){
			die('Failed to copy ' . $from. ' to ' . $to);
		}
/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
	}

	function install_custom_fields($fields){
		global $beanList, $beanFiles;
		include('include/modules.php');
		require_once('modules/DynamicFields/FieldCases.php');
		foreach($fields as $field){
			$installed = false;
			if(isset($beanList[ $field['module']])){
				$class = $beanList[ $field['module']];
                if(!isset($field['ext4']))$field['ext4'] = '';
                if(!isset($field['mass_update']))$field['mass_update'] = 0;
                if(!isset($field['duplicate_merge']))$field['duplicate_merge'] = 0;
                if(!isset($field['help']))$field['help'] = '';

                //Merge contents of the sugar field extension if we copied one over
                if (file_exists("custom/Extension/modules/{$field['module']}/Ext/Vardefs/sugarfield_{$field['name']}.php"))
                {
                    $dictionary = array();
                    include ("custom/Extension/modules/{$field['module']}/Ext/Vardefs/sugarfield_{$field['name']}.php");
                    $obj = BeanFactory::getObjectName($field['module']);
                    if (!empty($dictionary[$obj]['fields'][$field['name']])) {
                        $field = array_merge($dictionary[$obj]['fields'][$field['name']], $field);
                    }
                }

                if(file_exists($beanFiles[$class])){
					require_once($beanFiles[$class]);
					$mod = new $class();
					$installed = true;
					$fieldObject = get_widget($field['type']);
					$fieldObject->populateFromRow($field);
					$mod->custom_fields->use_existing_labels =  true;
					$mod->custom_fields->addFieldObject($fieldObject);
				}
			}
			if(!$installed){
				$GLOBALS['log']->debug('Could not install custom field ' . $field['name'] . ' for module ' .  $field['module'] . ': Module does not exist');
			}
		}
	}

	function uninstall_custom_fields($fields){
		global $beanList, $beanFiles;
		require_once('modules/DynamicFields/DynamicField.php');
		$dyField = new DynamicField();

		foreach($fields as $field){
			$class = $beanList[ $field['module']];
			if(file_exists($beanFiles[$class])){
					require_once($beanFiles[$class]);
					$mod = new $class();
					$dyField->bean = $mod;
					$dyField->module = $field['module'];
					$dyField->deleteField($field['name']);
			}
		}
	}

        /*
     * ModuleInstaller->install_relationships calls install_relationship for every file included in the module package that defines a relationship, and then
     * writes a custom/Extension/application/Ext/TableDictionary/$module.php file containing an include_once for every relationship metadata file passed to install_relationship.
     * Next it calls install_vardef and install_layoutdef. Finally, it rebuilds the vardefs and layoutdefs (by calling merge_files as usual), and then calls merge_files to merge
     * everything in 'Ext/TableDictionary/' into 'tabledictionary.ext.php'
     */
    function install_relationships ()
    {
        if (isset ( $this->installdefs [ 'relationships' ] ))
        {
            $this->log ( translate ( 'LBL_MI_IN_RELATIONSHIPS' ) ) ;
            $str = "<?php \n //WARNING: The contents of this file are auto-generated\n" ;
            $save_table_dictionary = false ;

            if (! file_exists ( "custom/Extension/application/Ext/TableDictionary" ))
            {
                mkdir_recursive ( "custom/Extension/application/Ext/TableDictionary", true ) ;
            }

            foreach ( $this->installdefs [ 'relationships' ] as $key => $relationship )
            {
                $filename = basename ( $relationship [ 'meta_data' ] ) ;
                $this->copy_path ( $relationship [ 'meta_data' ], 'custom/metadata/' . $filename ) ;
                $this->install_relationship ( 'custom/metadata/' . $filename ) ;
                $save_table_dictionary = true ;

                if (! empty ( $relationship [ 'module_vardefs' ] ))
                {
                    $relationship [ 'module_vardefs' ] = str_replace ( '<basepath>', $this->base_dir, $relationship [ 'module_vardefs' ] ) ;
                    $this->install_vardef ( $relationship [ 'module_vardefs' ], $relationship [ 'module' ] ) ;
                }

                if (! empty ( $relationship [ 'module_layoutdefs' ] ))
                {
                    $relationship [ 'module_layoutdefs' ] = str_replace ( '<basepath>', $this->base_dir, $relationship [ 'module_layoutdefs' ] ) ;
                    $this->install_layoutdef ( $relationship [ 'module_layoutdefs' ], $relationship [ 'module' ] ) ;
                }

                $relName = strpos($filename, "MetaData") !== false ? substr($filename, 0, strlen($filename) - 12) : $filename;
                $out = sugar_fopen ( "custom/Extension/application/Ext/TableDictionary/$relName.php", 'w' ) ;
                fwrite ( $out, $str . "include('custom/metadata/$filename');\n\n?>" ) ;
                fclose ( $out ) ;
            }




            Relationship::delete_cache();
            $this->rebuild_vardefs () ;
            $this->rebuild_layoutdefs () ;
            if ($save_table_dictionary)
            {
                $this->rebuild_tabledictionary () ;
            }
            require_once("data/Relationships/RelationshipFactory.php");
            SugarRelationshipFactory::deleteCache();
        }
    }

	/*
     * Install_relationship obtains a set of relationship definitions from the filename passed in as a parameter.
     * For each definition it calls db->createTableParams to build the relationships table if it does not exist,
     * and SugarBean::createRelationshipMeta to add the relationship into the 'relationships' table.
     */
	function install_relationship($file)
	{
		$_REQUEST['moduleInstaller'] = true;
		if(!file_exists($file))
		{
			$GLOBALS['log']->debug( 'File does not exists : '.$file);
			return;
		}
		include($file);
		$rel_dictionary = $dictionary;
		foreach ($rel_dictionary as $rel_name => $rel_data)
   	    {
   	        $table = ''; // table is actually optional
   	        // check if we have a table definition - not all relationships require a join table
            if ( isset( $rel_data[ 'table' ] ) )
            {
                $table = $rel_data[ 'table' ];

                if(!$this->db->tableExists($table))
                {
                    $this->db->createTableParams($table, $rel_data[ 'fields' ], $rel_data[ 'indices' ]);
                }
            }

            if(!$this->silent)
                $GLOBALS['log']->debug("Processing relationship meta for ". $rel_name."...");
            SugarBean::createRelationshipMeta($rel_name, $this->db,$table,$rel_dictionary,'');
            Relationship::delete_cache();
            if(!$this->silent)
                $GLOBALS['log']->debug( 'done<br>');
        }
	}

	function install_layoutfields() {
		 if (!empty ( $this->installdefs [ 'layoutfields' ] ))
		 {
		 	foreach ( $this->installdefs [ 'layoutfields' ] as $fieldSet )
            {
		 		if (!empty($fieldSet['additional_fields']))
		 		{
		 			$this->addFieldsToLayout($fieldSet['additional_fields']);
		 		}
            }
		 }
	}

	function uninstall_layoutfields() {
		 if (!empty ( $this->installdefs [ 'layoutfields' ] ))
		 {
		 	foreach ( $this->installdefs [ 'layoutfields' ] as $fieldSet )
            {
		 		if (!empty($fieldSet['additional_fields']))
		 		{
		 			$this->removeFieldsFromLayout($fieldSet['additional_fields']);
		 		}
            }
		 }
	}

	function uninstall_relationship($file, $rel_dictionary = null){
        if ($rel_dictionary == null)
		{
			if(!file_exists($file)){
				$GLOBALS['log']->debug( 'File does not exists : '.$file);
				return;
			}
			include($file);
			$rel_dictionary = $dictionary;
		}

		foreach ($rel_dictionary as $rel_name => $rel_data)
		{
			if (!empty($rel_data['table'])){
				$table = $rel_data['table'];
			}
			else{
				$table = ' One-to-Many ';
			}

			if ($this->db->tableExists($table) && isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])
			{
				SugarBean::removeRelationshipMeta($rel_name, $this->db,$table,$rel_dictionary,'');
				$this->db->dropTableName($table);
				if(!$this->silent) $this->log( translate('LBL_MI_UN_RELATIONSHIPS_DROP') . $table);
			}

			//Delete Layout defs
			// check to see if we have any vardef or layoutdef entries to remove - must have a relationship['module'] parameter if we do
			if (!isset($rel_data[ 'module' ]))
				$mods = array(
					$rel_data['relationships'][$rel_name]['lhs_module'],
					$rel_data['relationships'][$rel_name]['rhs_module'],
				);
			else
				$mods = array($rel_data[ 'module' ]);

			$filename = "$rel_name.php";

			foreach($mods as $mod) {
				if ($mod != 'application' )  {
					$basepath = "custom/Extension/modules/$mod/Ext/";
				} else {
					$basepath = "custom/Extension/application/Ext/";
				}

				foreach (array($filename , "custom" . $filename, $rel_name ."_". $mod. ".php") as $fn) {
					//remove any vardefs
					$path = $basepath . "Vardefs/$fn" ;
					if (file_exists( $path ))
						rmdir_recursive( $path );

					//remove any layoutdefs
					$path = $basepath . "Layoutdefs/$fn" ;
					if( file_exists( $path ))
					{
						rmdir_recursive( $path );
					}
                    $path = $basepath . "WirelessLayoutdefs/$fn";
                    if (file_exists($path)) {
                        rmdir_recursive($path);
                    }
				}
                $relationships_path = 'custom/Extension/modules/relationships/';

                $relationships_dirs = array(
                    'layoutdefs',
                    'vardefs',
                    'wirelesslayoutdefs'
                );
                foreach ($relationships_dirs as $relationship_dir) {
                    $realtionship_file_path = $relationships_path . $relationship_dir . "/{$rel_name}_{$mod}.php";
                    if (file_exists($realtionship_file_path)) {
                        rmdir_recursive($realtionship_file_path);
                    }
                }
                if (file_exists($relationships_path . "relationships/{$rel_name}MetaData.php")) {
                    rmdir_recursive($relationships_path . "relationships/{$rel_name}MetaData.php");
                }
            }

			foreach (array($filename , "custom" . $filename, $rel_name ."_". $mod. ".php") as $fn) {
				// remove the table dictionary extension
				if ( file_exists("custom/Extension/application/Ext/TableDictionary/$fn"))
				    unlink("custom/Extension/application/Ext/TableDictionary/$fn");

				if (file_exists("custom/metadata/{$rel_name}MetaData.php"))
					unlink( "custom/metadata/{$rel_name}MetaData.php" );
			}
		}
	}

	function uninstall_relationships($include_studio_relationships = false){
		$relationships = array();

		//Find and remove studio created relationships.
		global $beanList, $beanFiles, $dictionary;
		//Load up the custom relationship definitions.
		if(file_exists('custom/application/Ext/TableDictionary/tabledictionary.ext.php')){
			include('custom/application/Ext/TableDictionary/tabledictionary.ext.php');
		}
		//Find all the relatioships/relate fields involving this module.
		$rels_to_remove = array();
		foreach($beanList as $mod => $bean) {
			//Some modules like cases have a bean name that doesn't match the object name
			$bean = BeanFactory::getObjectName($mod);
            VardefManager::loadVardef($mod, $bean);
			//We can skip modules that are in this package as they will be removed anyhow
			if (!in_array($mod, $this->modulesInPackage) && !empty($dictionary[$bean]) && !empty($dictionary[$bean]['fields']))
			{
				$field_defs = $dictionary[$bean]['fields'];
				foreach($field_defs as $field => $def)
				{
					//Weed out most fields first
					if (isset ($def['type']))
					{
						//Custom relationships created in the relationship editor
						if ($def['type'] == "link" && !empty($def['relationship']) && !empty($dictionary[$def['relationship']]))
						{
							$rel_name = $def['relationship'];

							$rel_def = $dictionary[$rel_name]['relationships'][$rel_name];

							//Check against mods to be removed.
							foreach($this->modulesInPackage as $removed_mod) {
								if ($rel_def['lhs_module'] == $removed_mod || $rel_def['rhs_module'] == $removed_mod )
								{
									$dictionary[$rel_name]['from_studio'] = true;
									$relationships[$rel_name] = $dictionary[$rel_name];
								}
							}
						}
						//Custom "relate" fields created in studio also need to be removed
						if ($def['type'] == 'relate' && isset($def['module'])) {
							foreach($this->modulesInPackage as $removed_mod) {
								if ($def['module'] == $removed_mod)
								{
									require_once 'modules/ModuleBuilder/Module/StudioModule.php' ;
									$studioMod = new StudioModule ( $mod );
									$studioMod->removeFieldFromLayouts( $field );
									if (isset($def['custom_module'])) {
										require_once ('modules/DynamicFields/DynamicField.php') ;
										require_once ($beanFiles [ $bean ]) ;
										$seed = new $bean ( ) ;
										$df = new DynamicField ( $mod ) ;
										$df->setup ( $seed ) ;
										//Need to load the entire field_meta_data for some field types
										$field_obj = $df->getFieldWidget($mod, $field);
										$field_obj->delete ( $df ) ;
									}
								}
							}
						}
					}
				}
			}
		}



		$this->uninstall_relationship(null, $relationships);

		if(isset($this->installdefs['relationships'])) {
			$relationships = $this->installdefs['relationships'];
			$this->log(translate('LBL_MI_UN_RELATIONSHIPS') );
			foreach($relationships as $relationship)
			{
				// remove the metadata entry
				$filename = basename ( $relationship['meta_data'] );
				$pathname = (file_exists("custom/metadata/$filename")) ? "custom/metadata/$filename" : "metadata/$filename" ;
				if(isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])
				$this->uninstall_relationship( $pathname );
				if (file_exists($pathname))
					unlink( $pathname );
			}
		}

		if (file_exists("custom/Extension/application/Ext/TableDictionary/{$this->id_name}.php"))
			unlink("custom/Extension/application/Ext/TableDictionary/{$this->id_name}.php");
		Relationship::delete_cache();
		$this->rebuild_tabledictionary();
	}




	function uninstall($base_dir){
		if(defined('TEMPLATE_URL'))SugarTemplateUtilities::disableCache();
		global $app_strings;
		$total_steps = 5; //min steps with no tasks
		$current_step = 0;
		$this->base_dir = $base_dir;
		$tasks = array(
			'pre_uninstall',
			'uninstall_relationships',
			'uninstall_copy',
			'uninstall_dcactions',
			'uninstall_dashlets',
			'uninstall_connectors',
			'uninstall_layoutfields',
		    'uninstall_extensions',
            'uninstall_global_search',
			'disable_manifest_logichooks',
			'post_uninstall',
		);
		$total_steps += count($tasks); //now the real number of steps
		if(file_exists($this->base_dir . '/manifest.php')){
				if(!$this->silent){
					$current_step++;
					display_progress_bar('install', $current_step, $total_steps);
					echo '<div id ="displayLoglink" ><a href="#" onclick="toggleDisplay(\'displayLog\')">'.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
				}

				global $moduleList;
				include($this->base_dir . '/manifest.php');
				$this->installdefs = $installdefs;
				$this->id_name = $this->installdefs['id'];
				$installed_modules = array();
				if(isset($this->installdefs['beans'])){
					foreach($this->installdefs['beans'] as $bean){

						$installed_modules[] = $bean['module'];
						$this->uninstall_user_prefs($bean['module']);
					}
					$this->modulesInPackage = $installed_modules;
					$this->uninstall_beans($installed_modules);
					$this->uninstall_customizations($installed_modules);
					if(!$this->silent){
						$current_step++;
						update_progress_bar('install', $total_steps, $total_steps);
					}
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
				}
				foreach($tasks as $task){
					$this->$task();
					if(!$this->silent){
						$current_step++;
						update_progress_bar('install', $current_step, $total_steps);
					}
				}
				if(isset($installdefs['custom_fields']) && (isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])){
					$this->log(translate('LBL_MI_UN_CUSTOMFIELD'));
					$this->uninstall_custom_fields($installdefs['custom_fields']);
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
					echo '</div>';
				}
				//since we are passing $silent = true to rebuildAll() in that method it will set $this->silent = true, so
				//we need to save the setting to set it back after rebuildAll() completes.
				$silentBak = $this->silent;
				$this->rebuild_all(true);
				$this->silent = $silentBak;

				//#27877, If the request from MB redeploy a custom module , we will not remove the ACL actions for this package.
				if( !isset($_REQUEST['action']) || $_REQUEST['action']!='DeployPackage' ){
					$this->remove_acl_actions();
				}
				//end

				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
					echo '</div>';
				}

				UpdateSystemTabs('Restore',$installed_modules);

	            //clear the unified_search_module.php file
	            require_once('modules/Home/UnifiedSearchAdvanced.php');
	            UnifiedSearchAdvanced::unlinkUnifiedSearchModulesFile();

				$this->log('<br><b>' . translate('LBL_MI_COMPLETE') . '</b>');
				if(!$this->silent){
					update_progress_bar('install', $total_steps, $total_steps);
				}
		}else{
			die("No manifest.php Defined In $this->base_dir/manifest.php");
		}
	}

	function rebuild_languages($languages = array(), $modules="")
	{
            foreach($languages as $language=>$value){
				$this->log(translate('LBL_MI_REBUILDING') . " Language...$language");
				$this->merge_files('Ext/Language/', $language.'.lang.ext.php', $language);
	            if($modules!=""){
	                foreach($modules as $module){
	                	LanguageManager::clearLanguageCache($module, $language);
	                }
	            }
			}
			sugar_cache_reset();
	}

	function rebuild_vardefs()
	{
	    $this->rebuildExt("Vardefs", 'vardefs.ext.php');
		sugar_cache_reset();
	}

	function rebuild_dashletcontainers(){
            $this->log(translate('LBL_MI_REBUILDING') . " DC Actions...");
			$this->merge_files('Ext/DashletContainer/Containers/', 'dcactions.ext.php');
	}

	function rebuild_tabledictionary()
	{
	    $this->rebuildExt("TableDictionary", 'tabledictionary.ext.php');
	}

	function rebuild_relationships() {
        if(!$this->silent) echo translate('LBL_MI_REBUILDING') . ' Relationships';
		$_REQUEST['silent'] = true;
		global $beanFiles;
		include('include/modules.php');
		include("modules/Administration/RebuildRelationship.php");
	}

	function remove_acl_actions() {
		global $beanFiles, $beanList, $current_user;
		include('include/modules.php');
		include("modules/ACL/remove_actions.php");
	}

	/**
	 * Wrapper call to modules/Administration/RepairIndex.php
	 */
	function repair_indices() {
		global $current_user,$beanFiles,$dictionary;
		$this->log(translate('LBL_MI_REPAIR_INDICES'));
		$_REQUEST['silent'] = true; // local var flagging echo'd output in repair script
		$_REQUEST['mode'] = 'execute'; // flag to just go ahead and run the script
		include("modules/Administration/RepairIndex.php");
	}

	/**
	 * Rebuilds the extension files found in custom/Extension
	 * @param boolean $silent
	 */
	function rebuild_all($silent=false){
		if(defined('TEMPLATE_URL'))SugarTemplateUtilities::disableCache();
		$this->silent=$silent;
		global $sugar_config;

		//Check for new module extensions
		$this->rebuild_modules();

		$this->rebuild_languages($sugar_config['languages']);
		$this->rebuild_extensions();
		$this->rebuild_dashletcontainers();
		$this->rebuild_relationships();
		$this->rebuild_tabledictionary();
        $this->reset_opcodes();
		sugar_cache_reset();
	}

	/*
     * ModuleInstaller->merge_files runs over the list of all modules already installed in /modules. For each $module it reads the contents of every file in
     * custom/Extension/modules/$module/<path> (_override files last) and concatenates them to custom/modules/$module/<path>/<file>.
     * Then it does the same thing in custom/Extension/application/<path>, concatenating those files and copying the result to custom/application/<path>/<file>
     */
	function merge_files($path, $name, $filter = '', $application = false){
		if(!$application){
		$GLOBALS['log']->debug( get_class($this)."->merge_files() : merging module files in custom/Extension/modules/<module>/$path to custom/modules/<module>/$path$name");
		foreach($this->modules as $module){
				//$GLOBALS['log']->debug("Merging Files for: ".$module);
				//$GLOBALS['log']->debug("Merging Files for path: ".$path);
				$extension = "<?php \n //WARNING: The contents of this file are auto-generated\n";
				$extpath = "modules/$module/$path";
				$module_install  = 'custom/Extension/'.$extpath;
				$shouldSave = false;
				if(is_dir($module_install)){
					$dir = dir($module_install);
					$shouldSave = true;
					$override = array();
					while($entry = $dir->read()){
						if((empty($filter) || substr_count($entry, $filter) > 0) && is_file($module_install.'/'.$entry)
						  && $entry != '.' && $entry != '..' && strtolower(substr($entry, -4)) == ".php")
						{
						     if (substr($entry, 0, 9) == '_override') {
						    	$override[] = $entry;
						    } else {
							    $file = file_get_contents($module_install . '/' . $entry);
							    $GLOBALS['log']->debug(get_class($this)."->merge_files(): found {$module_install}{$entry}") ;
							    $extension .= "\n". str_replace(array('<?php', '?>', '<?PHP', '<?'), array('','', '' ,'') , $file);
						    }
						}
					}
					foreach ($override as $entry) {
                        $file = file_get_contents($module_install . '/' . $entry);
                        $extension .= "\n". str_replace(array('<?php', '?>', '<?PHP', '<?'), array('','', '' ,'') , $file);
					}
				}
				$extension .= "\n?>";

				if($shouldSave){
					if(!file_exists("custom/$extpath")) {
					    mkdir_recursive("custom/$extpath", true);
				    }
					$out = sugar_fopen("custom/$extpath/$name", 'w');
					fwrite($out,$extension);
					fclose($out);
				}else{
					if(file_exists("custom/$extpath/$name")){
						unlink("custom/$extpath/$name");
					}
				}
			}

		}

		$GLOBALS['log']->debug("Merging application files for $name in $path");
		//Now the application stuff
		$extension = "<?php \n //WARNING: The contents of this file are auto-generated\n";
		$extpath = "application/$path";
		$module_install  = 'custom/Extension/'.$extpath;
		$shouldSave = false;
					if(is_dir($module_install)){
						$dir = dir($module_install);
						while($entry = $dir->read()){
								$shouldSave = true;
								if((empty($filter) || substr_count($entry, $filter) > 0) && is_file($module_install.'/'.$entry)
								  && $entry != '.' && $entry != '..' && strtolower(substr($entry, -4)) == ".php")
								{
									$file = file_get_contents($module_install . '/' . $entry);
									$extension .= "\n". str_replace(array('<?php', '?>', '<?PHP', '<?'), array('','', '' ,'') , $file);
								}
						}
					}
					$extension .= "\n?>";
					if($shouldSave){
						if(!file_exists("custom/$extpath")){
							mkdir_recursive("custom/$extpath", true);
						}
						$out = sugar_fopen("custom/$extpath/$name", 'w');
						fwrite($out,$extension);
						fclose($out);
					}else{
					if(file_exists("custom/$extpath/$name")){
						unlink("custom/$extpath/$name");
					}
				}

}

    function install_modules()
    {
	    $this->installed_modules = array();
		$this->tab_modules = array();
        if(isset($this->installdefs['beans'])){
			$str = "<?php \n //WARNING: The contents of this file are auto-generated\n";
			foreach($this->installdefs['beans'] as $bean){
				if(!empty($bean['module']) && !empty($bean['class']) && !empty($bean['path'])){
					$module = $bean['module'];
					$class = $bean['class'];
					$path = $bean['path'];

					$str .= "\$beanList['$module'] = '$class';\n";
					$str .= "\$beanFiles['$class'] = '$path';\n";
					if($bean['tab']){
						$str .= "\$moduleList[] = '$module';\n";
						$this->install_user_prefs($module, empty($bean['hide_by_default']));
						$this->tab_modules[] = $module;
					}else{
						$str .= "\$modules_exempt_from_availability_check['$module'] = '$module';\n";
						$str .= "\$report_include_modules['$module'] = '$module';\n";
						$str .= "\$modInvisList[] = '$module';\n";
					}
				    $this->installed_modules[] = $module;
				}else{
						$errors[] = 'Bean array not well defined.';
						$this->abort($errors);
				}
			}
			$str.= "\n?>";
			if(!file_exists("custom/Extension/application/Ext/Include")){
				mkdir_recursive("custom/Extension/application/Ext/Include", true);
			}
			file_put_contents("custom/Extension/application/Ext/Include/{$this->id_name}.php", $str);
        }
    }

    /*
     * ModuleInstaller->install_beans runs through the list of beans given, instantiates each bean, calls bean->create_tables, and then calls SugarBean::createRelationshipMeta for the
     * bean/module.
     */
	function install_beans($beans){
        include('include/modules.php');
		foreach($beans as $bean){
			$this->log( translate('LBL_MI_IN_BEAN') . " $bean");
			if(isset($beanList[$bean])){
				$class = $beanList[$bean];
				if(file_exists($beanFiles[$class])){
					require_once($beanFiles[$class]);
					$mod = new $class();
					//#30273
					if(is_subclass_of($mod, 'SugarBean')  && $mod->disable_vardefs == false ){
						$GLOBALS['log']->debug( "Creating Tables Bean : $bean");
						$mod->create_tables();
						SugarBean::createRelationshipMeta($mod->getObjectName(), $mod->db,$mod->table_name,'',$mod->module_dir);
					}
				}else{
					$GLOBALS['log']->debug( "File Does Not Exist:" . $beanFiles[$class] );
				}
			}
		}
	}

		function uninstall_beans($beans){
		include('include/modules.php');
        foreach($beans as $bean){
			$this->log( translate('LBL_MI_UN_BEAN') . " $bean");
			if(isset($beanList[$bean])){
				$class = $beanList[$bean];

				if(file_exists($beanFiles[$class])){
					require_once($beanFiles[$class]);
					$mod = new $class();

					if(is_subclass_of($mod, 'SugarBean')){
						$GLOBALS['log']->debug( "Drop Tables : $bean");
						if(isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])
							$mod->drop_tables();
					}
				}else{
					$GLOBALS['log']->debug( "File Does Not Exist:" . $beanFiles[$class] );
				}
			}
		}
	}

	/**
	 * Remove any customizations made within Studio while the module was installed.
	 */
	function uninstall_customizations($beans){
        foreach($beans as $bean){
			$dirs = array(
				'custom/modules/' . $bean,
				'custom/Extension/modules/' . $bean,
                'custom/working/modules/' . $bean
			);
        	foreach($dirs as $dir)
        	{
				if(is_dir($dir)){
					rmdir_recursive($dir);
				}
        	}
		}
	}

	function log($str){
		$GLOBALS['log']->debug('ModuleInstaller:'. $str);
		if(!$this->silent){
			echo $str . '<br>';
		}
	}

/* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:15:18 PM 	*/
function copy_recursive_with_backup( $source, $dest, $backup_path, $uninstall=false ) {
	if(is_file($source)) {
	    if($uninstall) {
		    $GLOBALS['log']->debug("Restoring ... " . $source.  " to " .$dest );
		    if(copy( $source, $dest)) {
			    if(is_writable($dest))
			    	sugar_touch( $dest, filemtime($source) );
		    	return(unlink($source));
	    	}
		    else {
		    	$GLOBALS['log']->debug( "Can't restore file: " . $source );
		    	return true;
	    	}
	    }
	    else {
			if(file_exists($dest)) {
				$rest = clean_path($backup_path."/$dest");
				if( !is_dir(dirname($rest)) )
					mkdir_recursive(dirname($rest), true);

				$GLOBALS['log']->debug("Backup ... " . $dest.  " to " .$rest );
				if(copy( $dest, $rest)) {
					if(is_writable($rest))
						sugar_touch( $rest, filemtime($dest) );
				}
				else {
					$GLOBALS['log']->debug( "Can't backup file: " . $dest );
				}
			}
			return( copy( $source, $dest ) );
		}
    }
    elseif(!is_dir($source)) {
	    if($uninstall) {
			if(is_file($dest))
				return(unlink($dest));
			else {
				//don't do anything we already cleaned up the files using uninstall_new_files
				return true;
			}
		}
		else
			return false;
	}

    if( !is_dir($dest) && !$uninstall){
        sugar_mkdir( $dest );
    }

    $status = true;

    $d = dir( $source );
    while( $f = $d->read() ){
        if( $f == "." || $f == ".." ){
            continue;
        }
        $status &= $this->copy_recursive_with_backup( "$source/$f", "$dest/$f", $backup_path, $uninstall );
    }
    $d->close();
    return( $status );
}

private function dir_get_files($path, $base_path){
	$files = array();
	if(!is_dir($path))return $files;
	$d = dir($path);
	while ($e = $d->read()){
		//ignore invisible files . .. ._MACOSX
		if(substr($e, 0, 1) == '.')continue;
		if(is_file($path . '/' . $e))$files[str_replace($base_path , '', $path . '/' . $e)] = str_replace($base_path , '', $path . '/' . $e);
		if(is_dir($path . '/' . $e))$files = array_merge($files, $this->dir_get_files($path . '/' . $e, $base_path));
	}
	$d->close();
	return $files;

}

private function dir_file_count($path){
	//if its a file then it has at least 1 file in the directory
	if(is_file($path)) return 1;
	if(!is_dir($path)) return 0;
	$d = dir($path);
	$count = 0;
	while ($e = $d->read()){
		//ignore invisible files . .. ._MACOSX
		if(substr($e, 0, 1) == '.')continue;
		if(is_file($path . '/' . $e))$count++;
		if(is_dir($path . '/' . $e))$count += $this->dir_file_count($path . '/' . $e);
	}
	$d->close();
	return $count;


}
/* END - RESTORE POINT - by MR. MILK August 31, 2005 02:15:34 PM */


	/**
	 * Static function which allows a module developer to abort their progress, pass in an array of errors and
	 * redirect back to the main module loader page
	 *
	 * @param errors	an array of error messages which will be displayed on the
	 * 					main module loader page once it is loaded.
	 */
	function abort($errors = array()){
		//set the errors onto the session so we can display them one the moduler loader page loads
		$_SESSION['MODULEINSTALLER_ERRORS'] = $errors;
		echo '<META HTTP-EQUIV="Refresh" content="0;url=index.php?module=Administration&action=UpgradeWizard&view=module">';
		die();
		//header('Location: index.php?module=Administration&action=UpgradeWizard&view=module');
	}

	/**
	 * Return the set of errors stored in the SESSION
	 *
	 * @return an array of errors
	 */
	static function getErrors(){
		if(!empty($_SESSION['MODULEINSTALLER_ERRORS'])){
			$errors = $_SESSION['MODULEINSTALLER_ERRORS'];
			unset($_SESSION['MODULEINSTALLER_ERRORS']);
			return $errors;
		}
		else
			return null;
	}

	/*
     * Add any fields to the DetailView and EditView of the appropriate modules
     * Only add into deployed modules, as addFieldsToUndeployedLayouts has done this already for undeployed modules (and the admin might have edited the layouts already)
     * @param array $layoutAdditions  An array of module => fieldname
     * return null
     */
	function addFieldsToLayout($layoutAdditions) {
	require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;

        // these modules either lack editviews/detailviews or use custom mechanisms for the editview/detailview.
        // In either case, we don't want to attempt to add a relate field to them
        // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
        $invalidModules = array ( 'emails' , 'kbdocuments' ) ;

        foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
        {
            if ( ! in_array( strtolower ( $deployedModuleName ) , $invalidModules ) )
            {
                foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
                {
                    $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . ": adding $fieldName to $view layout for module $deployedModuleName" ) ;
                    $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
                    $parser->addField ( array ( 'name' => $fieldName ) ) ;
                    $parser->handleSave ( false ) ;
                }
            }
        }

	}

	function removeFieldsFromLayout($layoutAdditions) {
	require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;

        // these modules either lack editviews/detailviews or use custom mechanisms for the editview/detailview.
        // In either case, we don't want to attempt to add a relate field to them
        // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
        $invalidModules = array ( 'emails' , 'kbdocuments' ) ;

        foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
        {
            if ( ! in_array( strtolower ( $deployedModuleName ) , $invalidModules ) )
            {
                foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
                {
                    $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . ": adding $fieldName to $view layout for module $deployedModuleName" ) ;
                    $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
                    $parser->removeField ( $fieldName ) ;
                    $parser->handleSave ( false ) ;
                }
            }
        }

	}

	///////////////////
	//********** DISABLE/ENABLE FUNCTIONS
	///////////////////
	function enable($base_dir, $is_upgrade = false, $previous_version = ''){
		global $app_strings;
		$this->base_dir = $base_dir;
		$total_steps = 3; //minimum number of steps with no tasks
		$current_step = 0;
		$tasks = array(
								'enable_copy',
								'enable_dashlets',
								'enable_relationships',
		                        'enable_extensions',
                                'enable_global_search',
		                        'enable_manifest_logichooks',
								'reset_opcodes',
		);
		$total_steps += count($tasks);
		if(file_exists($this->base_dir . '/manifest.php')){
				if(!$this->silent){
					$current_step++;
					display_progress_bar('install', $current_step, $total_steps);
					echo '<div id ="displayLoglink" ><a href="#" onclick="toggleDisplay(\'displayLog\')">'.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
				}

				require_once($this->base_dir . '/manifest.php');
				if($is_upgrade && !empty($previous_version)){
					//check if the upgrade path exists
					if(!empty($upgrade_manifest)){
						if(!empty($upgrade_manifest['upgrade_paths'])){
							if(!empty($upgrade_manifest['upgrade_paths'][$previous_version])){
								$installdefs = 	$upgrade_manifest['upgrade_paths'][$previous_version];
							}else{
								$errors[] = 'No Upgrade Path Found in manifest.';
								$this->abort($errors);
							}//fi
						}//fi
					}//fi
				}//fi
				$this->id_name = $installdefs['id'];
				$this->installdefs = $installdefs;
				$installed_modules = array();
				if(isset($installdefs['beans'])){
					foreach($this->installdefs['beans'] as $bean){
						$installed_modules[] = $bean['module'];
					}
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
				}

				foreach($tasks as $task){
					$this->$task();
					if(!$this->silent){
						$current_step++;
						update_progress_bar('install', $current_step, $total_steps);
					}
				}

				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
					echo '</div>';
				}
				UpdateSystemTabs('Add',$installed_modules);
				$GLOBALS['log']->debug('Complete');

		}else{
			die("No \$installdefs Defined In $this->base_dir/manifest.php");
		}

	}
	function disable($base_dir){
		global $app_strings;
		$total_steps = 3; //min steps with no tasks
		$current_step = 0;
		$this->base_dir = $base_dir;
		$tasks = array(
							'disable_copy',
							'disable_dashlets',
							'disable_relationships',
		                    'disable_extensions',
                            'disable_global_search',
							'disable_manifest_logichooks',
							'reset_opcodes',
							);
		$total_steps += count($tasks); //now the real number of steps
		if(file_exists($this->base_dir . '/manifest.php')){
				if(!$this->silent){
					$current_step++;
					display_progress_bar('install', $current_step, $total_steps);
					echo '<div id ="displayLoglink" ><a href="#" onclick="toggleDisplay(\'displayLog\')">'.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
				}

				require_once($this->base_dir . '/manifest.php');
				$this->installdefs = $installdefs;
				$this->id_name = $this->installdefs['id'];
				$installed_modules = array();
				if(isset($this->installdefs['beans'])){
					foreach($this->installdefs['beans'] as $bean){
						$installed_modules[] = $bean['module'];
					}
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
				}
				foreach($tasks as $task){
					$this->$task();
					if(!$this->silent){
						$current_step++;
						update_progress_bar('install', $current_step, $total_steps);
					}
				}
				if(!$this->silent){
					$current_step++;
					update_progress_bar('install', $current_step, $total_steps);
					echo '</div>';
				}
			UpdateSystemTabs('Restore',$installed_modules);

		}else{
			die("No manifest.php Defined In $this->base_dir/manifest.php");
		}
	}

	function enable_vardef($to_module)
	{
	    $this->enableExt("vardefs", "Vardefs", $to_module);
	}

	function enable_layoutdef($to_module)
	{
	    $this->enableExt("layoutdefs", "Layoutdefs", $to_module);
	}

	function enable_relationships(){
		if(isset($this->installdefs['relationships'])){
			$str = "<?php \n //WARNING: The contents of this file are auto-generated\n";
			$save_table_dictionary = false;
			foreach($this->installdefs['relationships'] as $relationship){
                $filename	=basename($relationship['meta_data']);

				$save_table_dictionary  = true;
				$str .= "include_once('metadata/$filename');\n";
				if (empty($relationship['module']))
				    continue;

				if(!empty($relationship['module_vardefs'])){
					$this->enable_vardef($relationship['module']);
				}
				if(!empty($relationship['module_layoutdefs'])){
					$this->enable_layoutdef($relationship['module']);
				}
			}
			$this->rebuild_vardefs();
			$this->rebuild_layoutdefs();
			if($save_table_dictionary){
				if(!file_exists("custom/Extension/application/Ext/TableDictionary")){
					mkdir_recursive("custom/Extension/application/Ext/TableDictionary", true);
				}
				if (file_exists("custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH."/$this->id_name.php"))
				   rename("custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH."/$this->id_name.php", "custom/Extension/application/Ext/TableDictionary/$this->id_name.php");
				$this->rebuild_tabledictionary();
			}
		}
	}

	function disable_relationships($action = 'disable'){
		if(isset($this->installdefs['relationships'])){
			foreach($this->installdefs['relationships'] as $relationship){
				$filename = basename($relationship['meta_data']);
                $relName = substr($filename, -12) == "MetaData.php" ? substr($filename,0,strlen($filename) - 12) : "";
				if (empty($relationship['module']) && empty($relName))
                	continue;

				//remove the vardefs
				if (empty($relName))
					$path = 'custom/Extension/modules/' . $relationship['module']. '/Ext/Vardefs';
				if(!empty($relationship['module']) && $relationship['module'] == 'application'){
					$path ='custom/Extension/' . $relationship['module']. '/Ext/Vardefs';
				}
				if(!empty($relationship['module_vardefs']) && file_exists($path . '/'. $this->id_name . '.php')){
					mkdir_recursive($path . '/'.DISABLED_PATH, true);
					rename( $path . '/'. $this->id_name . '.php', $path . '/'.DISABLED_PATH.'/'. $this->id_name . '.php');
				}
				//remove the layoutdefs
				if ( !empty($relationship['module']) ) {
                    $path = 'custom/Extension/modules/' . $relationship['module']. '/Ext/Layoutdefs';
                    if($relationship['module'] == 'application'){
                        $path ='custom/Extension/' . $relationship['module']. '/Ext/Layoutdefs';
                    }
				}

				if(!empty($relationship['module_layoutdefs']) && file_exists($path . '/'. $this->id_name . '.php')){
					mkdir_recursive($path . '/'.DISABLED_PATH, true);
					rename( $path . '/'. $this->id_name . '.php', $path . '/'.DISABLED_PATH.'/'. $this->id_name . '.php');
				}

			}
			if(file_exists("custom/Extension/application/Ext/TableDictionary/$this->id_name.php")){
				mkdir_recursive("custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH, true);
				rename("custom/Extension/application/Ext/TableDictionary/$this->id_name.php", "custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH."/$this->id_name.php");
			}
			$this->rebuild_tabledictionary();
			$this->rebuild_vardefs();
			$this->rebuild_layoutdefs();
		}
	}

	function enable_dashlets(){
		if(isset($this->installdefs['dashlets'])){
			foreach($this->installdefs['dashlets'] as $cp){
				$cp['from'] = str_replace('<basepath>', $this->base_dir, $cp['from']);
				$path = 'custom/modules/Home/Dashlets/' . $cp['name'] . '/';
				$disabled_path = 'custom/modules/Home/'.DISABLED_PATH.'Dashlets/' . $cp['name'];
				$GLOBALS['log']->debug("Enabling Dashlet " . $cp['name'] . "..." . $cp['from'] );
				if (file_exists($disabled_path))
				{
					rename($disabled_path,  $path);
				}
			}
			include('modules/Administration/RebuildDashlets.php');

		}
	}

	function disable_dashlets(){
		if(isset($this->installdefs['dashlets'])){
					foreach($this->installdefs['dashlets'] as $cp){
						$path = 'custom/modules/Home/Dashlets/' . $cp['name'];
						$disabled_path = 'custom/modules/Home/'.DISABLED_PATH.'Dashlets/' . $cp['name'];
						$GLOBALS['log']->debug('Disabling ' .$path);
						if (file_exists($path))
						{
							mkdir_recursive('custom/modules/Home/'.DISABLED_PATH.'Dashlets/', true);
							rename( $path, $disabled_path);
						}
					}
					include('modules/Administration/RebuildDashlets.php');
				}
	}

	function enable_copy(){
		//copy files back onto file system. first perform md5 check to determine if anything has been modified
		//here we should just go through the files in the -restore directory and copy those back
		if(isset($GLOBALS['mi_overwrite_files']) && $GLOBALS['mi_overwrite_files']){
			if(!empty($this->installdefs['copy'])){
				foreach($this->installdefs['copy'] as $cp){
					$cp['to'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['to']));
					$backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore/".$cp['to'] );

					//check if this file exists in the -restore directory
					if(file_exists($backup_path)){
						//since the file exists, then we want do an md5 of the install version and the file system version
						//if(is_file($backup_path) && md5_file($backup_path) == md5_file($cp['to'])){
							//since the files are the same then we can safely move back from the -restore
							//directory into the file system
							$GLOBALS['log']->debug("ENABLE COPY:: FROM: ".$cp['from']. " TO: ".$cp['to']);
							$this->copy_path($cp['from'], $cp['to']);
						/*}else{
							//since they are not equal then we need to prompt the user
						}*/
					}//fi
				}//rof
			}//fi
		}//fi
	}

	function disable_copy(){
		//when we disable we want to copy the -restore files back into the file system
		//but we should check the version in the module install against the version on the file system
		//if they match then we can copy the file back, but otherwise we should ask the user.

//		$GLOBALS['log']->debug('ModuleInstaller.php->disable_copy()');
		if(isset($GLOBALS['mi_overwrite_files']) && $GLOBALS['mi_overwrite_files']){
//		$GLOBALS['log']->debug('ModuleInstaller.php->disable_copy():mi_overwrite_files=true');
			if(!empty($this->installdefs['copy'])){
//				$GLOBALS['log']->debug('ModuleInstaller.php->disable_copy(): installdefs not empty');
				foreach($this->installdefs['copy'] as $cp){
					$cp['to'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['to']));
					$backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore/".$cp['to'] ); // bug 16966 tyoung - replaced missing assignment to $backup_path
					//check if this file exists in the -restore directory
//					$GLOBALS['log']->debug("ModuleInstaller.php->disable_copy(): backup_path=".$backup_path);
					if(file_exists($backup_path)){
						//since the file exists, then we want do an md5 of the install version and the file system version
						$from = str_replace('<basepath>', $this->base_dir, $cp['from']);

						//if(is_file($from) && md5_file($from) == md5_file($cp['to'])){
							//since the files are the same then we can safely move back from the -restore
							//directory into the file system
							$GLOBALS['log']->debug("DISABLE COPY:: FROM: ".$backup_path. " TO: ".$cp['to']);
							$this->copy_path($backup_path, $cp['to']);
						/*}else{
							//since they are not equal then we need to prompt the user
						}*/
					}//fi
				}//rof
			}//fi
		}//fi
	}

	public function reset_opcodes()
    {
        /* Bug 39354 - added function_exists check. Not optimal fix, but safe nonetheless.
         * This is for the upgrade to 6.1 from pre 6.1, since the utils files haven't been updated to 6.1 when this is called,
         * but this file has been updated to 6.1
         */
        if(function_exists('sugar_clean_opcodes')){
            sugar_clean_opcodes();
        }
    }

    /**
     * BC implementation to provide specific calls to extensions
     */
    public function __call($name, $args)
    {
        $nameparts = explode('_', $name);
        // name is something_something
        if(count($nameparts) == 2 && isset($this->extensions[$nameparts[1]])) {
            $ext = $this->extensions[$nameparts[1]];
            switch($nameparts[0]) {
                case 'enable':
                    return $this->enableExt($ext['section'], $ext['extdir']);
                case 'disable':
                    return $this->disableExt($ext['section'], $ext['extdir']);
                case 'install':
                    return $this->installExt($ext['section'], $ext['extdir']);
                case 'uninstall':
                    return $this->uninstallExt($ext['section'], $ext['extdir']);
                case 'rebuild':
                    return $this->rebuildExt($ext['extdir'], $ext['file']);
            }
        }
        sugar_die("Unknown method ModuleInstaller::$name called");
    }

}

    function UpdateSystemTabs($action, $installed_modules){
        require_once("modules/MySettings/TabController.php");
        $controller = new TabController();
        $isSystemTabsInDB = $controller->is_system_tabs_in_db();
        if ($isSystemTabsInDB && !empty($installed_modules))
        {
            global $moduleList;
            switch ($action)
            {
                case 'Restore' :
                    $currentTabs = $controller->get_system_tabs();
                    foreach ($installed_modules as $module)
                    {
                        if(in_array($module, $currentTabs)){
                            unset($currentTabs[$module]);
                        }
                    }
                    $controller->set_system_tabs($currentTabs);;
                    break;
                case 'Add' :
                    $currentTabs = $controller->get_system_tabs();
                    foreach ($installed_modules as $module)
                    {
                        if(!in_array($module, $currentTabs)){
                            $currentTabs[$module] = $module;
                        }
                    }
                    $controller->set_system_tabs($currentTabs);
                default:
                    break;
            }
    }

}
