/
var
/
www
/
html
/
sugardemo
/
modules
/
ModuleBuilder
/
MB
/
Upload File
HOME
<?php /* * Your installation or use of this SugarCRM file is subject to the applicable * terms available at * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/. * If you do not agree to all of the applicable terms or do not have the * authority to bind the entity as an authorized representative, then do not * install or use this SugarCRM file. * * Copyright (C) SugarCRM Inc. All rights reserved. */ use Sugarcrm\Sugarcrm\Security\InputValidation\InputValidation; use Sugarcrm\Sugarcrm\Security\InputValidation\Request; use Sugarcrm\Sugarcrm\Util\Files\FileLoader; define('MB_TEMPLATES', 'include/SugarObjects/templates'); define('MB_IMPLEMENTS', 'include/SugarObjects/implements'); require_once 'modules/ModuleBuilder/parsers/constants.php'; class MBModule { /** * @var string|mixed|mixed[] */ public $key_name; public $package; public $package_key; public $package_path; /** * @var \UndeployedRelationships|mixed */ public $relationships; /** * @var \MBVardefs|mixed */ public $mbvardefs; /** * @var mixed[]|array<string, string>|mixed */ public $providedSubpanels; public $name = ''; public $config = [ 'team_security' => 1, 'assignable' => 1, 'taggable' => 1, 'acl' => 1, 'has_tab' => 1, 'studio' => 1, 'audit' => 1, 'activity_enabled' => 0, //activity stream disabled by default for new modules ]; public $mbpublicdefs; public $errors = []; public $path = ''; public $implementable = [ 'team_security' => 'Team Security', 'has_tab' => 'Navigation Tab']; public $always_implement = ['assignable' => 'Assignable', 'acl' => 'Access Controls', 'studio' => 'Studio Support', 'audit' => 'Audit Table']; public $iTemplate = [ 'team_security', 'assignable', 'taggable', ]; public $config_md5 = null; /** * The metadata converter object, used to keep the peace between BWC and Sidecar * formatted metadata * * @var MetaDataConverter */ public $mdc; /** * @var MBLanguage */ public $mblanguage; public $help = []; public function __construct($name, $path, $package, $package_key) { global $mod_strings; $this->config ['templates'] = ['basic' => 1]; $this->name = self::getDBName($name); $this->key_name = $package_key . '_' . $name; $this->package = $package; $this->package_key = $package_key; $this->package_path = $path; $this->implementable = [ 'team_security' => !empty($mod_strings['LBL_TEAM_SECURITY']) ? $mod_strings['LBL_TEAM_SECURITY'] : true, 'has_tab' => !empty($mod_strings['LBL_NAV_TAB']) ? $mod_strings['LBL_NAV_TAB'] : false]; $this->path = $this->getModuleDir(); // $this->mbrelationship = new MBRelationship($this->name, $this->path, $this->key_name); $this->relationships = new UndeployedRelationships($this->path); $this->mbvardefs = new MBVardefs($this->name, $this->path, $this->key_name); $this->load(); } public static function getDBName($name) { return preg_replace("/[^\w]+/", '_', $name); } public function getModuleName() { return $this->name; } public function getPackageName() { return $this->package; } /** * @return UndeployedRelationships */ public function getRelationships() { return $this->relationships; } /** * Loads the module based on the module name * */ public function load() { $config = []; if (file_exists($this->path . '/config.php')) { include FileLoader::validateFilePath($this->path . '/config.php'); $this->config = $config; $this->normalizeConfig(); } $label = (!empty($this->config ['label'])) ? $this->config ['label'] : $this->name; $label_singular = !empty($this->config['label_singular']) ? $this->config['label_singular'] : $label; $abbreviation = $this->config['label_abbreviation'] ?? self::getModuleAbbreviatedLabel($label); $this->mblanguage = new MBLanguage( $this->name, $this->path, $label, $this->key_name, $label_singular, $abbreviation ); foreach ($this->iTemplate as $temp) { if (!empty($this->config [$temp])) { $this->mbvardefs->iTemplates [$temp] = 1; $this->mblanguage->iTemplates [$temp] = $temp; } } $this->mbvardefs->templates = $this->config ['templates']; $this->mblanguage->templates = $this->config ['templates']; $this->mbvardefs->load(); $this->mblanguage->load(); $this->verifyMetadata(); } /** * Ensures the config matches current expectations. * The config may or may not match the current expectation if the package was exported from an older version * or existed before a version upgrade. */ protected function normalizeConfig() { if (empty($this->config['label_singular']) && !empty($this->config['label'])) { $this->config['label_singular'] = $this->config ['label']; } if (empty($this->config['label_abbreviation']) && !empty($this->config['label'])) { $this->config['label_abbreviation'] = self::getModuleAbbreviatedLabel($this->config['label']); } } public function addTemplate($template) { $this->config ['templates'] [$template] = 1; } public function getModuleDir() { return $this->package_path . '/modules/' . $this->name; } public function removeTemplate($template) { unset($this->config ['templates'] [$template]); } public function getVardefs($by_group = false) { $this->mbvardefs->updateVardefs($by_group); return $this->mbvardefs->getVardefs(); } public function addField($vardef) { $this->mbvardefs->addFieldVardef($vardef); } public function addFieldObject($field) { $vardef = $field->get_field_def(); $this->mbvardefs->mergeVardefs(); $existingVardefs = $this->mbvardefs->getVardefs(); // default unified_search is false, but if the fts is enabled, we need it to be true if (!empty($field->supports_unified_search) && isset($vardef['full_text_search']) && !empty($vardef['full_text_search']['enabled'])) { $vardef['unified_search'] = true; } //Merge with the existing vardef if it already exists if (!empty($existingVardefs['fields'][$vardef['name']])) { // if we edited non-db field, do not override its source if (isset($existingVardefs['fields'][$vardef['name']]['source']) && $existingVardefs['fields'][$vardef['name']]['source'] == 'non-db') { unset($vardef['source']); } $vardef = array_merge($existingVardefs['fields'][$vardef['name']], $vardef); } if (!empty($vardef ['source']) && $vardef ['source'] == 'custom_fields') { unset($vardef ['source']); } if (!empty($vardef['default'])) { $vardef['default'] = htmlspecialchars_decode($vardef['default'], ENT_QUOTES); } $this->mbvardefs->load(); $this->addField($vardef); $this->mbvardefs->save(); } public function deleteField($name) { $this->mbvardefs->deleteField($name); } public function fieldExists($name = '', $type = '') { $vardefs = $this->getVardefs(); if (!empty($vardefs)) { if (empty($type) && empty($name)) { return false; } elseif (empty($type)) { return !empty($vardefs ['fields'] [$name]); } elseif (empty($name)) { foreach ($vardefs ['fields'] as $def) { if ($def ['type'] == $type) { return true; } } return false; } else { return (!empty($vardefs ['fields'] [$name]) && ($vardefs ['fields'] [$name] ['type'] == $type)); } } else { return false; } } public function getModStrings($language = 'en_us') { return $this->mblanguage->getModStrings($language); } /** * @param string $language default 'en_us' * @param $mod_strings */ public function setModStrings(string $language, array $mod_strings): void { $language .= '.lang.php'; $this->mblanguage->strings [$language] = $mod_strings; } /** * @param string $language default 'en_us' * @param $key * @param $value */ public function setLabel(string $language, string $key, string $value): void { $language .= '.lang.php'; $this->mblanguage->strings [$language] [$key] = $value; //Ensure this key exists in all languages foreach ($this->mblanguage->strings as $lang => $values) { if (empty($values[$key])) { $this->mblanguage->strings[$lang][$key] = $value; } } } /** * @param string $language default 'en_us' * @param $key */ public function deleteLabel(string $language, string $key): void { foreach ($this->mblanguage->strings as $lang => $values) { if (!empty($values[$key])) { unset($this->mblanguage->strings[$lang][$key]); } } } /** * Required for an MB module to work with Dynamic fields */ public function addLabel(string $displayLabel): void { $this->setLabel('en_us', static::getDBName($displayLabel), translate($displayLabel)); $this->save(); } /** * @param string $language default 'en_us' * @param $key * @return mixed|string */ public function getLabel(string $language, string $key): string { $language .= '.lang.php'; if (empty($this->mblanguage->strings [$language] [$key])) { return ''; } return $this->mblanguage->strings [$language] [$key]; } public function getAppListStrings(string $language = 'en_us'): array { return $this->mblanguage->getAppListStrings($language); } /** * @param string $language default 'en_us' * @param $app_list_strings */ public function setAppListStrings(string $language, array $app_list_strings): void { $language .= '.lang.php'; $this->mblanguage->appListStrings [$language] = $app_list_strings; } /** * @param string $language default 'en_us' * @param $key * @param $value */ public function setDropDown(string $language, string $key, string $value): void { $language .= '.lang.php'; $this->mblanguage->appListStrings [$language] [$key] = $value; } /** * @param string $language default 'en_us' * @param $key */ public function deleteDropDown(string $language, string $key): void { $language .= '.lang.php'; unset($this->mblanguage->appListStrings [$language] [$key]); } public function save() { $this->path = $this->getModuleDir(); if (mkdir_recursive($this->path)) { $this->setConfigMD5(); $old_config_md5 = $this->config_md5; $this->saveConfig(); $this->getVardefs(); $this->mbvardefs->save($this->key_name); $this->relationships->save(); $this->copyMetaData(); $this->copyDashlet(); $this->copyViews(); $this->copyDashboards(); // Bug 56675 - Clients directory not copied over // When clients were split apart from metadata, there was no accounting // for that here. This accounts for that $this->copyClients(); // End bug 56675 if (0 !== strcmp((string)$old_config_md5, (string)$this->config_md5)) { $this->mblanguage->reload(); } $this->mblanguage->label = $this->config ['label']; $this->mblanguage->label_singular = !empty($this->config['label_singular']) ? $this->config['label_singular'] : $this->config['label']; $this->mblanguage->abbreviation = $this->config['label_abbreviation'] ?? self::getModuleAbbreviatedLabel($this->config['label']); //pass in the key_name incase it has changed mblanguage will check if it is different and handle it accordingly $this->mblanguage->save($this->key_name); if (!file_exists($this->package_path . '/icons/icon_' . ucfirst($this->key_name) . '.gif')) { $this->createIcon(); } $this->errors = array_merge($this->errors, $this->mbvardefs->errors); } } public function copyDashlet() { $templates = array_reverse($this->config ['templates'], true); foreach ($templates as $template => $a) { if (file_exists(MB_TEMPLATES . '/' . $template . '/Dashlets/Dashlet')) { $this->copyMetaRecursive(MB_TEMPLATES . '/' . $template . '/Dashlets/Dashlet', $this->path . '/Dashlets/' . $this->key_name . 'Dashlet/'); } } } public function copyViews() { $templates = array_reverse($this->config ['templates'], true); foreach ($templates as $template => $a) { if (file_exists(MB_TEMPLATES . '/' . $template . '/views')) { $this->copyMetaRecursive(MB_TEMPLATES . '/' . $template . '/views', $this->path . '/views/'); } } } public function copyCustomFiles($from, $to) { $d = dir($from); while ($filename = $d->read()) { if (substr($filename, 0, 1) == '.') { continue; } if ($filename != 'metadata' && $filename != 'Dashlets' && $filename != 'relationships' && $filename != 'language' && $filename != 'config.php' && $filename != 'relationships.php' && $filename != 'vardefs.php') { copy_recursive("$from/$filename", "$to/$filename"); } } } public function copyMetaData() { $templates = array_reverse($this->config ['templates'], true); foreach ($templates as $template => $a) { if (file_exists(MB_TEMPLATES . '/' . $template . '/metadata')) { $this->copyMetaRecursive(MB_TEMPLATES . '/' . $template . '/metadata', $this->path . '/metadata/'); } } } /** * Copies any available dashboards. */ public function copyDashboards() { if (file_exists(MB_TEMPLATES . '/basic/dashboards')) { $this->copyMetaRecursive(MB_TEMPLATES . '/basic/dashboards', $this->path . '/dashboards/'); } } public function copyMetaRecursive($from, $to, $overwrite = false) { if (!file_exists($from)) { return; } if (is_dir($from)) { $findArray = ['<module_name>', '<_module_name>', '<MODULE_NAME>', '<object_name>', '<_object_name>', '<OBJECT_NAME>']; $replaceArray = [$this->key_name, strtolower($this->key_name), strtoupper($this->key_name), $this->key_name, strtolower($this->key_name), strtoupper($this->key_name)]; mkdir_recursive($to); $d = dir($from); // Clean up to to make sure the path is clean $to = rtrim($to, '/') . '/'; while ($e = $d->read()) { if (substr($e, 0, 1) == '.') { continue; } $nfrom = $from . '/' . $e; $nto = $to . str_replace('m-n-', $this->key_name, $e); if (is_dir($nfrom)) { $this->copyMetaRecursive($nfrom, $nto, $overwrite); } else { if ($overwrite || !file_exists($nto)) { $contents = file_get_contents($nfrom); $contents = str_replace($findArray, $replaceArray, $contents); $fw = sugar_fopen($nto, 'w'); fwrite($fw, $contents); fclose($fw); } } } } } /** * Bug 56675 * * Copies the clients directory from the sugar object this module is based on. * This method is inspired heavily by copyMetaData as at one time the client * view defs were actually part of metadata. * * Bug 57259 * * Only copy mobile clients because all clients would include portal clients * * Adapted for Sugar7 to include base clients as well. */ public function copyClients() { $templates = array_reverse($this->config['templates'], true); foreach ($templates as $template => $a) { foreach (['base', 'mobile'] as $client) { if (file_exists(MB_TEMPLATES . '/' . $template . '/clients/' . $client)) { $this->copyMetaRecursive(MB_TEMPLATES . '/' . $template . '/clients/' . $client, $this->path . '/clients/' . $client); } } } } /** * Ensures that all required metadata files exist or copies them from the templates if they don't exist. */ protected function verifyMetadata() { //Abort if we have never been saved if (empty($this->name) || !file_exists($this->path)) { return; } $rebuildClients = false; foreach (['base', 'mobile'] as $client) { if (!file_exists($this->path . '/clients/' . $client)) { $rebuildClients = true; break; } } if ($rebuildClients) { $this->copyClients(); } } public function saveConfig() { $header = file_get_contents('modules/ModuleBuilder/MB/header.php'); if (!write_array_to_file('config', $this->config, $this->path . '/config.php', 'w', $header)) { $this->errors [] = 'Could not save config to ' . $this->path . '/config.php'; } $this->setConfigMD5(); } public function setConfigMD5() { if (file_exists($this->path . '/config.php')) { $this->config_md5 = md5(base64_encode(serialize($this->config))); } } public function build($basepath) { global $app_list_strings; $path = $basepath . '/modules/' . $this->key_name; if (mkdir_recursive($path)) { $this->createClasses($path); $this->createMenu($path); $this->copyCustomFiles($this->path, $path); $this->copyMetaRecursive($this->path . '/metadata/', $path . '/metadata/', true); $this->copyMetaRecursive( $this->path . '/Dashlets/' . $this->key_name . 'Dashlet/', $path . '/Dashlets/' . $this->key_name . 'Dashlet/', true ); // Add in clients directory building $this->copyMetaRecursive($this->path . '/clients/', $path . '/clients/', true); $app_list_strings['moduleList'][$this->key_name] = $this->mblanguage->label; $this->relationships->build($basepath); $this->mblanguage->build($path); } } /** * Generate module classes: * - <MODULE_NAME>_sugar.php Bean controller that shouldn't be customized * - <MODULE_NAME>.php Bean controller where to put customizations * - vardefs.php Bean vardefs * - studio.php This file adds support for Studio * * @param string $path Path to the custom module. */ public function createClasses($path) { $class = []; $class['name'] = $this->key_name; $class['table_name'] = strtolower($class['name']); $class['extends'] = 'Basic'; $class['requires'] = []; $class['team_security'] = !empty($this->config['team_security']); $class['audited'] = !empty($this->config['audit']); $class['activity_enabled'] = !empty($this->config['activity_enabled']); $class['acl'] = !empty($this->config['acl']); $class['templates'] = "'basic'"; foreach ($this->iTemplate as $template) { if (!empty($this->config[$template])) { $class['templates'] .= ",'$template'"; } } foreach ($this->config['templates'] as $template => $a) { if ($template == 'basic') { continue; } $class['templates'] .= ",'$template'"; $class['extends'] = ucFirst($template); $class['requires'][] = MB_TEMPLATES . '/' . $template . '/' . ucfirst($template) . '.php'; } $class['importable'] = $this->config['importable']; $this->mbvardefs->updateVardefs(); $class['fields'] = $this->mbvardefs->vardefs['fields']; $class['fields_string'] = var_export_helper($this->mbvardefs->vardef['fields']); $relationship = []; $class['relationships'] = var_export_helper($this->mbvardefs->vardef['relationships']); $smarty = new Sugar_Smarty(); $smarty->left_delimiter = '{{'; $smarty->right_delimiter = '}}'; $smarty->assign('class', $class); //Generate <MODULE_NAME>_sugar.php $fp = sugar_fopen($path . '/' . $class['name'] . '_sugar.php', 'w'); fwrite($fp, $smarty->fetch('modules/ModuleBuilder/tpls/MBModule/Class.tpl')); fclose($fp); //Generate vardefs.php $fp = sugar_fopen($path . '/vardefs.php', 'w'); fwrite($fp, $smarty->fetch('modules/ModuleBuilder/tpls/MBModule/vardef.tpl')); fclose($fp); //Generate <MODULE_NAME>.php if (!file_exists($path . '/' . $class['name'] . '.php')) { $fp = sugar_fopen($path . '/' . $class['name'] . '.php', 'w'); fwrite($fp, $smarty->fetch('modules/ModuleBuilder/tpls/MBModule/DeveloperClass.tpl')); fclose($fp); } if (!file_exists($path . '/metadata')) { mkdir_recursive($path . '/metadata'); } //Generate studio.php if (!empty($this->config['studio'])) { $fp = sugar_fopen($path . '/metadata/studio.php', 'w'); fwrite($fp, $smarty->fetch('modules/ModuleBuilder/tpls/MBModule/Studio.tpl')); fclose($fp); } elseif (file_exists($path . '/metadata/studio.php')) { unlink($path . '/metadata/studio.php'); } } public function createMenu($path) { $s = new Sugar_Smarty(); $s->assign('moduleName', $this->key_name); $s->assign('showVcard', isset($this->config['templates']['person'])); $s->assign('showImport', $this->config['importable']); $menu = $s->fetch( 'modules/ModuleBuilder/tpls/clients/base/menus/header/header.tpl' ); $target = "$path/clients/base/menus/header/header.php"; mkdir_recursive( dirname($target) ); $fp = sugar_fopen($target, 'w'); fwrite($fp, $menu); fclose($fp); } public function addInstallDefs(&$installDefs) { $name = $this->key_name; $installDefs ['copy'] [] = ['from' => '<basepath>/SugarModules/modules/' . $name, 'to' => 'modules/' . $name]; $installDefs ['beans'] [] = ['module' => $name, 'class' => $name, 'path' => 'modules/' . $name . '/' . $name . '.php', 'tab' => $this->config ['has_tab']]; $this->relationships->addInstallDefs($installDefs); } public function getNodes() { $lSubs = []; $psubs = $this->getProvidedSubpanels(); foreach ($psubs as $sub) { $subLabel = $sub; if ($subLabel == 'default') { $subLabel = $GLOBALS ['mod_strings'] ['LBL_DEFAULT']; } $lSubs [] = ['name' => $subLabel, 'type' => 'list', 'action' => 'module=ModuleBuilder&MB=true&action=editLayout&view=ListView&view_module=' . $this->name . '&view_package=' . $this->package . '&subpanel=' . $sub . '&subpanelLabel=' . $subLabel . '&local=1']; } $popups = []; $popups [] = ['name' => translate('LBL_POPUPLISTVIEW'), 'type' => 'popuplistview', 'action' => 'module=ModuleBuilder&action=editLayout&view=popuplist&view_module=' . $this->name . '&view_package=' . $this->package]; $popups [] = ['name' => translate('LBL_POPUPSEARCH'), 'type' => 'popupsearch', 'action' => 'module=ModuleBuilder&action=editLayout&view=popupsearch&view_module=' . $this->name . '&view_package=' . $this->package]; $layouts = [ ['name' => translate('LBL_RECORDVIEW'), 'type' => 'record', 'action' => 'module=ModuleBuilder&MB=true&action=editLayout&view=' . MB_RECORDVIEW . '&view_module=' . $this->name . '&view_package=' . $this->package], ['name' => translate('LBL_LISTVIEW'), 'type' => 'list', 'action' => 'module=ModuleBuilder&MB=true&action=editLayout&view=' . MB_LISTVIEW . '&view_module=' . $this->name . '&view_package=' . $this->package], ['name' => translate('LBL_POPUP'), 'type' => 'Folder', 'children' => $popups, 'action' => 'module=ModuleBuilder&MB=true&action=wizard&view=popup&view_module=' . $this->name . '&view_package=' . $this->package], ]; $children = [ ['name' => translate('LBL_FIELDS'), 'action' => 'module=ModuleBuilder&action=modulefields&view_module=' . $this->name . '&view_package=' . $this->package], ['name' => translate('LBL_LABELS'), 'action' => 'module=ModuleBuilder&action=modulelabels&view_module=' . $this->name . '&view_package=' . $this->package], ['name' => translate('LBL_RELATIONSHIPS'), 'action' => 'module=ModuleBuilder&action=relationships&view_module=' . $this->name . '&view_package=' . $this->package], ['name' => translate('LBL_LAYOUTS'), 'type' => 'Folder', 'action' => "module=ModuleBuilder&action=wizard&view_module={$this->name}&view_package={$this->package}&MB=1", 'children' => $layouts], ['name' => translate('LBL_WIRELESSLAYOUTS'), 'type' => 'Folder', 'action' => "module=ModuleBuilder&action=wizard&view=wirelesslayouts&view_module={$this->name}&view_package={$this->package}&MB=1", 'children' => $this->getWirelessLayouts()], ]; if (safeCount($lSubs) > 0) { $children [] = ['name' => translate('LBL_AVAILABLE_SUBPANELS'), 'type' => 'folder', 'children' => $lSubs]; } $nodes = ['name' => $this->name, 'children' => $children, 'action' => 'module=ModuleBuilder&action=module&view_module=' . $this->name . '&view_package=' . $this->package]; return $nodes; } public function getWirelessLayouts() { $nodes = []; $nodes [translate('LBL_WIRELESSEDITVIEW')] = [ 'name' => translate('LBL_WIRELESSEDITVIEW'), 'type' => MB_WIRELESSEDITVIEW, 'action' => 'module=ModuleBuilder&MB=true&action=editLayout&view=' . MB_WIRELESSEDITVIEW . "&view_module={$this->name}&view_package={$this->package}", 'imageTitle' => 'EditView', 'help' => 'viewBtn' . MB_WIRELESSEDITVIEW, 'size' => '48', ]; $nodes [translate('LBL_WIRELESSDETAILVIEW')] = [ 'name' => translate('LBL_WIRELESSDETAILVIEW'), 'type' => MB_WIRELESSDETAILVIEW, 'action' => 'module=ModuleBuilder&MB=true&action=editLayout&view=' . MB_WIRELESSDETAILVIEW . "&view_module={$this->name}&view_package={$this->package}", 'imageTitle' => 'DetailView', 'help' => 'viewBtn' . MB_WIRELESSDETAILVIEW, 'size' => '48', ]; $nodes [translate('LBL_WIRELESSLISTVIEW')] = [ 'name' => translate('LBL_WIRELESSLISTVIEW'), 'type' => MB_WIRELESSLISTVIEW, 'action' => 'module=ModuleBuilder&MB=true&action=editLayout&view=' . MB_WIRELESSLISTVIEW . "&view_module={$this->name}&view_package={$this->package}", 'imageTitle' => 'ListView', 'help' => 'viewBtn' . MB_WIRELESSLISTVIEW, 'size' => '48', ]; return $nodes; } public function getProvidedSubpanels() { $this->providedSubpanels = []; $subpanelDir = $this->getModuleDir() . '/metadata/subpanels/'; if (file_exists($subpanelDir)) { $f = dir($subpanelDir); while ($g = $f->read()) { // sanity check to confirm that this is a usable subpanel... if (substr($g, 0, 1) != '.' && AbstractRelationships::validSubpanel($subpanelDir . $g)) { $subname = str_replace('.php', '', $g); $this->providedSubpanels [$subname] = $subname; } } } return $this->providedSubpanels; } public static function getTypes() { $types = []; $d = dir(MB_TEMPLATES); while ($e = $d->read()) { if (substr($e, 0, 1) != '.') { $types [$e] = $e; } } return $types; } public function rename($new_name) { $oldModDir = $this->getModuleDir(); $old_name = $this->key_name; $this->name = $new_name; $this->key_name = $this->package_key . '_' . $this->name; if ($old_name === $this->key_name) { return; } $newModDir = $this->getModuleDir(); if (file_exists($newModDir)) { return false; } // clean data based on old relationship name before dir is renamed. $this->removeRelationships(); $renamed = rename($oldModDir, $newModDir); if ($renamed) { $this->renameRelationships($newModDir, $old_name, $this->key_name); $this->renameMetaData($newModDir, $old_name); $this->renameLanguageFiles($newModDir); } return $renamed; } private function removeRelationships() { $relationships = $this->getRelationships(); foreach ($relationships->getRelationshipList() as $relationshipName) { $relationships->delete($relationshipName); } } public function renameRelationships($newModDir, $oldModName, $newModName) { if ($oldModName === $newModName) { return; } //bug 39598 Relationship Name Is Not Updated If Module Name Is Changed In Module Builder // and BR-4147 lhs module name was not updated when module name is changed $relationships = new UndeployedRelationships($newModDir); $relationships->renameModule($oldModName, $newModName); $relationships->save(); } public function renameLanguageFiles($new_dir, $duplicate = false) { $this->mblanguage->name = $this->name; $this->mblanguage->path = $new_dir; $this->mblanguage->generateAppStrings(); $this->mblanguage->save($this->key_name, $duplicate, true); } /** * Rename module name in metadata * @param string $new_dir * @param string $old_name */ public function renameMetaData($new_dir, $old_name) { $GLOBALS ['log']->debug('MBModule.php->renameMetaData: new_dir=' . $new_dir); if (!file_exists($new_dir)) { return; } $dir = dir($new_dir); while ($e = $dir->read()) { if (substr($e, 0, 1) != '.') { if (is_dir($new_dir . '/' . $e)) { $this->renameMetaData($new_dir . '/' . $e, $old_name); } if (is_file($new_dir . '/' . $e)) { $contents = file_get_contents($new_dir . '/' . $e); $search_array = [ '/(\$module_name[ ]*=[ ]*\').*(\'[ ]*;)/', '/(\$_module_name[ ]*=[ ]*\').*(\'[ ]*;)/', '/(\$MODULE_NAME[ ]*=[ ]*\').*(\'[ ]*;)/', '/(\$object_name[ ]*=[ ]*\').*(\'[ ]*;)/', '/(\$_object_name[ ]*=[ ]*\').*(\'[ ]*;)/', '/(\$OBJECT_NAME[ ]*=[ ]*\').*(\'[ ]*;)/', ]; $replace_array = [ '$1' . $this->key_name . '$2', '$1' . strtolower($this->key_name) . '$2', '$1' . strtoupper($this->key_name) . '$2', '$1' . $this->key_name . '$2', '$1' . strtolower($this->key_name) . '$2', '$1' . strtoupper($this->key_name) . '$2', ]; $contents = preg_replace($search_array, $replace_array, $contents); $search_array = [ "{$old_name}_", "{$old_name}Dashlet", ]; $replace_array = [ $this->key_name . '_', $this->key_name . 'Dashlet', ]; $contents = str_replace($search_array, $replace_array, $contents); if ('relationships.php' == $e) { if (!empty($this->config['label'])) { $oldLabel = translate($old_name); $newLabel = $this->config['label']; $contents = $this->replaceDefinition('lhs_label', $oldLabel, $newLabel, $contents); $contents = $this->replaceDefinition('rhs_label', $oldLabel, $newLabel, $contents); } } $fp = sugar_fopen($new_dir . '/' . $e, 'w'); fwrite($fp, $contents); fclose($fp); } } } } /** * Replaces parameter which has old value with the new value in relationship definition * * @param string $param Parameter name * @param mixed $oldValue Old value * @param mixed $newValue New value * @param string $contents Relationship definition source contents * * @return string */ protected function replaceDefinition($param, $oldValue, $newValue, $contents) { $param = preg_quote(var_export($param, true), '/'); $oldValue = preg_quote(var_export($oldValue, true), '/'); $newValue = var_export($newValue, true); return preg_replace('/(' . $param . '\s*=>\s*)' . $oldValue . '/', '$1' . $newValue, $contents); } public function copy($new_name) { $old = $this->getModuleDir(); $count = 0; $old_name = $this->key_name; $this->name = $new_name; $this->key_name = $this->package_key . '_' . $this->name; $new = $this->getModuleDir(); while (file_exists($new)) { $count++; $this->name = $new_name . $count; $this->key_name = $this->package_key . '_' . $this->name; $new = $this->getModuleDir(); } $new = $this->getModuleDir(); $copied = copy_recursive($old, $new); if ($copied) { $this->renameRelationships($new, $old_name, $this->key_name); $this->renameMetaData($new, $old_name); $this->renameLanguageFiles($new, true); } return $copied; } public function delete() { $this->load(); // remove module language data from the package $this->mblanguage->delete($this->key_name); // remove module icons from the package $icons = [ 'icon_' . ucfirst($this->key_name) . '_32.png', $this->key_name . '.gif', 'Create' . $this->key_name . '.gif', 'icon_' . $this->key_name . '_bar_32.png', ]; foreach ($icons as $icon) { $file = $this->package_path . '/icons/' . $icon; if (file_exists($file)) { unlink($file); } } // remove associated legacy workflows WorkFlow::deleteWorkFlowsByModule([$this->key_name]); // remove the module itself return rmdir_recursive($this->getModuleDir()); } /** * Populates object from request * * @param Request $request */ public function populateFromPost(Request $request = null) { if (!$request) { $request = InputValidation::getService(); } foreach ($this->implementable as $key => $_) { $this->config[$key] = (bool)$request->getValidInputRequest($key); } foreach ($this->always_implement as $key => $value) { $this->config [$key] = true; } $type = $request->getValidInputRequest('type'); if ($type) { $this->addTemplate($type); } $label = $request->getValidInputRequest('label'); if ($label) { $label = htmlspecialchars_decode($label, ENT_QUOTES); $label = SugarCleaner::cleanHtml($label, false); $this->config['label'] = $label == '' ? $this->name : $label; } $singularLabel = $request->getValidInputRequest('label_singular'); if ($singularLabel) { $label = htmlspecialchars_decode($singularLabel, ENT_QUOTES); $label = SugarCleaner::cleanHtml($label, false); $this->config['label_singular'] = $label == '' ? $this->name : $label; } $this->config['importable'] = (bool)$request->getValidInputRequest('importable'); } /** * Return viewdefs for a subpanel by name. * * @param string $subpanelName Name of the subpanel to get the defs for * @param string $client The client to get the defs for * @param boolean $bwc Is this a BWC request? * @return array */ public function getAvailableSubpanelDef($subpanelName, $client = 'base', $bwc = false) { if (empty($client)) { $client = 'base'; } $filepath = $this->getSubpanelFilePath($subpanelName, $client, $bwc); $var = $bwc ? 'subpanel_layout' : 'viewdefs'; if (file_exists($filepath)) { include FileLoader::validateFilePath($filepath); if (isset(${$var}) && is_array(${$var})) { return ${$var}; } } return []; } /** * Saves a viewdef to the correct path * * @param string $subpanelName Name of the subpanel to get the defs for * @param array $viewdef The defs to save * @param string $client The client to get the defs for * @param boolean $bwc Is this a BWC request? */ public function saveAvailableSubpanelDef($subpanelName, array $viewdef, $client = 'base', $bwc = false) { if (empty($client)) { $client = 'base'; } $filepath = $this->getSubpanelFilePath($subpanelName, $client, $bwc); $dir = dirname($filepath); if (!file_exists($dir) && !mkdir_recursive($dir)) { throw new \RuntimeException(sprintf('Could not make directory %s for subpanel %s'), $dir, $subpanelName); } $GLOBALS['log']->debug("About to save this file to $filepath"); $GLOBALS['log']->debug(print_r($viewdef, true)); if ($bwc) { $array = 'subpanel_layout'; } else { $array = "viewdefs['{$this->key_name}']['{$client}']['view']['{$subpanelName}']"; } write_array_to_file($array, $viewdef, $filepath); } public function getLocalSubpanelDef($panelName) { } public function createIcon() { $icondir = $this->package_path . '/icons'; mkdir_recursive($icondir); $template = ''; foreach ($this->config ['templates'] as $temp => $val) { $template = $temp; } copy("themes/RacerX/images/icon_{$template}_32.png", "$icondir/icon_" . ucfirst($this->key_name) . '_32.png'); copy("include/SugarObjects/templates/$template/icons/$template.gif", "$icondir/" . $this->key_name . '.gif'); if (file_exists("include/SugarObjects/templates/$template/icons/Create$template.gif")) { copy("include/SugarObjects/templates/$template/icons/Create$template.gif", "$icondir/Create" . $this->key_name . '.gif'); } if (file_exists("include/SugarObjects/templates/$template/icons/{$template}_bar_32.png")) { copy("include/SugarObjects/templates/$template/icons/{$template}_bar_32.png", "$icondir/icon_{$this->key_name}_bar_32.png"); } } public function removeFieldFromLayouts($fieldName) { // hardcoded list of types for now, as also hardcoded in a different form in getNodes // TODO: replace by similar mechanism to StudioModule to determine the list of available views for this module $views = ['recordview', 'editview', 'detailview', 'listview', 'basic_search', 'advanced_search', 'popuplist']; foreach ($this->getWirelessLayouts() as $layout) { $views[] = $layout['type']; } foreach ($views as $type) { $parser = ParserFactory::getParser($type, $this->name, $this->package); if ($parser->removeField($fieldName)) { $parser->handleSave(false); // don't populate from $_REQUEST, just save as is... } } //Remove the fields in subpanel $psubs = $this->getProvidedSubpanels(); foreach ($psubs as $sub) { $parser = ParserFactory::getParser(MB_LISTVIEW, $this->name, $this->package, $sub); if ($parser->removeField($fieldName)) { $parser->handleSave(false); } } } /** * Returns an array of fields defs with all the link fields for this module. * @return array */ public function getLinkFields() { $list = $this->relationships->getRelationshipList(); $field_defs = []; foreach ($list as $name) { $rel = $this->relationships->get($name); $relFields = $rel->buildVardefs(); $relDef = $rel->getDefinition(); $relLabels = $rel->getLabels(); $relatedModule = $this->key_name == $relDef['rhs_module'] ? $relDef['lhs_module'] : $relDef['rhs_module']; if (!empty($relFields[$this->key_name])) { //Massage the result of getVardefs to look like field_defs foreach ($relFields[$this->key_name] as $def) { $def['module'] = $relatedModule; $def['translated_label'] = empty($relLabels[$this->key_name][$def['vname']]) ? $name : $relLabels[$this->key_name][$def['vname']]; $field_defs[$def['name']] = $def; } } } return $field_defs; } /** * Returns a TemplateField object by name * Returns a TemplateField object by name or null if field not exists. If type not set use text type as default * * @param string $name * @return TemplateField|null * */ public function getField($name) { $field = null; $varDefs = $this->getVardefs(); if (isset($varDefs['fields'][$name])) { $fieldVarDefs = $varDefs['fields'][$name]; if (!isset($fieldVarDefs['type'])) { $fieldVarDefs['type'] = 'varchar'; } $field = get_widget($fieldVarDefs['type']); foreach ($fieldVarDefs as $key => $opt) { $field->$key = $opt; } } return $field; } /** * Returns an array of field definitions for this bean's module. * * Optionally, you can filter the returned list of field definitions by * field type, name, etc (any property). * * @param string|null $property Field def property to filter by (e.g. type). * @param array $filter An array of values to filter the returned field definitions. * @return array Field definitions. */ public function getFieldDefinitions(?string $property = null, array $filter = []): array { $definitions = $this->getVardefs()['fields'] ?? []; if (empty($property) || empty($filter)) { return $definitions; } return array_filter($definitions, function (array $def) use ($property, $filter): bool { return isset($def[$property]) && in_array($def[$property], $filter); }); } /** * remove team field in recordview when team_security is disabled */ public function cleanupLayout() { if (empty($this->config['team_security'])) { $this->removeFieldFromLayouts('team_name'); } } /** * Gets the sugar object template type for this module * * @return string */ public function getModuleType() { // The last key in the template array it the type $templateKeys = array_keys($this->config['templates']); return end($templateKeys); } /** * Gets the MetadataConverter object * * @return MetaDataConverter */ public function getMetadataConverter() { if ($this->mdc === null) { $this->mdc = new MetaDataConverter(); } return $this->mdc; } /** * Gets a new subpanel path from the name of the subpanel and client * * @param string $name The subpanel name * @param string $client The client * @param boolean $bwc Is this a BWC request? * @return string */ public function getSubpanelFilePath($name, $client, $bwc = false) { $dir = $this->getModuleDir(); if ($bwc === true) { $path = "$dir/metadata/subpanels/$name.php"; } else { // Only convert the name if it needs it, as it may have already been done if (strpos($name, 'subpanel-') === false) { $mdc = $this->getMetadataConverter(); $name = $mdc->fromLegacySubpanelName($name); } $path = "$dir/clients/$client/views/$name/$name.php"; } return $path; } /** * Returns the abbreviated version of the given module name. The * abbreviation is as follows: * * -If a module name is one word: * -(First character of word) + (Second character of word) * -If a module name is more than one word: * -(First character of first word) + (First character of second word) * * @param string $moduleLabel The full label of the module name * @return string The abbreviated version of the module label */ public static function getModuleAbbreviatedLabel($moduleLabel) { $spaceIndex = sugarStrpos($moduleLabel, ' '); if ($spaceIndex !== false && $spaceIndex !== sugarStrlen($moduleLabel) - 1) { return sugarSubstr($moduleLabel, 0, 1) . sugarSubstr($moduleLabel, $spaceIndex + 1, 1); } return sugarSubstr($moduleLabel, 0, 2); } }