]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - ModuleInstall/ModuleInstaller.php
Release 6.3.0beta1
[Github/sugarcrm.git] / ModuleInstall / ModuleInstaller.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 /*
40  * ModuleInstaller - takes an installation package from files in the custom/Extension/X directories, and moves them into custom/X to install them.
41  * If a directory has multiple files they are concatenated together.
42  * Relevant directories (X) are Layoutdefs, Vardefs, Include (bean stuff), Language, TableDictionary (relationships)
43  *
44  * Installation steps that involve more than just copying files:
45  * 1. installing custom fields - calls bean->custom_fields->addField
46  * 2. installing relationships - calls createTableParams to build the relationship table, and createRelationshipMeta to add the relationship to the relationship table
47  * 3. rebuilding the relationships - at almost the last step in install(), calls modules/Administration/RebuildRelationship.php
48  * 4. repair indices - uses "modules/Administration/RepairIndex.php";
49  */
50
51
52
53 require_once('include/utils/progress_bar_utils.php');
54
55 require_once('ModuleInstall/ModuleScanner.php');
56 define('DISABLED_PATH', 'Disabled');
57
58 class ModuleInstaller{
59         var $modules = array();
60         var $silent = false;
61         var $base_dir  = '';
62         var $modulesInPackage = array();
63         public $disabled_path = DISABLED_PATH;
64
65         function ModuleInstaller(){
66                 $this->ms = new ModuleScanner();
67                 $this->modules = get_module_dir_list();
68                 $this->db = & DBManagerFactory::getInstance();
69         include("ModuleInstall/extensions.php");
70         $this->extensions = $extensions;
71         }
72
73    /*
74     * 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
75     * 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
76     * and moduleList - and then calls ModuleInstaller->merge_files('Ext/Include', 'modules.ext.php', '', true) to merge the individual module files into a combined file
77     * /custom/Extension/application/Ext/Include/modules.ext.php (which now contains a list of all $beanList, $beanFiles and $moduleList for all extension modules) -
78     * this file modules.ext.php is included at the end of modules.php.
79     *
80     * 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.
81     */
82         function install($base_dir, $is_upgrade = false, $previous_version = ''){
83                 if(defined('TEMPLATE_URL'))SugarTemplateUtilities::disableCache();
84                 if(!empty($GLOBALS['sugar_config']['moduleInstaller']['packageScan'])){
85                         $this->ms->scanPackage($base_dir);
86                         if($this->ms->hasIssues()){
87                                 $this->ms->displayIssues();
88                                 sugar_cleanup(true);
89                         }
90                 }
91
92                 global $app_strings, $mod_strings;
93                 $this->base_dir = $base_dir;
94                 $total_steps = 5; //minimum number of steps with no tasks
95                 $current_step = 0;
96                 $tasks = array(
97                         'pre_execute',
98                         'install_copy',
99                     'install_extensions',
100                         'install_images',
101                         'install_dcactions',
102                         'install_dashlets',
103                         'install_connectors',
104                         'install_layoutfields',
105                         'install_relationships',
106             'enable_manifest_logichooks',
107                         'post_execute',
108                         'reset_opcodes',
109                 );
110
111                 $total_steps += count($tasks);
112                 if(file_exists($this->base_dir . '/manifest.php')){
113                                 if(!$this->silent){
114                                         $current_step++;
115                                         display_progress_bar('install', $current_step, $total_steps);
116                                         echo '<div id ="displayLoglink" ><a href="#" onclick="document.getElementById(\'displayLog\').style.display=\'\'">'
117                                                 .$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
118                                 }
119
120                                 include($this->base_dir . '/manifest.php');
121                                 if($is_upgrade && !empty($previous_version)){
122                                         //check if the upgrade path exists
123                                         if(!empty($upgrade_manifest)){
124                                                 if(!empty($upgrade_manifest['upgrade_paths'])){
125                                                         if(!empty($upgrade_manifest['upgrade_paths'][$previous_version])){
126                                                                 $installdefs =  $upgrade_manifest['upgrade_paths'][$previous_version];
127                                                         }else{
128                                                                 $errors[] = 'No Upgrade Path Found in manifest.';
129                                                                 $this->abort($errors);
130                                                         }//fi
131                                                 }//fi
132                                         }//fi
133                                 }//fi
134                                 $this->id_name = $installdefs['id'];
135                                 $this->installdefs = $installdefs;
136                                 if(!$this->silent){
137                                         $current_step++;
138                                         update_progress_bar('install', $current_step, $total_steps);
139                                 }
140
141                                 foreach($tasks as $task){
142                                         $this->$task();
143                                         if(!$this->silent){
144                                                 $current_step++;
145                                                 update_progress_bar('install', $current_step, $total_steps);
146                                         }
147                                 }
148                                 $this->install_beans($this->installed_modules);
149                                 if(!$this->silent){
150                                         $current_step++;
151                                         update_progress_bar('install', $total_steps, $total_steps);
152                                 }
153                                 if(isset($installdefs['custom_fields'])){
154                                         $this->log(translate('LBL_MI_IN_CUSTOMFIELD'));
155                                         $this->install_custom_fields($installdefs['custom_fields']);
156                                 }
157                                 if(!$this->silent){
158                                         $current_step++;
159                                         update_progress_bar('install', $current_step, $total_steps);
160                                         echo '</div>';
161                                 }
162                                 if(!$this->silent){
163                                         $current_step++;
164                                         update_progress_bar('install', $current_step, $total_steps);
165                                         echo '</div>';
166                                 }
167                                 $selectedActions = array(
168                         'clearTpls',
169                         'clearJsFiles',
170                         'clearDashlets',
171                         'clearVardefs',
172                         'clearJsLangFiles',
173                         'rebuildAuditTables',
174                         'repairDatabase',
175                 );
176                                 VardefManager::clearVardef();
177                                 global $beanList, $beanFiles, $moduleList;
178                                 if (file_exists('custom/application/Ext/Include/modules.ext.php'))
179                                 {
180                                     include('custom/application/Ext/Include/modules.ext.php');
181                                 }
182                                 require_once("modules/Administration/upgrade_custom_relationships.php");
183                 upgrade_custom_relationships($this->installed_modules);
184                                 $this->rebuild_all(true);
185                                 require_once('modules/Administration/QuickRepairAndRebuild.php');
186                                 $rac = new RepairAndClear();
187                                 $rac->repairAndClearAll($selectedActions, $this->installed_modules,true, false);
188                                 $this->rebuild_relationships();
189                                 UpdateSystemTabs('Add',$this->tab_modules);
190
191                                 //clear the unified_search_module.php file
192                     require_once('modules/Home/UnifiedSearchAdvanced.php');
193                     UnifiedSearchAdvanced::unlinkUnifiedSearchModulesFile();
194
195                                 $this->log('<br><b>' . translate('LBL_MI_COMPLETE') . '</b>');
196                 }else{
197                         die("No \$installdefs Defined In $this->base_dir/manifest.php");
198                 }
199
200         }
201
202         function install_user_prefs($module, $hide_from_user=false){
203                 UserPreference::updateAllUserPrefs('display_tabs', $module, '', true, !$hide_from_user);
204                 UserPreference::updateAllUserPrefs('hide_tabs', $module, '', true, $hide_from_user);
205                 UserPreference::updateAllUserPrefs('remove_tabs', $module, '', true, $hide_from_user);
206         }
207         function uninstall_user_prefs($module){
208                 UserPreference::updateAllUserPrefs('display_tabs', $module, '', true, true);
209                 UserPreference::updateAllUserPrefs('hide_tabs', $module, '', true, true);
210                 UserPreference::updateAllUserPrefs('remove_tabs', $module, '', true, true);
211         }
212
213         function pre_execute(){
214                 require_once($this->base_dir . '/manifest.php');
215                 if(isset($this->installdefs['pre_execute']) && is_array($this->installdefs['pre_execute'])){
216                         foreach($this->installdefs['pre_execute'] as $includefile){
217                                 require_once(str_replace('<basepath>', $this->base_dir, $includefile));
218                         }
219                 }
220         }
221
222         function post_execute(){
223                 require_once($this->base_dir . '/manifest.php');
224                 if(isset($this->installdefs['post_execute']) && is_array($this->installdefs['post_execute'])){
225                         foreach($this->installdefs['post_execute'] as $includefile){
226                                 require_once(str_replace('<basepath>', $this->base_dir, $includefile));
227                         }
228                 }
229         }
230
231         function pre_uninstall(){
232                 require_once($this->base_dir . '/manifest.php');
233                 if(isset($this->installdefs['pre_uninstall']) && is_array($this->installdefs['pre_uninstall'])){
234                         foreach($this->installdefs['pre_uninstall'] as $includefile){
235                                 require_once(str_replace('<basepath>', $this->base_dir, $includefile));
236                         }
237                 }
238         }
239
240         function post_uninstall(){
241                 require_once($this->base_dir . '/manifest.php');
242                 if(isset($this->installdefs['post_uninstall']) && is_array($this->installdefs['post_uninstall'])){
243                         foreach($this->installdefs['post_uninstall'] as $includefile){
244                                 require_once(str_replace('<basepath>', $this->base_dir, $includefile));
245                         }
246                 }
247         }
248
249         /*
250      * 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
251      * (specified as from and to in the manifest), replacing <basepath> by the base_dir value passed in to install.
252      */
253         function install_copy(){
254                 if(isset($this->installdefs['copy'])){
255                         /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:11 PM */
256                         $backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore" );
257                         /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
258                         foreach($this->installdefs['copy'] as $cp){
259                                 $GLOBALS['log']->debug("Copying ..." . $cp['from'].  " to " .$cp['to'] );
260                                 /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:11 PM */
261                                 //$this->copy_path($cp['from'], $cp['to']);
262                                 $this->copy_path($cp['from'], $cp['to'], $backup_path);
263                                 /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
264                         }
265                         //here we should get the module list again as we could have copied something to the modules dir
266                         $this->modules = get_module_dir_list();
267                 }
268         }
269         function uninstall_copy(){
270                 if(!empty($this->installdefs['copy'])){
271                                         foreach($this->installdefs['copy'] as $cp){
272                                                 $cp['to'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['to']));
273                                                 $cp['from'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['from']));
274                                                 $GLOBALS['log']->debug('Unlink ' . $cp['to']);
275                                 /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:11 PM */
276                                                 //rmdir_recursive($cp['to']);
277
278                                                 $backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore/".$cp['to'] );
279                                                 $this->uninstall_new_files($cp, $backup_path);
280                                                 $this->copy_path($backup_path, $cp['to'], $backup_path, true);
281                                 /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
282                                         }
283                                         $backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore");
284                                         if(file_exists($backup_path))
285                                                 rmdir_recursive($backup_path);
286                                 }
287         }
288
289
290         /**
291          * Removes any files that were added by the loaded module. If the files already existed prior to install
292          * it will be handled by copy_path with the uninstall parameter.
293          *
294          */
295         function uninstall_new_files($cp, $backup_path){
296                 $zip_files = $this->dir_get_files($cp['from'],$cp['from']);
297                 $backup_files = $this->dir_get_files($backup_path, $backup_path);
298                 foreach($zip_files as $k=>$v){
299                         //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
300                         if(!isset($backup_files[$k])){
301                                 $to = $cp['to'] . $k;
302                                 //if it's not a sugar file then we remove it otherwise we can't restor it
303                                 if(!$this->ms->sugarFileExists($to)){
304                                         $GLOBALS['log']->debug('ModuleInstaller[uninstall_new_file] deleting file ' . $to);
305                                         if(file_exists($to)) {
306                                             unlink($to);
307                                         }
308                                 }else{
309                                         $GLOBALS['log']->fatal('ModuleInstaller[uninstall_new_file] Could not remove file ' . $to . ' as no backup file was found to restore to');
310                                 }
311                         }
312                 }
313                 //lets check if the directory is empty if it is we will delete it as well
314                 $files_remaining = $this->dir_file_count($cp['to']);
315                 if(file_exists($cp['to']) && $files_remaining == 0){
316                         $GLOBALS['log']->debug('ModuleInstaller[uninstall_new_file] deleting directory ' . $cp['to']);
317                         rmdir_recursive($cp['to']);
318                 }
319
320         }
321
322         /**
323          * Get directory where module's extensions go
324          * @param string $module Module name
325          */
326     public function getExtDir($module)
327     {
328             if($module == 'application') {
329             return "custom/Extension/application/Ext";
330         } else {
331                         return "custom/Extension/modules/$module/Ext";
332         }
333     }
334
335         /**
336          * Install file(s) into Ext/ part
337          * @param string $section Name of the install file section
338          * @param string $extname Name in Ext directory
339          * @param string $module This extension belongs to a specific module
340          */
341         public function installExt($section, $extname, $module = '')
342         {
343         if(isset($this->installdefs[$section])){
344                         $this->log(sprintf(translate("LBL_MI_IN_EXT"), $section));
345                         foreach($this->installdefs[$section] as $item){
346                             if(isset($item['from'])) {
347                                     $from = str_replace('<basepath>', $this->base_dir, $item['from']);
348                             } else {
349                                 $from = '';
350                             }
351                                 if(!empty($module)) {
352                                     $item['to_module'] = $module;
353                                 }
354                                 $GLOBALS['log']->debug("Installing section $section from $from for " .$item['to_module'] );
355                 if($item['to_module'] == 'application') {
356                     $path = "custom/Extension/application/Ext/$extname";
357                 } else {
358                                     $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
359                 }
360                                 if(!file_exists($path)){
361                                         mkdir_recursive($path, true);
362                                 }
363                                 if(isset($item["name"])) {
364                                     $target = $item["name"];
365                                 } else if (!empty($from)){
366                     $target = basename($from, ".php");
367                 } else {
368                                     $target = $this->id_name;
369                                 }
370                                 if(!empty($from)) {
371                                     copy_recursive($from , "$path/$target.php");
372                                 }
373                         }
374                 }
375         }
376
377         /**
378          * Uninstall file(s) into Ext/ part
379          * @param string $section Name of the install file section
380          * @param string $extname Name in Ext directory
381          * @param string $module This extension belongs to a specific module
382          */
383         public function uninstallExt($section, $extname, $module = '')
384         {
385         if(isset($this->installdefs[$section])){
386                         $this->log(sprintf(translate("LBL_MI_UN_EXT"), $section));
387                         foreach($this->installdefs[$section] as $item){
388                             if(isset($item['from'])) {
389                                     $from = str_replace('<basepath>', $this->base_dir, $item['from']);
390                             } else {
391                                 $from = '';
392                             }
393                             if(!empty($module)) {
394                                     $item['to_module'] = $module;
395                                 }
396                                 $GLOBALS['log']->debug("Uninstalling section $section from $from for " .$item['to_module'] );
397                 if($item['to_module'] == 'application') {
398                     $path = "custom/Extension/application/Ext/$extname";
399                 } else {
400                                     $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
401                 }
402                                 if(isset($item["name"])) {
403                                     $target = $item["name"];
404                                 } else if (!empty($from)){
405                     $target = basename($from, ".php");
406                 } else {
407                                     $target = $this->id_name;
408                                 }
409                                 $disabled_path = $path.'/'.DISABLED_PATH;
410                                 if (file_exists("$path/$target.php")) {
411                                     rmdir_recursive("$path/$target.php");
412                 } else if (file_exists("$disabled_path/$target.php")) {
413                     rmdir_recursive("$disabled_path/$target.php");
414                                 } else if (!empty($from) && file_exists($path . '/'. basename($from))) {
415                                     rmdir_recursive( $path . '/'. basename($from));
416                 } else if (!empty($from) && file_exists($disabled_path . '/'. basename($from))) {
417                                         rmdir_recursive( $disabled_path . '/'. basename($from));
418                                 }
419                     }
420             }
421         }
422
423         /**
424      * Rebuild generic extension
425      * @param string $ext Extension directory
426      * @param string $filename Target filename
427      */
428         public function rebuildExt($ext, $filename)
429         {
430             $this->log(translate('LBL_MI_REBUILDING') . " $ext...");
431                         $this->merge_files("Ext/$ext/", $filename);
432         }
433
434         /**
435          * Disable generic extension
436          * @param string $section Install file section name
437          * @param string $extname Extension directory
438          * @param string $module This extension belongs to a specific module
439          */
440         public function disableExt($section, $extname, $module = '')
441         {
442                 if(isset($this->installdefs[$section])) {
443                         foreach($this->installdefs[$section] as $item) {
444                             if(isset($item['from'])) {
445                                     $from = str_replace('<basepath>', $this->base_dir, $item['from']);
446                             } else {
447                                 $from = '';
448                             }
449                                 if(!empty($module)) {
450                                     $item['to_module'] = $module;
451                                 }
452                                 $GLOBALS['log']->debug("Disabling $extname ... from $from for " .$item['to_module']);
453                 if($item['to_module'] == 'application') {
454                     $path = "custom/Extension/application/Ext/$extname";
455                 } else {
456                                     $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
457                 }
458                                 if(isset($item["name"])) {
459                                     $target = $item["name"];
460                                 } else if (!empty($from)){
461                     $target = basename($from, ".php");
462                 }else {
463                                     $target = $this->id_name;
464                                 }
465                                 $disabled_path = $path.'/'.DISABLED_PATH;
466                 if (file_exists("$path/$target.php")) {
467                                         mkdir_recursive($disabled_path, true);
468                                         rename("$path/$target.php", "$disabled_path/$target.php");
469                                 } else if (!empty($from) && file_exists($path . '/'. basename($from))) {
470                                         mkdir_recursive($disabled_path, true);
471                                     rename( $path . '/'. basename($from), $disabled_path.'/'. basename($from));
472                                 }
473                         }
474                 }
475         }
476
477         /**
478          * Enable generic extension
479          * @param string $section Install file section name
480          * @param string $extname Extension directory
481          * @param string $module This extension belongs to a specific module
482          */
483         public function enableExt($section, $extname, $module = '')
484         {
485                 if(isset($this->installdefs[$section])) {
486                         foreach($this->installdefs[$section] as $item) {
487                             if(isset($item['from'])) {
488                                     $from = str_replace('<basepath>', $this->base_dir, $item['from']);
489                             } else {
490                                 $from = '';
491                             }
492                             if(!empty($module)) {
493                                     $item['to_module'] = $module;
494                                 }
495                                 $GLOBALS['log']->debug("Enabling $extname ... from $from for " .$item['to_module']);
496
497                 if($item['to_module'] == 'application') {
498                     $path = "custom/Extension/application/Ext/$extname";
499                 } else {
500                                     $path = "custom/Extension/modules/{$item['to_module']}/Ext/$extname";
501                 }
502                                 if(isset($item["name"])) {
503                                     $target = $item["name"];
504                                 } else if (!empty($from)){
505                     $target = basename($from, ".php");
506                 } else {
507                                     $target = $this->id_name;
508                                 }
509                                 if(!file_exists($path)) {
510                                     mkdir_recursive($path, true);
511                                 }
512                 $disabled_path = $path.'/'.DISABLED_PATH;
513                                 if (file_exists("$disabled_path/$target.php")) {
514                                         rename("$disabled_path/$target.php",  "$path/$target.php");
515                                 }
516                                 if (!empty($from) && file_exists($disabled_path . '/'. basename($from))) {
517                                         rename($disabled_path.'/'. basename($from),  $path . '/'. basename($from));
518                                 }
519                         }
520                 }
521     }
522
523         public function install_extensions()
524         {
525             foreach($this->extensions as $extname => $ext) {
526                 $install = "install_$extname";
527                 if(method_exists($this, $install)) {
528                     // non-standard function
529                 $this->$install();
530                 } else {
531                     if(!empty($ext["section"])) {
532                         $module = isset($ext['module'])?$ext['module']:'';
533                         $this->installExt($ext["section"], $ext["extdir"], $module);
534                     }
535                 }
536             }
537             $this->rebuild_extensions();
538         }
539
540         public function uninstall_extensions()
541         {
542             foreach($this->extensions as $extname => $ext) {
543                 $func = "uninstall_$extname";
544                 if(method_exists($this, $func)) {
545                     // non-standard function
546                 $this->$func();
547                 } else {
548                     if(!empty($ext["section"])) {
549                         $module = isset($ext['module'])?$ext['module']:'';
550                         $this->uninstallExt($ext["section"], $ext["extdir"], $module);
551                     }
552                 }
553             }
554             $this->rebuild_extensions();
555         }
556
557         public function rebuild_extensions()
558         {
559             foreach($this->extensions as $extname => $ext) {
560                 $func = "rebuild_$extname";
561                 if(method_exists($this, $func)) {
562                     // non-standard function
563                 $this->$func();
564                 } else {
565                 $this->rebuildExt($ext["extdir"], $ext["file"]);
566                 }
567             }
568         }
569
570         public function disable_extensions()
571         {
572             foreach($this->extensions as $extname => $ext) {
573                 $func = "disable_$extname";
574                 if(method_exists($this, $func)) {
575                     // non-standard install
576                 $this->$func();
577                 } else {
578                     if(!empty($ext["section"])) {
579                         $module = isset($ext['module'])?$ext['module']:'';
580                         $this->disableExt($ext["section"], $ext["extdir"], $module);
581                     }
582                 }
583             }
584             $this->rebuild_extensions();
585         }
586
587         public function enable_extensions()
588         {
589             foreach($this->extensions as $extname => $ext) {
590                 $func = "enable_$extname";
591                 if(method_exists($this, $func)) {
592                     // non-standard install
593                 $this->$func();
594                 } else {
595                     if(!empty($ext["section"])) {
596                         $module = isset($ext['module'])?$ext['module']:'';
597                         $this->enableExt($ext["section"], $ext["extdir"], $module);
598                     }
599                 }
600             }
601             $this->rebuild_extensions();
602         }
603
604         function install_dashlets()
605         {
606         if(isset($this->installdefs['dashlets'])){
607                         foreach($this->installdefs['dashlets'] as $cp){
608                                 $this->log(translate('LBL_MI_IN_DASHLETS') . $cp['name']);
609                                 $cp['from'] = str_replace('<basepath>', $this->base_dir, $cp['from']);
610                                 $path = 'custom/modules/Home/Dashlets/' . $cp['name'] . '/';
611                                 $GLOBALS['log']->debug("Installing Dashlet " . $cp['name'] . "..." . $cp['from'] );
612                                 if(!file_exists($path)){
613                                         mkdir_recursive($path, true);
614                                 }
615                                 copy_recursive($cp['from'] , $path);
616                         }
617                         include('modules/Administration/RebuildDashlets.php');
618
619                 }
620         }
621
622         function uninstall_dashlets(){
623         if(isset($this->installdefs['dashlets'])){
624                         foreach($this->installdefs['dashlets'] as $cp){
625                                 $this->log(translate('LBL_MI_UN_DASHLETS') . $cp['name']);
626                                 $path = 'custom/modules/Home/Dashlets/' . $cp['name'];
627                                 $GLOBALS['log']->debug('Unlink ' .$path);
628                                 if (file_exists($path))
629                                         rmdir_recursive($path);
630                         }
631                         include('modules/Administration/RebuildDashlets.php');
632                 }
633         }
634
635
636         function install_images(){
637         if(isset($this->installdefs['image_dir'])){
638                         $this->log( translate('LBL_MI_IN_IMAGES') );
639                         $this->copy_path($this->installdefs['image_dir'] , 'custom/themes');
640
641                 }
642         }
643
644         function install_dcactions(){
645                 if(isset($this->installdefs['dcaction'])){
646                         $this->log(translate('LBL_MI_IN_MENUS'));
647                         foreach($this->installdefs['dcaction'] as $action){
648                                 $action['from'] = str_replace('<basepath>', $this->base_dir, $action['from']);
649                                 $GLOBALS['log']->debug("Installing DCActions ..." . $action['from']);
650                                 $path = 'custom/Extension/application/Ext/DashletContainer/Containers';
651                                 if(!file_exists($path)){
652                                         mkdir_recursive($path, true);
653                                 }
654                                 copy_recursive($action['from'] , $path . '/'. $this->id_name . '.php');
655                         }
656                         $this->rebuild_dashletcontainers();
657                 }
658         }
659
660         function uninstall_dcactions(){
661         if(isset($this->installdefs['dcaction'])){
662                         $this->log(translate('LBL_MI_UN_MENUS'));
663                         foreach($this->installdefs['dcaction'] as $action){
664                                 $action['from'] = str_replace('<basepath>', $this->base_dir, $action['from']);
665                                 $GLOBALS['log']->debug("Uninstalling DCActions ..." . $action['from'] );
666                                 $path = 'custom/Extension/application/Ext/DashletContainer/Containers';
667                                 if (sugar_is_file($path . '/'. $this->id_name . '.php', 'w'))
668                                 {
669                                         rmdir_recursive( $path . '/'. $this->id_name . '.php');
670                                 }
671                                 else if (sugar_is_file($path . '/'. DISABLED_PATH . '/'. $this->id_name . '.php', 'w'))
672                                 {
673                                         rmdir_recursive( $path . '/'. DISABLED_PATH . '/'. $this->id_name . '.php');
674                                 }
675                         }
676                         $this->rebuild_dashletcontainers();
677                 }
678         }
679
680         function install_connectors(){
681         if(isset($this->installdefs['connectors'])){
682                 foreach($this->installdefs['connectors'] as $cp){
683                                 $this->log(translate('LBL_MI_IN_CONNECTORS') . $cp['name']);
684                                 $dir = str_replace('_','/',$cp['name']);
685                                 $cp['connector'] = str_replace('<basepath>', $this->base_dir, $cp['connector']);
686                                 $source_path = 'custom/modules/Connectors/connectors/sources/' . $dir. '/';
687                                 $GLOBALS['log']->debug("Installing Connector " . $cp['name'] . "..." . $cp['connector'] );
688                                 if(!file_exists($source_path)){
689                                         mkdir_recursive($source_path, true);
690                                 }
691                                 copy_recursive($cp['connector'] , $source_path);
692
693                                 //Install optional formatter code if it is specified
694                                 if(!empty($cp['formatter'])) {
695                                         $cp['formatter'] = str_replace('<basepath>', $this->base_dir, $cp['formatter']);
696                                         $formatter_path = 'custom/modules/Connectors/connectors/formatters/' . $dir. '/';
697                                         if(!file_exists($formatter_path)){
698                                                 mkdir_recursive($formatter_path, true);
699                                         }
700                                         copy_recursive($cp['formatter'] , $formatter_path);
701                                 }
702                         }
703                         require_once('include/connectors/utils/ConnectorUtils.php');
704                         ConnectorUtils::installSource($cp['name']);
705                 }
706
707         }
708         function uninstall_connectors(){
709         if(isset($this->installdefs['connectors'])){
710                 foreach($this->installdefs['connectors'] as $cp){
711                                 $this->log(translate('LBL_MI_UN_CONNECTORS') . $cp['name']);
712                                 $dir = str_replace('_','/',$cp['name']);
713                                 $source_path = 'custom/modules/Connectors/connectors/sources/' . $dir;
714                                 $formatter_path = 'custom/modules/Connectors/connectors/formatters/' . $dir;
715                                 $GLOBALS['log']->debug('Unlink ' .$source_path);
716                                 rmdir_recursive($source_path);
717                                 rmdir_recursive($formatter_path);
718                         }
719                         require_once('include/connectors/utils/ConnectorUtils.php');
720                         //ConnectorUtils::getConnectors(true);
721                         ConnectorUtils::uninstallSource($cp['name']);
722                 }
723         }
724
725         function install_vardef($from, $to_module)
726         {
727                         $GLOBALS['log']->debug("Installing Vardefs ..." . $from .  " for " .$to_module);
728                         $path = 'custom/Extension/modules/' . $to_module. '/Ext/Vardefs';
729                         if($to_module == 'application'){
730                                 $path ='custom/Extension/' . $to_module. '/Ext/Vardefs';
731                         }
732                         if(!file_exists($path)){
733                                 mkdir_recursive($path, true);
734                         }
735                         copy_recursive($from , $path.'/'. basename($from));
736         }
737
738         function install_layoutdef($from, $to_module){
739                         $GLOBALS['log']->debug("Installing Layout Defs ..." . $from .  " for " .$to_module);
740                         $path = 'custom/Extension/modules/' . $to_module. '/Ext/Layoutdefs';
741                         if($to_module == 'application'){
742                                 $path ='custom/Extension/' . $to_module. '/Ext/Layoutdefs';
743                         }
744                         if(!file_exists($path)){
745                                 mkdir_recursive($path, true);
746                         }
747                         copy_recursive($from , $path.'/'. basename($from));
748         }
749
750     // Non-standard - needs special rebuild call
751         function install_languages()
752         {
753         $languages = array();
754         if(isset($this->installdefs['language']))
755         {
756             $this->log(translate('LBL_MI_IN_LANG') );
757             foreach($this->installdefs['language'] as $packs)
758             {
759                 $modules[]=$packs['to_module'];
760                 $languages[$packs['language']] = $packs['language'];
761                                 $packs['from'] = str_replace('<basepath>', $this->base_dir, $packs['from']);
762                                 $GLOBALS['log']->debug("Installing Language Pack ..." . $packs['from']  .  " for " .$packs['to_module']);
763                             $path = 'custom/Extension/modules/' . $packs['to_module']. '/Ext/Language';
764                                 if($packs['to_module'] == 'application'){
765                                     $path ='custom/Extension/' . $packs['to_module']. '/Ext/Language';
766                                 }
767
768                                 if(!file_exists($path)){
769                                     mkdir_recursive($path, true);
770                 }
771                                 copy_recursive($packs['from'] , $path.'/'.$packs['language'].'.'. $this->id_name . '.php');
772                         }
773                         $this->rebuild_languages($languages, $modules);
774
775                 }
776         }
777
778     // Non-standard, needs special rebuild
779         function uninstall_languages(){
780         $languages = array();
781                                 if(isset($this->installdefs['language'])){
782                                         $this->log(translate('LBL_MI_UN_LANG') );
783                                         foreach($this->installdefs['language'] as $packs){
784                                                 $modules[]=$packs['to_module'];
785                                                 $languages[$packs['language']] = $packs['language'];
786                                                 $packs['from'] = str_replace('<basepath>', $this->base_dir, $packs['from']);
787                                                 $GLOBALS['log']->debug("Uninstalling Language Pack ..." . $packs['from']  .  " for " .$packs['to_module']);
788                                                 $path = 'custom/Extension/modules/' . $packs['to_module']. '/Ext/Language';
789                                                 if($packs['to_module'] == 'application'){
790                                                         $path ='custom/Extension/' . $packs['to_module']. '/Ext/Language';
791                                                 }
792                                                 if (sugar_is_file($path.'/'.$packs['language'].'.'. $this->id_name . '.php', 'w')) {
793                                                         rmdir_recursive( $path.'/'.$packs['language'].'.'. $this->id_name . '.php');
794                                                 } else if (sugar_is_file($path.'/'.DISABLED_PATH.'/'.$packs['language'].'.'. $this->id_name . '.php', 'w')) {
795                                                         rmdir_recursive($path.'/'.DISABLED_PATH.'/'.$packs['language'].'.'. $this->id_name . '.php', 'w');
796                                                 }
797                                         }
798                                         $this->rebuild_languages($languages, $modules);
799
800                                 }
801         }
802
803     // Non-standard, needs special rebuild
804         public function disable_languages()
805         {
806                 if(isset($this->installdefs['language'])) {
807                     $languages = $modules = array();
808                         foreach($this->installdefs['language'] as $item) {
809                                 $from = str_replace('<basepath>', $this->base_dir, $item['from']);
810                                 $GLOBALS['log']->debug("Disabling Language {$item['language']}... from $from for " .$item['to_module']);
811                                 $modules[]=$item['to_module'];
812                                 $languages[$item['language']] = $item['language'];
813                                 if($item['to_module'] == 'application') {
814                     $path = "custom/Extension/application/Ext/Language";
815                 } else {
816                                     $path = "custom/Extension/modules/{$item['to_module']}/Ext/Language";
817                 }
818                                 if(isset($item["name"])) {
819                                     $target = $item["name"];
820                                 } else {
821                                     $target = $this->id_name;
822                                 }
823                                 $target = "{$item['language']}.$target";
824
825                                 $disabled_path = $path.'/'.DISABLED_PATH;
826                 if (file_exists("$path/$target.php")) {
827                                         mkdir_recursive($disabled_path, true);
828                                         rename("$path/$target.php", "$disabled_path/$target.php");
829                                 } else if (file_exists($path . '/'. basename($from))) {
830                                         mkdir_recursive($disabled_path, true);
831                                     rename( $path . '/'. basename($from), $disabled_path.'/'. basename($from));
832                                 }
833                         }
834                         $this->rebuild_languages($languages, $modules);
835                 }
836         }
837
838     // Non-standard, needs special rebuild
839         public function enable_languages()
840         {
841                 if(isset($this->installdefs['language'])) {
842                         foreach($this->installdefs['language'] as $item) {
843                                 $from = str_replace('<basepath>', $this->base_dir, $item['from']);
844                                 $GLOBALS['log']->debug("Enabling Language {$item['language']}... from $from for " .$item['to_module']);
845                                 $modules[]=$item['to_module'];
846                                 $languages[$item['language']] = $item['language'];
847                                 if(!empty($module)) {
848                                     $item['to_module'] = $module;
849                                 }
850
851                 if($item['to_module'] == 'application') {
852                     $path = "custom/Extension/application/Ext/Language";
853                 } else {
854                                     $path = "custom/Extension/modules/{$item['to_module']}/Ext/Language";
855                 }
856                                 if(isset($item["name"])) {
857                                     $target = $item["name"];
858                                 } else {
859                                     $target = $this->id_name;
860                                 }
861                                 $target = "{$item['language']}.$target";
862
863                                 if(!file_exists($path)) {
864                                     mkdir_recursive($path, true);
865                                 }
866                 $disabled_path = $path.'/'.DISABLED_PATH;
867                                 if (file_exists("$disabled_path/$target.php")) {
868                                         rename("$disabled_path/$target.php",  "$path/$target.php");
869                                 }
870                                 if (file_exists($disabled_path . '/'. basename($from))) {
871                                         rename($disabled_path.'/'. basename($from),  $path . '/'. basename($from));
872                                 }
873                         }
874                         $this->rebuild_languages($languages, $modules);
875                 }
876     }
877
878     // Functions for adding and removing logic hooks from uploaded files
879     // 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
880     /* The module hook definition should look like this:
881      $installdefs = array(
882      ... blah blah ...
883          'logic_hooks' => array(
884              array('module'      => 'Accounts',
885                    'hook'        => 'after_save',
886                    'order'       => 99,
887                    'description' => 'Account sample logic hook',
888                    'file'        => 'modules/Sample/sample_account_logic_hook_file.php',
889                    'class'       => 'SampleLogicClass',
890                    'function'    => 'accountAfterSave',
891              ),
892          ),
893      ... blah blah ...
894      );
895      */
896     function enable_manifest_logichooks() {
897         if(empty($this->installdefs['logic_hooks']) || !is_array($this->installdefs['logic_hooks'])) {
898            return;
899         }
900
901
902
903         foreach($this->installdefs['logic_hooks'] as $hook ) {
904             check_logic_hook_file($hook['module'], $hook['hook'], array($hook['order'], $hook['description'],  $hook['file'], $hook['class'], $hook['function']));
905         }
906     }
907
908     function disable_manifest_logichooks() {
909         if(empty($this->installdefs['logic_hooks']) || !is_array($this->installdefs['logic_hooks'])) {
910             return;
911         }
912
913         foreach($this->installdefs['logic_hooks'] as $hook ) {
914             remove_logic_hook($hook['module'], $hook['hook'], array($hook['order'], $hook['description'],  $hook['file'], $hook['class'], $hook['function']));
915         }
916     }
917
918 /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
919         function copy_path($from, $to, $backup_path='', $uninstall=false){
920         //function copy_path($from, $to){
921 /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
922                 $to = str_replace('<basepath>', $this->base_dir, $to);
923
924                 if(!$uninstall) {
925                 $from = str_replace('<basepath>', $this->base_dir, $from);
926                 $GLOBALS['log']->debug('Copy ' . $from);
927                 }
928                 else {
929                         $from = str_replace('<basepath>', $backup_path, $from);
930                         //$GLOBALS['log']->debug('Restore ' . $from);
931                 }
932                 $from = clean_path($from);
933                 $to = clean_path($to);
934
935                 $dir = dirname($to);
936                 //there are cases where if we need to create a directory in the root directory
937                 if($dir == '.' && is_dir($from)){
938                         $dir = $to;
939                 }
940                 if(!sugar_is_dir($dir, 'instance'))
941                         mkdir_recursive($dir, true);
942 /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
943                 if(empty($backup_path)) {
944 /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
945                 if(!copy_recursive($from, $to)){
946                         die('Failed to copy ' . $from. ' ' . $to);
947                 }
948 /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
949                 }
950                 elseif(!$this->copy_recursive_with_backup($from, $to, $backup_path, $uninstall)){
951                         die('Failed to copy ' . $from. ' to ' . $to);
952                 }
953 /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:22:18 PM */
954         }
955
956         function install_custom_fields($fields){
957                 global $beanList, $beanFiles;
958                 include('include/modules.php');
959                 require_once('modules/DynamicFields/FieldCases.php');
960                 foreach($fields as $field){
961                         $installed = false;
962                         if(isset($beanList[ $field['module']])){
963                                 $class = $beanList[ $field['module']];
964                 if(!isset($field['ext4']))$field['ext4'] = '';
965                 if(!isset($field['mass_update']))$field['mass_update'] = 0;
966                 if(!isset($field['duplicate_merge']))$field['duplicate_merge'] = 0;
967                 if(!isset($field['help']))$field['help'] = '';
968
969                                 if(file_exists($beanFiles[$class])){
970                                         require_once($beanFiles[$class]);
971                                         $mod = new $class();
972                                         $installed = true;
973                                         $fieldObject = get_widget($field['type']);
974                                         $fieldObject->populateFromRow($field);
975                                         $mod->custom_fields->use_existing_labels =  true;
976                                         $mod->custom_fields->addFieldObject($fieldObject);
977                                 }
978                         }
979                         if(!$installed){
980                                 $GLOBALS['log']->debug('Could not install custom field ' . $field['name'] . ' for module ' .  $field['module'] . ': Module does not exist');
981                         }
982                 }
983         }
984
985         function uninstall_custom_fields($fields){
986                 global $beanList, $beanFiles;
987                 require_once('modules/DynamicFields/DynamicField.php');
988                 $dyField = new DynamicField();
989
990                 foreach($fields as $field){
991                         $class = $beanList[ $field['module']];
992                         if(file_exists($beanFiles[$class])){
993                                         require_once($beanFiles[$class]);
994                                         $mod = new $class();
995                                         $dyField->bean = $mod;
996                                         $dyField->module = $field['module'];
997                                         $dyField->deleteField($field['name']);
998                         }
999                 }
1000         }
1001
1002         /*
1003      * ModuleInstaller->install_relationships calls install_relationship for every file included in the module package that defines a relationship, and then
1004      * writes a custom/Extension/application/Ext/TableDictionary/$module.php file containing an include_once for every relationship metadata file passed to install_relationship.
1005      * 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
1006      * everything in 'Ext/TableDictionary/' into 'tabledictionary.ext.php'
1007      */
1008     function install_relationships ()
1009     {
1010         if (isset ( $this->installdefs [ 'relationships' ] ))
1011         {
1012             $this->log ( translate ( 'LBL_MI_IN_RELATIONSHIPS' ) ) ;
1013             $str = "<?php \n //WARNING: The contents of this file are auto-generated\n" ;
1014             $save_table_dictionary = false ;
1015
1016             if (! file_exists ( "custom/Extension/application/Ext/TableDictionary" ))
1017             {
1018                 mkdir_recursive ( "custom/Extension/application/Ext/TableDictionary", true ) ;
1019             }
1020
1021             foreach ( $this->installdefs [ 'relationships' ] as $key => $relationship )
1022             {
1023                 $filename = basename ( $relationship [ 'meta_data' ] ) ;
1024                 $this->copy_path ( $relationship [ 'meta_data' ], 'custom/metadata/' . $filename ) ;
1025                 $this->install_relationship ( 'custom/metadata/' . $filename ) ;
1026                 $save_table_dictionary = true ;
1027
1028                 if (! empty ( $relationship [ 'module_vardefs' ] ))
1029                 {
1030                     $relationship [ 'module_vardefs' ] = str_replace ( '<basepath>', $this->base_dir, $relationship [ 'module_vardefs' ] ) ;
1031                     $this->install_vardef ( $relationship [ 'module_vardefs' ], $relationship [ 'module' ] ) ;
1032                 }
1033
1034                 if (! empty ( $relationship [ 'module_layoutdefs' ] ))
1035                 {
1036                     $relationship [ 'module_layoutdefs' ] = str_replace ( '<basepath>', $this->base_dir, $relationship [ 'module_layoutdefs' ] ) ;
1037                     $this->install_layoutdef ( $relationship [ 'module_layoutdefs' ], $relationship [ 'module' ] ) ;
1038                 }
1039
1040                 $relName = strpos($filename, "MetaData") !== false ? substr($filename, 0, strlen($filename) - 12) : $filename;
1041                 $out = sugar_fopen ( "custom/Extension/application/Ext/TableDictionary/$relName.php", 'w' ) ;
1042                 fwrite ( $out, $str . "include('custom/metadata/$filename');\n\n?>" ) ;
1043                 fclose ( $out ) ;
1044             }
1045
1046
1047
1048
1049             Relationship::delete_cache();
1050             $this->rebuild_vardefs () ;
1051             $this->rebuild_layoutdefs () ;
1052             if ($save_table_dictionary)
1053             {
1054                 $this->rebuild_tabledictionary () ;
1055             }
1056             require_once("data/Relationships/RelationshipFactory.php");
1057             SugarRelationshipFactory::deleteCache();
1058         }
1059     }
1060
1061         /*
1062      * Install_relationship obtains a set of relationship definitions from the filename passed in as a parameter.
1063      * For each definition it calls db->createTableParams to build the relationships table if it does not exist,
1064      * and SugarBean::createRelationshipMeta to add the relationship into the 'relationships' table.
1065      */
1066         function install_relationship($file)
1067         {
1068                 $_REQUEST['moduleInstaller'] = true;
1069                 if(!file_exists($file))
1070                 {
1071                         $GLOBALS['log']->debug( 'File does not exists : '.$file);
1072                         return;
1073                 }
1074                 include($file);
1075                 $rel_dictionary = $dictionary;
1076                 foreach ($rel_dictionary as $rel_name => $rel_data)
1077             {
1078                 $table = ''; // table is actually optional
1079                 // check if we have a table definition - not all relationships require a join table
1080             if ( isset( $rel_data[ 'table' ] ) )
1081             {
1082                 $table = $rel_data[ 'table' ];
1083
1084                 if(!$this->db->tableExists($table))
1085                 {
1086                     $this->db->createTableParams($table, $rel_data[ 'fields' ], $rel_data[ 'indices' ]);
1087                 }
1088             }
1089
1090             if(!$this->silent)
1091                 $GLOBALS['log']->debug("Processing relationship meta for ". $rel_name."...");
1092             SugarBean::createRelationshipMeta($rel_name, $this->db,$table,$rel_dictionary,'');
1093             Relationship::delete_cache();
1094             if(!$this->silent)
1095                 $GLOBALS['log']->debug( 'done<br>');
1096         }
1097         }
1098
1099         function install_layoutfields() {
1100                  if (!empty ( $this->installdefs [ 'layoutfields' ] ))
1101                  {
1102                         foreach ( $this->installdefs [ 'layoutfields' ] as $fieldSet )
1103             {
1104                                 if (!empty($fieldSet['additional_fields']))
1105                                 {
1106                                         $this->addFieldsToLayout($fieldSet['additional_fields']);
1107                                 }
1108             }
1109                  }
1110         }
1111
1112         function uninstall_layoutfields() {
1113                  if (!empty ( $this->installdefs [ 'layoutfields' ] ))
1114                  {
1115                         foreach ( $this->installdefs [ 'layoutfields' ] as $fieldSet )
1116             {
1117                                 if (!empty($fieldSet['additional_fields']))
1118                                 {
1119                                         $this->removeFieldsFromLayout($fieldSet['additional_fields']);
1120                                 }
1121             }
1122                  }
1123         }
1124
1125         function uninstall_relationship($file, $rel_dictionary = null){
1126         if ($rel_dictionary == null)
1127                 {
1128                         if(!file_exists($file)){
1129                                 $GLOBALS['log']->debug( 'File does not exists : '.$file);
1130                                 return;
1131                         }
1132                         include($file);
1133                         $rel_dictionary = $dictionary;
1134                 }
1135
1136                 foreach ($rel_dictionary as $rel_name => $rel_data)
1137                 {
1138                         if (!empty($rel_data['table'])){
1139                                 $table = $rel_data['table'];
1140                         }
1141                         else{
1142                                 $table = ' One-to-Many ';
1143                         }
1144
1145                         if ($this->db->tableExists($table) && isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])
1146                         {
1147                                 SugarBean::removeRelationshipMeta($rel_name, $this->db,$table,$rel_dictionary,'');
1148                                 $this->db->dropTableName($table);
1149                                 if(!$this->silent) $this->log( translate('LBL_MI_UN_RELATIONSHIPS_DROP') . $table);
1150                         }
1151
1152                         //Delete Layout defs
1153                         // check to see if we have any vardef or layoutdef entries to remove - must have a relationship['module'] parameter if we do
1154                         if (!isset($rel_data[ 'module' ]))
1155                                 $mods = array(
1156                                         $rel_data['relationships'][$rel_name]['lhs_module'],
1157                                         $rel_data['relationships'][$rel_name]['rhs_module'],
1158                                 );
1159                         else
1160                                 $mods = array($rel_data[ 'module' ]);
1161
1162                         $filename = "$rel_name.php";
1163
1164                         foreach($mods as $mod) {
1165                                 if ($mod != 'application' )  {
1166                                         $basepath = "custom/Extension/modules/$mod/Ext/";
1167                                 } else {
1168                                         $basepath = "custom/Extension/application/Ext/";
1169                                 }
1170
1171                                 foreach (array($filename , "custom" . $filename) as $fn) {
1172                                         //remove any vardefs
1173                                         $path = $basepath . "Vardefs/$fn" ;
1174                                         if (file_exists( $path ))
1175                                                 rmdir_recursive( $path );
1176
1177                                         //remove any layoutdefs
1178                                         $path = $basepath . "Layoutdefs/$fn" ;
1179                                         if( file_exists( $path ))
1180                                         {
1181                                                 rmdir_recursive( $path );
1182                                         }
1183                                 }
1184                         }
1185
1186                         foreach (array($filename , "custom" . $filename) as $fn) {
1187                                 // remove the table dictionary extension
1188                                 if ( file_exists("custom/Extension/application/Ext/TableDictionary/$fn"))
1189                                     unlink("custom/Extension/application/Ext/TableDictionary/$fn");
1190
1191                                 if (file_exists("custom/metadata/{$rel_name}MetaData.php"))
1192                                         unlink( "custom/metadata/{$rel_name}MetaData.php" );
1193                         }
1194                 }
1195         }
1196
1197         function uninstall_relationships($include_studio_relationships = false){
1198                 $relationships = array();
1199
1200                 //Find and remove studio created relationships.
1201                 global $beanList, $beanFiles, $dictionary;
1202                 //Load up the custom relationship definitions.
1203                 if(file_exists('custom/application/Ext/TableDictionary/tabledictionary.ext.php')){
1204                         include('custom/application/Ext/TableDictionary/tabledictionary.ext.php');
1205                 }
1206                 //Find all the relatioships/relate fields involving this module.
1207                 $rels_to_remove = array();
1208                 foreach($beanList as $mod => $bean) {
1209                         VardefManager::loadVardef($mod, $bean);
1210                         //We can skip modules that are in this package as they will be removed anyhow
1211                         if (!in_array($mod, $this->modulesInPackage) && !empty($dictionary[$bean]) && !empty($dictionary[$bean]['fields']))
1212                         {
1213                                 $field_defs = $dictionary[$bean]['fields'];
1214                                 foreach($field_defs as $field => $def)
1215                                 {
1216                                         //Weed out most fields first
1217                                         if (isset ($def['type']))
1218                                         {
1219                                                 //Custom relationships created in the relationship editor
1220                                                 if ($def['type'] == "link" && !empty($def['relationship']) && !empty($dictionary[$def['relationship']]))
1221                                                 {
1222                                                         $rel_name = $def['relationship'];
1223
1224                                                         $rel_def = $dictionary[$rel_name]['relationships'][$rel_name];
1225
1226                                                         //Check against mods to be removed.
1227                                                         foreach($this->modulesInPackage as $removed_mod) {
1228                                                                 if ($rel_def['lhs_module'] == $removed_mod || $rel_def['rhs_module'] == $removed_mod )
1229                                                                 {
1230                                                                         $dictionary[$rel_name]['from_studio'] = true;
1231                                                                         $relationships[$rel_name] = $dictionary[$rel_name];
1232                                                                 }
1233                                                         }
1234                                                 }
1235                                                 //Custom "relate" fields created in studio also need to be removed
1236                                                 if ($def['type'] == 'relate' && isset($def['module'])) {
1237                                                         foreach($this->modulesInPackage as $removed_mod) {
1238                                                                 if ($def['module'] == $removed_mod)
1239                                                                 {
1240                                                                         require_once 'modules/ModuleBuilder/Module/StudioModule.php' ;
1241                                                                         $studioMod = new StudioModule ( $mod );
1242                                                                         $studioMod->removeFieldFromLayouts( $field );
1243                                                                         if (isset($def['custom_module'])) {
1244                                                                                 require_once ('modules/DynamicFields/DynamicField.php') ;
1245                                                                                 require_once ($beanFiles [ $bean ]) ;
1246                                                                                 $seed = new $bean ( ) ;
1247                                                                                 $df = new DynamicField ( $mod ) ;
1248                                                                                 $df->setup ( $seed ) ;
1249                                                                                 //Need to load the entire field_meta_data for some field types
1250                                                                                 $field_obj = $df->getFieldWidget($mod, $field);
1251                                                                                 $field_obj->delete ( $df ) ;
1252                                                                         }
1253                                                                 }
1254                                                         }
1255                                                 }
1256                                         }
1257                                 }
1258                         }
1259                 }
1260
1261
1262
1263                 $this->uninstall_relationship(null, $relationships);
1264
1265                 if(isset($this->installdefs['relationships'])) {
1266                         $relationships = $this->installdefs['relationships'];
1267                         $this->log(translate('LBL_MI_UN_RELATIONSHIPS') );
1268                         foreach($relationships as $relationship)
1269                         {
1270                                 // remove the metadata entry
1271                                 $filename = basename ( $relationship['meta_data'] );
1272                                 $pathname = (file_exists("custom/metadata/$filename")) ? "custom/metadata/$filename" : "metadata/$filename" ;
1273                                 if(isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])
1274                                 $this->uninstall_relationship( $pathname );
1275                                 if (file_exists($pathname))
1276                                         unlink( $pathname );
1277                         }
1278                 }
1279
1280                 if (file_exists("custom/Extension/application/Ext/TableDictionary/{$this->id_name}.php"))
1281                         unlink("custom/Extension/application/Ext/TableDictionary/{$this->id_name}.php");
1282                 Relationship::delete_cache();
1283                 $this->rebuild_tabledictionary();
1284         }
1285
1286
1287
1288
1289         function uninstall($base_dir){
1290                 if(defined('TEMPLATE_URL'))SugarTemplateUtilities::disableCache();
1291                 global $app_strings;
1292                 $total_steps = 5; //min steps with no tasks
1293                 $current_step = 0;
1294                 $this->base_dir = $base_dir;
1295                 $tasks = array(
1296                         'pre_uninstall',
1297                         'uninstall_relationships',
1298                         'uninstall_copy',
1299                         'uninstall_dcactions',
1300                         'uninstall_dashlets',
1301                         'uninstall_connectors',
1302                         'uninstall_layoutfields',
1303                     'uninstall_extensions',
1304                         'disable_manifest_logichooks',
1305                         'post_uninstall',
1306                 );
1307                 $total_steps += count($tasks); //now the real number of steps
1308                 if(file_exists($this->base_dir . '/manifest.php')){
1309                                 if(!$this->silent){
1310                                         $current_step++;
1311                                         display_progress_bar('install', $current_step, $total_steps);
1312                                         echo '<div id ="displayLoglink" ><a href="#" onclick="toggleDisplay(\'displayLog\')">'.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
1313                                 }
1314
1315                                 global $moduleList;
1316                                 include($this->base_dir . '/manifest.php');
1317                                 $this->installdefs = $installdefs;
1318                                 $this->id_name = $this->installdefs['id'];
1319                                 $installed_modules = array();
1320                                 if(isset($this->installdefs['beans'])){
1321                                         foreach($this->installdefs['beans'] as $bean){
1322
1323                                                 $installed_modules[] = $bean['module'];
1324                                                 $this->uninstall_user_prefs($bean['module']);
1325                                         }
1326                                         $this->modulesInPackage = $installed_modules;
1327                                         $this->uninstall_beans($installed_modules);
1328                                         $this->uninstall_customizations($installed_modules);
1329                                         if(!$this->silent){
1330                                                 $current_step++;
1331                                                 update_progress_bar('install', $total_steps, $total_steps);
1332                                         }
1333                                 }
1334                                 if(!$this->silent){
1335                                         $current_step++;
1336                                         update_progress_bar('install', $current_step, $total_steps);
1337                                 }
1338                                 foreach($tasks as $task){
1339                                         $this->$task();
1340                                         if(!$this->silent){
1341                                                 $current_step++;
1342                                                 update_progress_bar('install', $current_step, $total_steps);
1343                                         }
1344                                 }
1345                                 if(isset($installdefs['custom_fields']) && (isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])){
1346                                         $this->log(translate('LBL_MI_UN_CUSTOMFIELD'));
1347                                         $this->uninstall_custom_fields($installdefs['custom_fields']);
1348                                 }
1349                                 if(!$this->silent){
1350                                         $current_step++;
1351                                         update_progress_bar('install', $current_step, $total_steps);
1352                                         echo '</div>';
1353                                 }
1354                                 //since we are passing $silent = true to rebuildAll() in that method it will set $this->silent = true, so
1355                                 //we need to save the setting to set it back after rebuildAll() completes.
1356                                 $silentBak = $this->silent;
1357                                 $this->rebuild_all(true);
1358                                 $this->silent = $silentBak;
1359
1360                                 //#27877, If the request from MB redeploy a custom module , we will not remove the ACL actions for this package.
1361                                 if( !isset($_REQUEST['action']) || $_REQUEST['action']!='DeployPackage' ){
1362                                         $this->remove_acl_actions();
1363                                 }
1364                                 //end
1365
1366                                 if(!$this->silent){
1367                                         $current_step++;
1368                                         update_progress_bar('install', $current_step, $total_steps);
1369                                         echo '</div>';
1370                                 }
1371
1372                                 UpdateSystemTabs('Restore',$installed_modules);
1373
1374                     //clear the unified_search_module.php file
1375                     require_once('modules/Home/UnifiedSearchAdvanced.php');
1376                     UnifiedSearchAdvanced::unlinkUnifiedSearchModulesFile();
1377
1378                                 $this->log('<br><b>' . translate('LBL_MI_COMPLETE') . '</b>');
1379                                 if(!$this->silent){
1380                                         update_progress_bar('install', $total_steps, $total_steps);
1381                                 }
1382                 }else{
1383                         die("No manifest.php Defined In $this->base_dir/manifest.php");
1384                 }
1385         }
1386
1387         function rebuild_languages($languages = array(), $modules="")
1388         {
1389             foreach($languages as $language=>$value){
1390                                 $this->log(translate('LBL_MI_REBUILDING') . " Language...$language");
1391                                 $this->merge_files('Ext/Language/', $language.'.lang.ext.php', $language);
1392                     if($modules!=""){
1393                         foreach($modules as $module){
1394                                 LanguageManager::clearLanguageCache($module, $language);
1395                         }
1396                     }
1397                         }
1398                         sugar_cache_reset();
1399         }
1400
1401         function rebuild_vardefs()
1402         {
1403             $this->rebuildExt("Vardefs", 'vardefs.ext.php');
1404                 sugar_cache_reset();
1405         }
1406
1407         function rebuild_dashletcontainers(){
1408             $this->log(translate('LBL_MI_REBUILDING') . " DC Actions...");
1409                         $this->merge_files('Ext/DashletContainer/Containers/', 'dcactions.ext.php');
1410         }
1411
1412         function rebuild_tabledictionary()
1413         {
1414             $this->rebuildExt("TableDictionary", 'tabledictionary.ext.php');
1415         }
1416
1417         function rebuild_relationships() {
1418         if(!$this->silent) echo translate('LBL_MI_REBUILDING') . ' Relationships';
1419                 $_REQUEST['silent'] = true;
1420                 global $beanFiles;
1421                 include('include/modules.php');
1422                 include("modules/Administration/RebuildRelationship.php");
1423         }
1424
1425         function remove_acl_actions() {
1426                 global $beanFiles, $beanList, $current_user;
1427                 include('include/modules.php');
1428                 include("modules/ACL/remove_actions.php");
1429         }
1430
1431         /**
1432          * Wrapper call to modules/Administration/RepairIndex.php
1433          */
1434         function repair_indices() {
1435                 global $current_user,$beanFiles,$dictionary;
1436                 $this->log(translate('LBL_MI_REPAIR_INDICES'));
1437                 $_REQUEST['silent'] = true; // local var flagging echo'd output in repair script
1438                 $_REQUEST['mode'] = 'execute'; // flag to just go ahead and run the script
1439                 include("modules/Administration/RepairIndex.php");
1440         }
1441
1442         /**
1443          * Rebuilds the extension files found in custom/Extension
1444          * @param boolean $silent
1445          */
1446         function rebuild_all($silent=false){
1447                 if(defined('TEMPLATE_URL'))SugarTemplateUtilities::disableCache();
1448                 $this->silent=$silent;
1449                 global $sugar_config;
1450
1451                 //Check for new module extensions
1452                 $this->rebuild_modules();
1453
1454                 $this->rebuild_languages($sugar_config['languages']);
1455                 $this->rebuild_extensions();
1456                 $this->rebuild_dashletcontainers();
1457                 $this->rebuild_relationships();
1458                 $this->rebuild_tabledictionary();
1459         $this->reset_opcodes();
1460                 sugar_cache_reset();
1461         }
1462
1463         /*
1464      * 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
1465      * custom/Extension/modules/$module/<path> (_override files last) and concatenates them to custom/modules/$module/<path>/<file>.
1466      * Then it does the same thing in custom/Extension/application/<path>, concatenating those files and copying the result to custom/application/<path>/<file>
1467      */
1468         function merge_files($path, $name, $filter = '', $application = false){
1469                 if(!$application){
1470                 $GLOBALS['log']->debug( get_class($this)."->merge_files() : merging module files in custom/Extension/modules/<module>/$path to custom/modules/<module>/$path$name");
1471                 foreach($this->modules as $module){
1472                                 //$GLOBALS['log']->debug("Merging Files for: ".$module);
1473                                 //$GLOBALS['log']->debug("Merging Files for path: ".$path);
1474                                 $extension = "<?php \n //WARNING: The contents of this file are auto-generated\n";
1475                                 $extpath = "modules/$module/$path";
1476                                 $module_install  = 'custom/Extension/'.$extpath;
1477                                 $shouldSave = false;
1478                                 if(is_dir($module_install)){
1479                                         $dir = dir($module_install);
1480                                         $shouldSave = true;
1481                                         $override = array();
1482                                         while($entry = $dir->read()){
1483                                                 if((empty($filter) || substr_count($entry, $filter) > 0) && is_file($module_install.'/'.$entry)
1484                                                   && $entry != '.' && $entry != '..' && strtolower(substr($entry, -4)) == ".php")
1485                                                 {
1486                                                      if (substr($entry, 0, 9) == '_override') {
1487                                                         $override[] = $entry;
1488                                                     } else {
1489                                                             $file = file_get_contents($module_install . '/' . $entry);
1490                                                             $GLOBALS['log']->debug(get_class($this)."->merge_files(): found {$module_install}{$entry}") ;
1491                                                             $extension .= "\n". str_replace(array('<?php', '?>', '<?PHP', '<?'), array('','', '' ,'') , $file);
1492                                                     }
1493                                                 }
1494                                         }
1495                                         foreach ($override as $entry) {
1496                         $file = file_get_contents($module_install . '/' . $entry);
1497                         $extension .= "\n". str_replace(array('<?php', '?>', '<?PHP', '<?'), array('','', '' ,'') , $file);
1498                                         }
1499                                 }
1500                                 $extension .= "\n?>";
1501
1502                                 if($shouldSave){
1503                                         if(!file_exists("custom/$extpath")) {
1504                                             mkdir_recursive("custom/$extpath", true);
1505                                     }
1506                                         $out = sugar_fopen("custom/$extpath/$name", 'w');
1507                                         fwrite($out,$extension);
1508                                         fclose($out);
1509                                 }else{
1510                                         if(file_exists("custom/$extpath/$name")){
1511                                                 unlink("custom/$extpath/$name");
1512                                         }
1513                                 }
1514                         }
1515
1516                 }
1517
1518                 $GLOBALS['log']->debug("Merging application files for $name in $path");
1519                 //Now the application stuff
1520                 $extension = "<?php \n //WARNING: The contents of this file are auto-generated\n";
1521                 $extpath = "application/$path";
1522                 $module_install  = 'custom/Extension/'.$extpath;
1523                 $shouldSave = false;
1524                                         if(is_dir($module_install)){
1525                                                 $dir = dir($module_install);
1526                                                 while($entry = $dir->read()){
1527                                                                 $shouldSave = true;
1528                                                                 if((empty($filter) || substr_count($entry, $filter) > 0) && is_file($module_install.'/'.$entry)
1529                                                                   && $entry != '.' && $entry != '..' && strtolower(substr($entry, -4)) == ".php")
1530                                                                 {
1531                                                                         $file = file_get_contents($module_install . '/' . $entry);
1532                                                                         $extension .= "\n". str_replace(array('<?php', '?>', '<?PHP', '<?'), array('','', '' ,'') , $file);
1533                                                                 }
1534                                                 }
1535                                         }
1536                                         $extension .= "\n?>";
1537                                         if($shouldSave){
1538                                                 if(!file_exists("custom/$extpath")){
1539                                                         mkdir_recursive("custom/$extpath", true);
1540                                                 }
1541                                                 $out = sugar_fopen("custom/$extpath/$name", 'w');
1542                                                 fwrite($out,$extension);
1543                                                 fclose($out);
1544                                         }else{
1545                                         if(file_exists("custom/$extpath/$name")){
1546                                                 unlink("custom/$extpath/$name");
1547                                         }
1548                                 }
1549
1550 }
1551
1552     function install_modules()
1553     {
1554             $this->installed_modules = array();
1555                 $this->tab_modules = array();
1556         if(isset($this->installdefs['beans'])){
1557                         $str = "<?php \n //WARNING: The contents of this file are auto-generated\n";
1558                         foreach($this->installdefs['beans'] as $bean){
1559                                 if(!empty($bean['module']) && !empty($bean['class']) && !empty($bean['path'])){
1560                                         $module = $bean['module'];
1561                                         $class = $bean['class'];
1562                                         $path = $bean['path'];
1563
1564                                         $str .= "\$beanList['$module'] = '$class';\n";
1565                                         $str .= "\$beanFiles['$class'] = '$path';\n";
1566                                         if($bean['tab']){
1567                                                 $str .= "\$moduleList[] = '$module';\n";
1568                                                 $this->install_user_prefs($module, empty($bean['hide_by_default']));
1569                                                 $this->tab_modules[] = $module;
1570                                         }else{
1571                                                 $str .= "\$modules_exempt_from_availability_check['$module'] = '$module';\n";
1572                                                 $str .= "\$modInvisList[] = '$module';\n";
1573                                         }
1574                                     $this->installed_modules[] = $module;
1575                                 }else{
1576                                                 $errors[] = 'Bean array not well defined.';
1577                                                 $this->abort($errors);
1578                                 }
1579                         }
1580                         $str.= "\n?>";
1581                         if(!file_exists("custom/Extension/application/Ext/Include")){
1582                                 mkdir_recursive("custom/Extension/application/Ext/Include", true);
1583                         }
1584                         file_put_contents("custom/Extension/application/Ext/Include/{$this->id_name}.php", $str);
1585         }
1586     }
1587
1588     /*
1589      * ModuleInstaller->install_beans runs through the list of beans given, instantiates each bean, calls bean->create_tables, and then calls SugarBean::createRelationshipMeta for the
1590      * bean/module.
1591      */
1592         function install_beans($beans){
1593         include('include/modules.php');
1594                 foreach($beans as $bean){
1595                         $this->log( translate('LBL_MI_IN_BEAN') . " $bean");
1596                         if(isset($beanList[$bean])){
1597                                 $class = $beanList[$bean];
1598                                 if(file_exists($beanFiles[$class])){
1599                                         require_once($beanFiles[$class]);
1600                                         $mod = new $class();
1601                                         //#30273
1602                                         if(is_subclass_of($mod, 'SugarBean')  && $mod->disable_vardefs == false ){
1603                                                 $GLOBALS['log']->debug( "Creating Tables Bean : $bean");
1604                                                 $mod->create_tables();
1605                                                 SugarBean::createRelationshipMeta($mod->getObjectName(), $mod->db,$mod->table_name,'',$mod->module_dir);
1606                                         }
1607                                 }else{
1608                                         $GLOBALS['log']->debug( "File Does Not Exist:" . $beanFiles[$class] );
1609                                 }
1610                         }
1611                 }
1612         }
1613
1614                 function uninstall_beans($beans){
1615                 include('include/modules.php');
1616         foreach($beans as $bean){
1617                         $this->log( translate('LBL_MI_UN_BEAN') . " $bean");
1618                         if(isset($beanList[$bean])){
1619                                 $class = $beanList[$bean];
1620
1621                                 if(file_exists($beanFiles[$class])){
1622                                         require_once($beanFiles[$class]);
1623                                         $mod = new $class();
1624
1625                                         if(is_subclass_of($mod, 'SugarBean')){
1626                                                 $GLOBALS['log']->debug( "Drop Tables : $bean");
1627                                                 if(isset($GLOBALS['mi_remove_tables']) && $GLOBALS['mi_remove_tables'])
1628                                                         $mod->drop_tables();
1629                                         }
1630                                 }else{
1631                                         $GLOBALS['log']->debug( "File Does Not Exist:" . $beanFiles[$class] );
1632                                 }
1633                         }
1634                 }
1635         }
1636
1637         /**
1638          * Remove any customizations made within Studio while the module was installed.
1639          */
1640         function uninstall_customizations($beans){
1641         foreach($beans as $bean){
1642                         $dirs = array(
1643                                 'custom/modules/' . $bean,
1644                                 'custom/Extension/modules/' . $bean
1645                         );
1646                 foreach($dirs as $dir)
1647                 {
1648                                 if(is_dir($dir)){
1649                                         rmdir_recursive($dir);
1650                                 }
1651                 }
1652                 }
1653         }
1654
1655         function log($str){
1656                 $GLOBALS['log']->debug('ModuleInstaller:'. $str);
1657                 if(!$this->silent){
1658                         echo $str . '<br>';
1659                 }
1660         }
1661
1662 /* BEGIN - RESTORE POINT - by MR. MILK August 31, 2005 02:15:18 PM      */
1663 function copy_recursive_with_backup( $source, $dest, $backup_path, $uninstall=false ) {
1664         if(is_file($source)) {
1665             if($uninstall) {
1666                     $GLOBALS['log']->debug("Restoring ... " . $source.  " to " .$dest );
1667                     if(copy( $source, $dest)) {
1668                             if(is_writable($dest))
1669                                 sugar_touch( $dest, filemtime($source) );
1670                         return(unlink($source));
1671                 }
1672                     else {
1673                         $GLOBALS['log']->debug( "Can't restore file: " . $source );
1674                         return true;
1675                 }
1676             }
1677             else {
1678                         if(file_exists($dest)) {
1679                                 $rest = clean_path($backup_path."/$dest");
1680                                 if( !is_dir(dirname($rest)) )
1681                                         mkdir_recursive(dirname($rest), true);
1682
1683                                 $GLOBALS['log']->debug("Backup ... " . $dest.  " to " .$rest );
1684                                 if(copy( $dest, $rest)) {
1685                                         if(is_writable($rest))
1686                                                 sugar_touch( $rest, filemtime($dest) );
1687                                 }
1688                                 else {
1689                                         $GLOBALS['log']->debug( "Can't backup file: " . $dest );
1690                                 }
1691                         }
1692                         return( copy( $source, $dest ) );
1693                 }
1694     }
1695     elseif(!is_dir($source)) {
1696             if($uninstall) {
1697                         if(is_file($dest))
1698                                 return(unlink($dest));
1699                         else {
1700                                 //don't do anything we already cleaned up the files using uninstall_new_files
1701                                 return true;
1702                         }
1703                 }
1704                 else
1705                         return false;
1706         }
1707
1708     if( !is_dir($dest) && !$uninstall){
1709         sugar_mkdir( $dest );
1710     }
1711
1712     $status = true;
1713
1714     $d = dir( $source );
1715     while( $f = $d->read() ){
1716         if( $f == "." || $f == ".." ){
1717             continue;
1718         }
1719         $status &= $this->copy_recursive_with_backup( "$source/$f", "$dest/$f", $backup_path, $uninstall );
1720     }
1721     $d->close();
1722     return( $status );
1723 }
1724
1725 private function dir_get_files($path, $base_path){
1726         $files = array();
1727         if(!is_dir($path))return $files;
1728         $d = dir($path);
1729         while ($e = $d->read()){
1730                 //ignore invisible files . .. ._MACOSX
1731                 if(substr($e, 0, 1) == '.')continue;
1732                 if(is_file($path . '/' . $e))$files[str_replace($base_path , '', $path . '/' . $e)] = str_replace($base_path , '', $path . '/' . $e);
1733                 if(is_dir($path . '/' . $e))$files = array_merge($files, $this->dir_get_files($path . '/' . $e, $base_path));
1734         }
1735         $d->close();
1736         return $files;
1737
1738 }
1739
1740 private function dir_file_count($path){
1741         //if its a file then it has at least 1 file in the directory
1742         if(is_file($path)) return 1;
1743         if(!is_dir($path)) return 0;
1744         $d = dir($path);
1745         $count = 0;
1746         while ($e = $d->read()){
1747                 //ignore invisible files . .. ._MACOSX
1748                 if(substr($e, 0, 1) == '.')continue;
1749                 if(is_file($path . '/' . $e))$count++;
1750                 if(is_dir($path . '/' . $e))$count += $this->dir_file_count($path . '/' . $e);
1751         }
1752         $d->close();
1753         return $count;
1754
1755
1756 }
1757 /* END - RESTORE POINT - by MR. MILK August 31, 2005 02:15:34 PM */
1758
1759
1760         /**
1761          * Static function which allows a module developer to abort their progress, pass in an array of errors and
1762          * redirect back to the main module loader page
1763          *
1764          * @param errors        an array of error messages which will be displayed on the
1765          *                                      main module loader page once it is loaded.
1766          */
1767         function abort($errors = array()){
1768                 //set the errors onto the session so we can display them one the moduler loader page loads
1769                 $_SESSION['MODULEINSTALLER_ERRORS'] = $errors;
1770                 echo '<META HTTP-EQUIV="Refresh" content="0;url=index.php?module=Administration&action=UpgradeWizard&view=module">';
1771                 die();
1772                 //header('Location: index.php?module=Administration&action=UpgradeWizard&view=module');
1773         }
1774
1775         /**
1776          * Return the set of errors stored in the SESSION
1777          *
1778          * @return an array of errors
1779          */
1780         function getErrors(){
1781                 if(!empty($_SESSION['MODULEINSTALLER_ERRORS'])){
1782                         $errors = $_SESSION['MODULEINSTALLER_ERRORS'];
1783                         unset($_SESSION['MODULEINSTALLER_ERRORS']);
1784                         return $errors;
1785                 }
1786                 else
1787                         return null;
1788         }
1789
1790         /*
1791      * Add any fields to the DetailView and EditView of the appropriate modules
1792      * Only add into deployed modules, as addFieldsToUndeployedLayouts has done this already for undeployed modules (and the admin might have edited the layouts already)
1793      * @param array $layoutAdditions  An array of module => fieldname
1794      * return null
1795      */
1796         function addFieldsToLayout($layoutAdditions) {
1797         require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
1798
1799         // these modules either lack editviews/detailviews or use custom mechanisms for the editview/detailview.
1800         // In either case, we don't want to attempt to add a relate field to them
1801         // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
1802         $invalidModules = array ( 'emails' , 'kbdocuments' ) ;
1803
1804         foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
1805         {
1806             if ( ! in_array( strtolower ( $deployedModuleName ) , $invalidModules ) )
1807             {
1808                 foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
1809                 {
1810                     $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . ": adding $fieldName to $view layout for module $deployedModuleName" ) ;
1811                     $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
1812                     $parser->addField ( array ( 'name' => $fieldName ) ) ;
1813                     $parser->handleSave ( false ) ;
1814                 }
1815             }
1816         }
1817
1818         }
1819
1820         function removeFieldsFromLayout($layoutAdditions) {
1821         require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
1822
1823         // these modules either lack editviews/detailviews or use custom mechanisms for the editview/detailview.
1824         // In either case, we don't want to attempt to add a relate field to them
1825         // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
1826         $invalidModules = array ( 'emails' , 'kbdocuments' ) ;
1827
1828         foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
1829         {
1830             if ( ! in_array( strtolower ( $deployedModuleName ) , $invalidModules ) )
1831             {
1832                 foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
1833                 {
1834                     $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . ": adding $fieldName to $view layout for module $deployedModuleName" ) ;
1835                     $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
1836                     $parser->removeField ( $fieldName ) ;
1837                     $parser->handleSave ( false ) ;
1838                 }
1839             }
1840         }
1841
1842         }
1843
1844         ///////////////////
1845         //********** DISABLE/ENABLE FUNCTIONS
1846         ///////////////////
1847         function enable($base_dir, $is_upgrade = false, $previous_version = ''){
1848                 global $app_strings;
1849                 $this->base_dir = $base_dir;
1850                 $total_steps = 3; //minimum number of steps with no tasks
1851                 $current_step = 0;
1852                 $tasks = array(
1853                                                                 'enable_copy',
1854                                                                 'enable_dashlets',
1855                                                                 'enable_relationships',
1856                                         'enable_extensions',
1857                                         'enable_manifest_logichooks',
1858                                                                 'reset_opcodes',
1859                 );
1860                 $total_steps += count($tasks);
1861                 if(file_exists($this->base_dir . '/manifest.php')){
1862                                 if(!$this->silent){
1863                                         $current_step++;
1864                                         display_progress_bar('install', $current_step, $total_steps);
1865                                         echo '<div id ="displayLoglink" ><a href="#" onclick="toggleDisplay(\'displayLog\')">'.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
1866                                 }
1867
1868                                 require_once($this->base_dir . '/manifest.php');
1869                                 if($is_upgrade && !empty($previous_version)){
1870                                         //check if the upgrade path exists
1871                                         if(!empty($upgrade_manifest)){
1872                                                 if(!empty($upgrade_manifest['upgrade_paths'])){
1873                                                         if(!empty($upgrade_manifest['upgrade_paths'][$previous_version])){
1874                                                                 $installdefs =  $upgrade_manifest['upgrade_paths'][$previous_version];
1875                                                         }else{
1876                                                                 $errors[] = 'No Upgrade Path Found in manifest.';
1877                                                                 $this->abort($errors);
1878                                                         }//fi
1879                                                 }//fi
1880                                         }//fi
1881                                 }//fi
1882                                 $this->id_name = $installdefs['id'];
1883                                 $this->installdefs = $installdefs;
1884                                 $installed_modules = array();
1885                                 if(isset($installdefs['beans'])){
1886                                         foreach($this->installdefs['beans'] as $bean){
1887                                                 $installed_modules[] = $bean['module'];
1888                                         }
1889                                 }
1890                                 if(!$this->silent){
1891                                         $current_step++;
1892                                         update_progress_bar('install', $current_step, $total_steps);
1893                                 }
1894
1895                                 foreach($tasks as $task){
1896                                         $this->$task();
1897                                         if(!$this->silent){
1898                                                 $current_step++;
1899                                                 update_progress_bar('install', $current_step, $total_steps);
1900                                         }
1901                                 }
1902
1903                                 if(!$this->silent){
1904                                         $current_step++;
1905                                         update_progress_bar('install', $current_step, $total_steps);
1906                                         echo '</div>';
1907                                 }
1908                                 UpdateSystemTabs('Add',$installed_modules);
1909                                 $GLOBALS['log']->debug('Complete');
1910
1911                 }else{
1912                         die("No \$installdefs Defined In $this->base_dir/manifest.php");
1913                 }
1914
1915         }
1916         function disable($base_dir){
1917                 global $app_strings;
1918                 $total_steps = 3; //min steps with no tasks
1919                 $current_step = 0;
1920                 $this->base_dir = $base_dir;
1921                 $tasks = array(
1922                                                         'disable_copy',
1923                                                         'disable_dashlets',
1924                                                         'disable_relationships',
1925                                     'disable_extensions',
1926                                                         'disable_manifest_logichooks',
1927                                                         'reset_opcodes',
1928                                                         );
1929                 $total_steps += count($tasks); //now the real number of steps
1930                 if(file_exists($this->base_dir . '/manifest.php')){
1931                                 if(!$this->silent){
1932                                         $current_step++;
1933                                         display_progress_bar('install', $current_step, $total_steps);
1934                                         echo '<div id ="displayLoglink" ><a href="#" onclick="toggleDisplay(\'displayLog\')">'.$app_strings['LBL_DISPLAY_LOG'].'</a> </div><div id="displayLog" style="display:none">';
1935                                 }
1936
1937                                 require_once($this->base_dir . '/manifest.php');
1938                                 $this->installdefs = $installdefs;
1939                                 $this->id_name = $this->installdefs['id'];
1940                                 $installed_modules = array();
1941                                 if(isset($this->installdefs['beans'])){
1942                                         foreach($this->installdefs['beans'] as $bean){
1943                                                 $installed_modules[] = $bean['module'];
1944                                         }
1945                                 }
1946                                 if(!$this->silent){
1947                                         $current_step++;
1948                                         update_progress_bar('install', $current_step, $total_steps);
1949                                 }
1950                                 foreach($tasks as $task){
1951                                         $this->$task();
1952                                         if(!$this->silent){
1953                                                 $current_step++;
1954                                                 update_progress_bar('install', $current_step, $total_steps);
1955                                         }
1956                                 }
1957                                 if(!$this->silent){
1958                                         $current_step++;
1959                                         update_progress_bar('install', $current_step, $total_steps);
1960                                         echo '</div>';
1961                                 }
1962                         UpdateSystemTabs('Restore',$installed_modules);
1963
1964                 }else{
1965                         die("No manifest.php Defined In $this->base_dir/manifest.php");
1966                 }
1967         }
1968
1969         function enable_vardef($to_module)
1970         {
1971             $this->enableExt("vardefs", "Vardefs", $to_module);
1972         }
1973
1974         function enable_layoutdef($to_module)
1975         {
1976             $this->enableExt("layoutdefs", "Layoutdefs", $to_module);
1977         }
1978
1979         function enable_relationships(){
1980                 if(isset($this->installdefs['relationships'])){
1981                         $str = "<?php \n //WARNING: The contents of this file are auto-generated\n";
1982                         $save_table_dictionary = false;
1983                         foreach($this->installdefs['relationships'] as $relationship){
1984                 $filename       =basename($relationship['meta_data']);
1985
1986                                 $save_table_dictionary  = true;
1987                                 $str .= "include_once('metadata/$filename');\n";
1988                                 if (empty($relationship['module']))
1989                                     continue;
1990
1991                                 if(!empty($relationship['module_vardefs'])){
1992                                         $this->enable_vardef($relationship['module']);
1993                                 }
1994                                 if(!empty($relationship['module_layoutdefs'])){
1995                                         $this->enable_layoutdef($relationship['module']);
1996                                 }
1997                         }
1998                         $this->rebuild_vardefs();
1999                         $this->rebuild_layoutdefs();
2000                         if($save_table_dictionary){
2001                                 if(!file_exists("custom/Extension/application/Ext/TableDictionary")){
2002                                         mkdir_recursive("custom/Extension/application/Ext/TableDictionary", true);
2003                                 }
2004                                 if (file_exists("custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH."/$this->id_name.php"))
2005                                    rename("custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH."/$this->id_name.php", "custom/Extension/application/Ext/TableDictionary/$this->id_name.php");
2006                                 $this->rebuild_tabledictionary();
2007                         }
2008                 }
2009         }
2010
2011         function disable_relationships($action = 'disable'){
2012                 if(isset($this->installdefs['relationships'])){
2013                         foreach($this->installdefs['relationships'] as $relationship){
2014                                 $filename = basename($relationship['meta_data']);
2015                 $relName = substr($filename, -12) == "MetaData.php" ? substr($filename,0,strlen($filename) - 12) : "";
2016                                 if (empty($relationship['module']) && empty($relName))
2017                         continue;
2018
2019                                 //remove the vardefs
2020                                 if (empty($relName))
2021                                         $path = 'custom/Extension/modules/' . $relationship['module']. '/Ext/Vardefs';
2022                                 if(!empty($relationship['module']) && $relationship['module'] == 'application'){
2023                                         $path ='custom/Extension/' . $relationship['module']. '/Ext/Vardefs';
2024                                 }
2025                                 if(!empty($relationship['module_vardefs']) && file_exists($path . '/'. $this->id_name . '.php')){
2026                                         mkdir_recursive($path . '/'.DISABLED_PATH, true);
2027                                         rename( $path . '/'. $this->id_name . '.php', $path . '/'.DISABLED_PATH.'/'. $this->id_name . '.php');
2028                                 }
2029                                 //remove the layoutdefs
2030                                 if ( !empty($relationship['module']) ) {
2031                     $path = 'custom/Extension/modules/' . $relationship['module']. '/Ext/Layoutdefs';
2032                     if($relationship['module'] == 'application'){
2033                         $path ='custom/Extension/' . $relationship['module']. '/Ext/Layoutdefs';
2034                     }
2035                                 }
2036
2037                                 if(!empty($relationship['module_layoutdefs']) && file_exists($path . '/'. $this->id_name . '.php')){
2038                                         mkdir_recursive($path . '/'.DISABLED_PATH, true);
2039                                         rename( $path . '/'. $this->id_name . '.php', $path . '/'.DISABLED_PATH.'/'. $this->id_name . '.php');
2040                                 }
2041
2042                         }
2043                         if(file_exists("custom/Extension/application/Ext/TableDictionary/$this->id_name.php")){
2044                                 mkdir_recursive("custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH, true);
2045                                 rename("custom/Extension/application/Ext/TableDictionary/$this->id_name.php", "custom/Extension/application/Ext/TableDictionary/".DISABLED_PATH."/$this->id_name.php");
2046                         }
2047                         $this->rebuild_tabledictionary();
2048                         $this->rebuild_vardefs();
2049                         $this->rebuild_layoutdefs();
2050                 }
2051         }
2052
2053         function enable_dashlets(){
2054                 if(isset($this->installdefs['dashlets'])){
2055                         foreach($this->installdefs['dashlets'] as $cp){
2056                                 $cp['from'] = str_replace('<basepath>', $this->base_dir, $cp['from']);
2057                                 $path = 'custom/modules/Home/Dashlets/' . $cp['name'] . '/';
2058                                 $disabled_path = 'custom/modules/Home/'.DISABLED_PATH.'Dashlets/' . $cp['name'];
2059                                 $GLOBALS['log']->debug("Enabling Dashlet " . $cp['name'] . "..." . $cp['from'] );
2060                                 if (file_exists($disabled_path))
2061                                 {
2062                                         rename($disabled_path,  $path);
2063                                 }
2064                         }
2065                         include('modules/Administration/RebuildDashlets.php');
2066
2067                 }
2068         }
2069
2070         function disable_dashlets(){
2071                 if(isset($this->installdefs['dashlets'])){
2072                                         foreach($this->installdefs['dashlets'] as $cp){
2073                                                 $path = 'custom/modules/Home/Dashlets/' . $cp['name'];
2074                                                 $disabled_path = 'custom/modules/Home/'.DISABLED_PATH.'Dashlets/' . $cp['name'];
2075                                                 $GLOBALS['log']->debug('Disabling ' .$path);
2076                                                 if (file_exists($path))
2077                                                 {
2078                                                         mkdir_recursive('custom/modules/Home/'.DISABLED_PATH.'Dashlets/', true);
2079                                                         rename( $path, $disabled_path);
2080                                                 }
2081                                         }
2082                                         include('modules/Administration/RebuildDashlets.php');
2083                                 }
2084         }
2085
2086         function enable_copy(){
2087                 //copy files back onto file system. first perform md5 check to determine if anything has been modified
2088                 //here we should just go through the files in the -restore directory and copy those back
2089                 if(isset($GLOBALS['mi_overwrite_files']) && $GLOBALS['mi_overwrite_files']){
2090                         if(!empty($this->installdefs['copy'])){
2091                                 foreach($this->installdefs['copy'] as $cp){
2092                                         $cp['to'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['to']));
2093                                         $backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore/".$cp['to'] );
2094
2095                                         //check if this file exists in the -restore directory
2096                                         if(file_exists($backup_path)){
2097                                                 //since the file exists, then we want do an md5 of the install version and the file system version
2098                                                 //if(is_file($backup_path) && md5_file($backup_path) == md5_file($cp['to'])){
2099                                                         //since the files are the same then we can safely move back from the -restore
2100                                                         //directory into the file system
2101                                                         $GLOBALS['log']->debug("ENABLE COPY:: FROM: ".$cp['from']. " TO: ".$cp['to']);
2102                                                         $this->copy_path($cp['from'], $cp['to']);
2103                                                 /*}else{
2104                                                         //since they are not equal then we need to prompt the user
2105                                                 }*/
2106                                         }//fi
2107                                 }//rof
2108                         }//fi
2109                 }//fi
2110         }
2111
2112         function disable_copy(){
2113                 //when we disable we want to copy the -restore files back into the file system
2114                 //but we should check the version in the module install against the version on the file system
2115                 //if they match then we can copy the file back, but otherwise we should ask the user.
2116
2117 //              $GLOBALS['log']->debug('ModuleInstaller.php->disable_copy()');
2118                 if(isset($GLOBALS['mi_overwrite_files']) && $GLOBALS['mi_overwrite_files']){
2119 //              $GLOBALS['log']->debug('ModuleInstaller.php->disable_copy():mi_overwrite_files=true');
2120                         if(!empty($this->installdefs['copy'])){
2121 //                              $GLOBALS['log']->debug('ModuleInstaller.php->disable_copy(): installdefs not empty');
2122                                 foreach($this->installdefs['copy'] as $cp){
2123                                         $cp['to'] = clean_path(str_replace('<basepath>', $this->base_dir, $cp['to']));
2124                                         $backup_path = clean_path( remove_file_extension(urldecode(hashToFile($_REQUEST['install_file'])))."-restore/".$cp['to'] ); // bug 16966 tyoung - replaced missing assignment to $backup_path
2125                                         //check if this file exists in the -restore directory
2126 //                                      $GLOBALS['log']->debug("ModuleInstaller.php->disable_copy(): backup_path=".$backup_path);
2127                                         if(file_exists($backup_path)){
2128                                                 //since the file exists, then we want do an md5 of the install version and the file system version
2129                                                 $from = str_replace('<basepath>', $this->base_dir, $cp['from']);
2130
2131                                                 //if(is_file($from) && md5_file($from) == md5_file($cp['to'])){
2132                                                         //since the files are the same then we can safely move back from the -restore
2133                                                         //directory into the file system
2134                                                         $GLOBALS['log']->debug("DISABLE COPY:: FROM: ".$backup_path. " TO: ".$cp['to']);
2135                                                         $this->copy_path($backup_path, $cp['to']);
2136                                                 /*}else{
2137                                                         //since they are not equal then we need to prompt the user
2138                                                 }*/
2139                                         }//fi
2140                                 }//rof
2141                         }//fi
2142                 }//fi
2143         }
2144
2145         public function reset_opcodes()
2146     {
2147         /* Bug 39354 - added function_exists check. Not optimal fix, but safe nonetheless.
2148          * 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,
2149          * but this file has been updated to 6.1
2150          */
2151         if(function_exists('sugar_clean_opcodes')){
2152             sugar_clean_opcodes();
2153         }
2154     }
2155
2156     /**
2157      * BC implementation to provide specific calls to extensions
2158      */
2159     public function __call($name, $args)
2160     {
2161         $nameparts = explode('_', $name);
2162         // name is something_something
2163         if(count($nameparts) == 2 && isset($this->extensions[$nameparts[1]])) {
2164             $ext = $this->extensions[$nameparts[1]];
2165             switch($nameparts[0]) {
2166                 case 'enable':
2167                     return $this->enableExt($ext['section'], $ext['extdir']);
2168                 case 'disable':
2169                     return $this->disableExt($ext['section'], $ext['extdir']);
2170                 case 'install':
2171                     return $this->installExt($ext['section'], $ext['extdir']);
2172                 case 'uninstall':
2173                     return $this->uninstallExt($ext['section'], $ext['extdir']);
2174                 case 'rebuild':
2175                     return $this->rebuildExt($ext['extdir'], $ext['file']);
2176             }
2177         }
2178         sugar_die("Unknown method ModuleInstaller::$name called");
2179     }
2180
2181 }
2182
2183     function UpdateSystemTabs($action, $installed_modules){
2184         require_once("modules/MySettings/TabController.php");
2185         $controller = new TabController();
2186         $isSystemTabsInDB = $controller->is_system_tabs_in_db();
2187         if ($isSystemTabsInDB && !empty($installed_modules))
2188         {
2189             global $moduleList;
2190             switch ($action)
2191             {
2192                 case 'Restore' :
2193                     $currentTabs = $controller->get_system_tabs();
2194                     foreach ($installed_modules as $module)
2195                     {
2196                         if(in_array($module, $currentTabs)){
2197                             unset($currentTabs[$module]);
2198                         }
2199                     }
2200                     $controller->set_system_tabs($currentTabs);;
2201                     break;
2202                 case 'Add' :
2203                     $currentTabs = $controller->get_system_tabs();
2204                     foreach ($installed_modules as $module)
2205                     {
2206                         if(!in_array($module, $currentTabs)){
2207                             $currentTabs[$module] = $module;
2208                         }
2209                     }
2210                     $controller->set_system_tabs($currentTabs);
2211                 default:
2212                     break;
2213             }
2214     }
2215
2216 }