* SPDX-License-Identifier: AGPL-3.0-only ************************************/ /* crmv@64542 */ /*@ DEL This is a template file for the ModuleMaker There are some special values in this file: 1. Any comment starting with /*@ is treated as a special comment and the next characters on that line specifies a command: DEL: remove all the comment until the closing tag (which has the @ sign as well) REPLACE: the comment is replaced with a standard comment, with some other text inside 2. All the variables that begin with $TPL_ are replaced with other values/variables @*/ /* Automatic installation script */ /* This file must be placed in the VTEROOT/storage/custom_modules folder */ // security check if (php_sapi_name() != "cli" && !defined('MODULEMAKERSCRIPT')) { throw new UninstallException("You are not allowed to invoke this script in this way!"); } $configInc = '../../config.inc.php'; if (!is_readable($configInc)) throw new UninstallException("File $configInc not found. Ensure this file is in the storage/custom_modules folder"); /* Includes */ require($configInc); chdir($root_directory); require('vteversion.php'); // crmv@181168 require_once('include/utils/utils.php'); require_once('modules/Update/Update.php'); require_once('vtlib/Vtecrm/Utils.php'); require_once('vtlib/Vtecrm/Field.php'); require_once('vtlib/Vtecrm/Module.php'); /* Globals */ global $adb, $table_prefix, $vtlib_Utils_Log, $current_user; $vtlib_Utils_Log = true; // missing current_user, when invoking from CLI if (!isset($current_user)) { $current_user = CRMEntity::getInstance('Users'); $current_user->id = 1; } /* Script class */ $MMS = new ModuleMakerScriptUninstall(); /* Basic variables */ $module_name = $TPL_MODULENAME; $module_dest_dir = "modules/$module_name"; /* Module variables */ // other fields to remove $module_other_fields = $TPL_OTHERFIELDS; // relations NtoN to remove $module_relations = $TPL_RELATIONS; // relations 1toN to existing fields to remove $module_addrelations = $TPL_ADDRELATIONS; // labels to remove $labels = $TPL_LANGUAGES; // ----------------- SCRIPT START ------------------- // check destination directory if (!is_writable('modules')) { throw new InstallException("The modules folder is not writable, please fix the permissions"); } // instance to the module $newModule = Vtecrm_Module::getInstance($module_name); // remove other labels if (is_array($labels)) { foreach ($labels as $module=>$modlang) { foreach ($modlang as $lang=>$translist) { foreach ($translist as $label=>$translabel) { SDK::deleteLanguageEntry($module, $lang, $label); } } } } // relations NtoN (couple of related lists) if (is_array($module_relations)) { foreach ($module_relations as $rel) { $relinst = Vtecrm_Module::getInstance($rel['module']); if ($relinst) { if (!empty($rel['function'])) { $func = $rel['function']; } elseif ($rel['module'] == 'Messages') { $func = 'get_messages_list'; } elseif ($rel['module'] == 'Documents') { $func = 'get_attachments'; } else { $func = 'get_related_list'; } // check the inverse relation function if (!empty($rel['reverse_function'])) { $func2 = $rel['reverse_function']; } elseif ($rel['module'] == 'Documents') { $func2 = 'get_documents_dependents_list'; } else { $func2 = $func; } $relinst->unsetRelatedList($newModule, $module_name, $func2); if ($newModule) $newModule->unsetRelatedList($relinst, $rel['module'], $func); } } } // relations 1toN with fields not to remove if (is_array($module_addrelations)) { foreach ($module_addrelations as $rel) { $relinst = Vtecrm_Module::getInstance($rel['module']); if ($relinst) { $label = $rel['module']; if (!empty($rel['function'])) { $func = $rel['function']; } elseif ($rel['module'] == 'Calendar') { $func = 'get_activities'; } else { $func = 'get_dependents_list'; } if ($func == 'get_activities') { $label = 'Activities'; } if ($newModule) $newModule->unsetRelatedList($relinst, $label, $func); if ($rel['field']) { $field = @Vtecrm_Field::getInstance($rel['field'], $relinst); if ($field) { $field->unsetRelatedModules(array($module_name)); } } } } } // remove other modules fields if (is_array($module_other_fields)) { foreach ($module_other_fields as $field) { $name = $field['name']; $mod = $field['module']; $modInst = Vtecrm_Module::getInstance($mod); if (empty($modInst)) { $MMS->warn("The module $mod was not found while removing the field $name"); continue; } $field = @Vtecrm_Field::getInstance($name, $modInst); if ($field) { $field->delete(); } } } // now delete all $r = $MMS->deleteModuleData($module_name); if (!$r) throw new UninstallException("Error during module removal"); $r = $MMS->deleteModuleFiles($module_name); if (!$r) throw new UninstallException("Error during module files removal"); // and just to be sure, regenerate the tabdata create_tab_data_file(); // crmv@104956 // clear some caches if (class_exists('Cache')) { $cache = Cache::getInstance('getQuickCreateModules'); if ($cache) $cache->clear(); } // crmv@104956e // ----------------------------------- CLASSES --------------------------------------- class UninstallException extends Exception { } // some useful functions for the uninstall class ModuleMakerScriptUninstall { public $enableLog = true; protected static $dbtables = array(); // crmv@167431 public function warn($s) { return $this->log('[WARNING] '.$s); } public function log($s) { if ($this->enableLog) echo $s."\n"; } /** * Remove ALL the record and metadata for the specified custom module */ public function deleteModuleData($modname) { global $adb, $table_prefix; // crmv@108984 $module = Vtecrm_Module::getInstance($modname); if ($module) { $tabid = $module->id; } else { $tabid = getTabid($modname); } // crmv@69568 // remove some accessory data $this->deleteReports($modname); $this->deleteWorkflows($modname); $this->deletePicklists($modname); // crmv@69568e // remove the module tables $this->dropModuleTables($modname); // delete module with vtlib if ($module) $module->__delete(); // crmv@108984e // removes all the rows with references to this module if ($tabid > 0) { $this->deleteRowsWithColumnValue('tabid', $tabid, true); } $this->deleteRowsWithColumnValue('entitytype', $modname, false); $this->deleteRowsWithColumnValue('entity_type', $modname, false); $this->deleteRowsWithColumnValue('setype', $modname, false); $this->deleteRowsWithColumnValue('module', $modname, false); $this->deleteRowsWithColumnValue('relmodule', $modname, false); $this->deleteRowsWithColumnValue('semodule', $modname, false); // crmv@80117 if (Vtecrm_Utils::CheckTable($table_prefix.'_fieldformulas')) { $adb->pquery("DELETE FROM {$table_prefix}_fieldformulas WHERE modulename = ?", array($modname)); } // crmv@80117e //crmv@85638 - these shouldn't be necessary, but sometimes the deleteRowsWithColumnValue doesn't work (?) if ($tabid > 0) { $adb->pquery("DELETE FROM {$table_prefix}_profile2standardperm WHERE tabid = ?", array($tabid)); $adb->pquery("DELETE FROM {$table_prefix}_org_share_action2tab WHERE tabid = ?", array($tabid)); } //crmv@85638e // TODO: maybe there are other tables/columns? return true; } // crmv@69568 //crmv@95610 // delete all the workflows with this primary module protected function deleteWorkflows($modname) { global $adb, $table_prefix; require_once("modules/com_workflow/VTWorkflowApplication.inc"); require_once("modules/com_workflow/VTWorkflowManager.inc"); require_once("modules/com_workflow/VTWorkflowUtils.php"); $this->log('Deleting all workflows...'); $res = $adb->pquery("SELECT w.workflow_id FROM com_{$table_prefix}_workflows w WHERE w.module_name = ?", array($modname)); if ($res && $adb->num_rows($res) > 0) { $wm = new VTWorkflowManager($adb); while ($row = $adb->FetchByAssoc($res, -1, false)) { $id = intval($row['workflow_id']); if ($id > 0) $wm->delete($id); } } } //crmv@95610e // crmv@99794 // delete all the reports with this primary module protected function deleteReports($modname) { global $adb, $table_prefix; require_once('modules/Reports/Reports.php'); $reports = Reports::getInstance(); $this->log('Deleting all reports...'); $res = $adb->pquery("SELECT r.reportid FROM {$table_prefix}_report r INNER JOIN {$table_prefix}_reportconfig rc ON rc.reportid = r.reportid WHERE rc.module = ?", array($modname)); if ($res && $adb->num_rows($res) > 0) { while ($row = $adb->FetchByAssoc($res, -1, false)) { $id = intval($row['reportid']); if ($id > 0) $reports->deleteReport($id); } } } // crmv@99794e protected function deletePicklists($modname) { global $adb, $table_prefix; $this->log('Dropping all picklist values and tables...'); // search for picklist fields and remove their tables $res = $adb->pquery("SELECT fieldid, fieldname, p.picklistid FROM {$table_prefix}_field f INNER JOIN {$table_prefix}_tab t ON t.tabid = f.tabid LEFT JOIN {$table_prefix}_picklist p ON p.name = f.fieldname WHERE t.name = ? AND f.uitype IN (?,?,?,?) ", array($modname, 15, 16, 1015, 300)); if ($res && $adb->num_rows($res) > 0) { while ($row = $adb->FetchByAssoc($res, -1, false)) { $fieldid = $row['fieldid']; $pickid = intval($row['picklistid']); // delete the values if ($pickid > 0) { $adb->pquery("DELETE FROM {$table_prefix}_picklist WHERE picklistid = ?", array($pickid)); $adb->pquery("DELETE FROM {$table_prefix}_role2picklist WHERE picklistid = ?", array($pickid)); } // drop the tables $tab = $table_prefix.'_'.$row['fieldname']; if (Vtecrm_Utils::CheckTable($tab)) { $dropq = $adb->datadict->DropTableSQL($tab); $adb->datadict->ExecuteSQLArray($dropq); } $tab_seq = $table_prefix.'_'.$row['fieldname'].'_seq'; if (Vtecrm_Utils::CheckTable($tab_seq)) { $dropq = $adb->datadict->DropTableSQL($tab_seq); $adb->datadict->ExecuteSQLArray($dropq); } } } } // crmv@69568e protected function dropModuleTables($modname) { global $adb, $table_prefix; $this->log('Dropping module tables...'); $tables = array(); @include_once("modules/$modname/{$modname}.php"); if (class_exists($modname)) { $instance = CRMEntity::getInstance($modname); if ($instance) { $tables[] = $instance->table_name; $tables[] = $instance->customFieldTable[0]; } } $tables = array_filter($tables); if (count($tables) > 0) { foreach ($tables as $modTable) { if (Vtecrm_Utils::CheckTable($modTable)) { $dropq = $adb->datadict->DropTableSQL($modTable); $adb->datadict->ExecuteSQLArray($dropq); } } } return true; } /** * Delete all the files for the specified custom module. */ public function deleteModuleFiles($modname) { global $adb, $table_prefix; $this->log('Removing module files...'); $this->rrmdir("modules/$modname"); $this->rrmdir("cron/modules/$modname"); $this->rrmdir("Smarty/templates/modules/$modname"); return true; } protected function rrmdir($dir) { if (is_dir($dir)) { $objects = scandir($dir); foreach ($objects as $object) { if ($object != "." && $object != "..") { $subpath = $dir.DIRECTORY_SEPARATOR.$object; if (filetype($subpath) == "dir") $this->rrmdir($subpath); else unlink($subpath); } } reset($objects); rmdir($dir); } elseif (is_file($dir)) { unlink($dir); } } protected function deleteRowsWithColumnValue($column, $value, $fuzzy = false) { global $adb, $table_prefix; $list = $this->getTablesWithColumn($column, $fuzzy); if ($list && is_array($list)) { foreach ($list as $table) { if ($fuzzy) { list($tablename, $columnname) = explode('.', $table, 2); } else { $tablename = $table; $columnname = $column; } $adb->format_columns($columnname); $q = "DELETE FROM $tablename WHERE $columnname = ?"; $adb->pquery($q, array($value)); } } return true; } // use only alphanumeric names for the column protected function getTablesWithColumn($column, $fuzzy = false) { global $adb, $dbconfig; $dbname = $dbconfig['db_name']; $params = array(); // crmv@167431 // crmv@73504 if ($adb->isMysql()) { // use a faster method $list = array(); $tables = $this->getMysqlTables(); foreach ($tables as $tablename=>$tablefields) { if ($fuzzy) { foreach ($tablefields as $colfield) { if (stripos($colfield,$column) !== false) $list[] = $tablename.'.'.$colfield; } } else { if (in_array($column,$tablefields)) $list[] = $tablename; } } return $list; } elseif ($adb->isPostgres()) { // crmv@167431e // crmv@140072 if ($fuzzy) { $query = "SELECT table_name, column_name FROM information_schema.columns INNER JOIN information_schema.tables USING (table_schema,table_name) WHERE table_schema = '$dbname' AND column_name LIKE '%$column%' AND table_type = 'BASE TABLE'"; } else { $query = "SELECT table_name FROM information_schema.columns INNER JOIN information_schema.tables USING (table_schema,table_name) WHERE table_schema = '$dbname' AND column_name = ? AND table_type = 'BASE TABLE'"; $params = array($column); } // crmv@140072e } elseif ($adb->isMssql()) { if ($fuzzy) { $query = "SELECT table_name, column_name FROM information_schema.columns WHERE table_catalog = '$dbname' AND table_schema = 'dbo' AND column_name LIKE '%$column%'"; } else { $query = "SELECT table_name FROM information_schema.columns WHERE table_catalog = '$dbname' AND table_schema = 'dbo' AND column_name = ?"; $params = array($column); } // crmv@73504e } elseif ($adb->isOracle()) { if ($fuzzy) { $query = "SELECT object_name AS table_name, user_tab_cols.column_name FROM user_objects INNER JOIN user_tab_cols ON user_objects.object_name = user_tab_cols.table_name WHERE user_objects.object_type = 'TABLE' AND user_tab_cols.column_name LIKE '%$column%'"; } else { $query = "SELECT object_name AS table_name FROM user_objects INNER JOIN user_tab_cols ON user_objects.object_name = user_tab_cols.table_name WHERE user_objects.object_type = 'TABLE' AND user_tab_cols.column_name = ?"; $params = array(strtoupper($column)); } } else { // database type not supported return false; } $list = array(); $res = $adb->pquery($query, $params); if ($res && $adb->num_rows($res) > 0) { while ($row = $adb->FetchByAssoc($res,-1, false)) { if ($fuzzy) { $col = $row['table_name'].'.'.$row['column_name']; } else { $col = $row['table_name']; } $list[] = $col; } } return $list; } // crmv@167431 protected function getMysqlTables(){ global $adb; if (empty(self::$dbtables)){ $res = $adb->query("SHOW TABLES"); if ($res && $adb->num_rows($res) > 0) { while ($row = $adb->fetch_array_no_html($res)) { $table = $row[0]; //get column definition $tablefields = array(); $res1 = $adb->query("DESCRIBE `{$table}`"); // crmv@205759 if ($res1 && $adb->num_rows($res1) > 0) { while ($row1 = $adb->fetch_array_no_html($res1)) { $tablefields[]=$row1[0]; } self::$dbtables[$table] = $tablefields; } } } } return self::$dbtables; } // crmv@167431e }