2 if (! defined ( 'sugarEntry' ) || ! sugarEntry)
3 die ( 'Not A Valid Entry Point' ) ;
4 /*********************************************************************************
5 * SugarCRM is a customer relationship management program developed by
6 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU Affero General Public License version 3 as published by the
10 * Free Software Foundation with the addition of the following permission added
11 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
12 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
13 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
20 * You should have received a copy of the GNU Affero General Public License along with
21 * this program; if not, see http://www.gnu.org/licenses or write to the Free
22 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
26 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
28 * The interactive user interfaces in modified source and object code versions
29 * of this program must display Appropriate Legal Notices, as required under
30 * Section 5 of the GNU Affero General Public License version 3.
32 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
33 * these Appropriate Legal Notices must retain the display of the "Powered by
34 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
35 * technical reasons, the Appropriate Legal Notices must display the words
36 * "Powered by SugarCRM".
37 ********************************************************************************/
40 require_once 'modules/ModuleBuilder/parsers/relationships/AbstractRelationships.php' ;
41 require_once 'modules/ModuleBuilder/parsers/relationships/RelationshipsInterface.php' ;
42 require_once 'modules/ModuleBuilder/parsers/relationships/RelationshipFactory.php' ;
45 class DeployedRelationships extends AbstractRelationships implements RelationshipsInterface
48 function __construct ($moduleName)
50 $this->moduleName = $moduleName ;
54 static function findRelatableModules ()
56 return parent::findRelatableModules ( true ) ;
60 * Load the set of relationships for this module - the set is the combination of that held in the working file plus all of the relevant deployed relationships for the module
61 * Note that deployed relationships are readonly and cannot be modified - getDeployedRelationships() takes care of marking them as such
62 * Assumes that only called for modules which exist in $beansList - otherwise get_module_info will break
63 * This means that load() cannot be called for Activities, only Tasks, Notes, etc
65 * Note that we may need to adjust the cardinality for any custom relationships that we do not have entries for in the working directory
66 * These relationships might have been loaded from an installation package by ModuleInstaller, or the custom/working directory might have been cleared at some point
67 * The cardinality in the installed relationship is not necessarily correct for custom relationships, which currently are all built as many-to-many relationships
68 * Instead we must obtain the true cardinality from a property we added to the relationship metadata when we created the relationship
69 * This relationship metadata is accessed through the Table Dictionary
74 $relationships = $this->getDeployedRelationships () ;
76 if (! empty ( $relationships ))
78 // load the relationship definitions for all installed custom relationships into $dictionary
79 $dictionary = array ( ) ;
80 if (file_exists ( 'custom/application/Ext/TableDictionary/tabledictionary.ext.php' ))
82 include ('custom/application/Ext/TableDictionary/tabledictionary.ext.php') ;
85 $invalidModules = array ( 'Users' ) ;
86 $validModules = array_keys ( self::findRelatableModules () ) ;
88 // now convert the relationships array into an array of AbstractRelationship objects
89 foreach ( $relationships as $name => $definition )
91 if (($definition [ 'lhs_module' ] == $this->moduleName) || ($definition [ 'rhs_module' ] == $this->moduleName))
93 if (in_array ( $definition [ 'lhs_module' ], $validModules ) && in_array ( $definition [ 'rhs_module' ], $validModules ) && ! in_array ( $definition [ 'lhs_module' ], $invalidModules ) && ! in_array ( $definition [ 'rhs_module' ], $invalidModules ))
95 // identify the subpanels for this relationship - TODO: optimize this - currently does m x n scans through the subpanel list...
96 $definition [ 'rhs_subpanel' ] = self::identifySubpanel ( $definition [ 'lhs_module' ], $definition [ 'rhs_module' ] ) ;
97 $definition [ 'lhs_subpanel' ] = self::identifySubpanel ( $definition [ 'rhs_module' ], $definition [ 'lhs_module' ] ) ;
99 // now adjust the cardinality with the true cardinality found in the relationships metadata (see method comment above)
102 if (! empty ( $dictionary ) && ! empty ( $dictionary [ $name ] ) ) {
103 if (! empty ( $dictionary [ $name ] [ 'true_relationship_type' ] )) {
104 $definition [ 'relationship_type' ] = $dictionary [ $name ] [ 'true_relationship_type' ] ;
106 if (! empty ( $dictionary [ $name ] [ 'from_studio' ] )) {
107 $definition [ 'from_studio' ] = $dictionary [ $name ] [ 'from_studio' ] ;
109 $definition [ 'is_custom' ] = true;
113 $this->relationships [ $name ] = RelationshipFactory::newRelationship ( $definition ) ;
120 /* // Now override with any definitions from the working directory
121 // must do this to capture one-to-ones that we have created as these don't show up in the relationship table that is the source for getDeployedRelationships()
122 $overrides = parent::_load ( "custom/working/modules/{$this->moduleName}" ) ;
123 foreach ( $overrides as $name => $relationship )
125 $this->relationships [ $name ] = $relationship ;
131 * Save this modules relationship definitions out to a working file
135 parent::_save ( $this->relationships, "custom/working/modules/{$this->moduleName}" ) ;
139 * Update pre-5.1 relationships to the 5.1 relationship definition
140 * There is nothing to do for Deployed relationships as these were only introduced in 5.1
141 * @param array definition The 5.0 relationship definition
142 * @return array The definition updated to 5.1 format
144 protected function _updateRelationshipDefinition ($definition)
150 * Use the module Loader to delete the relationship from the instance.
152 function delete ($rel_name)
154 require_once("ModuleInstall/ModuleInstaller.php");
155 require_once ('modules/Administration/QuickRepairAndRebuild.php') ;
156 $mi = new ModuleInstaller();
158 $mi->uninstall_relationship("custom/metadata/{$rel_name}MetaData.php");
160 // now clear all caches so that our changes are visible
161 Relationship::delete_cache();
162 $mi->rebuild_tabledictionary();
164 $MBmodStrings = $GLOBALS [ 'mod_strings' ];
165 $GLOBALS [ 'mod_strings' ] = return_module_language ( '', 'Administration' ) ;
166 $rac = new RepairAndClear ( ) ;
167 $rac->repairAndClearAll ( array ( 'clearAll', 'rebuildExtensions', ), array ( $GLOBALS [ 'mod_strings' ] [ 'LBL_ALL_MODULES' ] ), true, false ) ;
168 $GLOBALS [ 'mod_strings' ] = $MBmodStrings;
170 if ($relationship = $this->get ( $rel_name ))
172 return $this->removeFieldsFromDeployedLayout ( $relationship) ;
178 * Return the set of all known relevant relationships for a deployed module
179 * The set is made up of the relationships held in this class, plus all those already deployed in the application
180 * @return array Set of all relevant relationships
182 protected function getAllRelationships ()
184 return array_merge ( $this->relationships, parent::getDeployedRelationships () ) ;
188 * Return the name of the first (currently only) subpanel displayed in the DetailView of $thisModuleName provided by $sourceModuleName
189 * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
190 * @param string $thisModuleName Name of the related module
191 * @param string $sourceModuleName Name of the primary module
192 * @return string Name of the subpanel if found; null otherwise
194 static private function identifySubpanel ($thisModuleName , $sourceModuleName)
196 $module = get_module_info ( $thisModuleName ) ;
197 require_once ('include/SubPanel/SubPanelDefinitions.php') ;
198 $spd = new SubPanelDefinitions ( $module ) ;
199 $subpanelNames = $spd->get_available_tabs () ; // actually these are the displayed subpanels
202 $subPanels = array ( ) ;
203 foreach ( $subpanelNames as $key => $name )
205 $GLOBALS [ 'log' ]->debug ( $thisModuleName . " " . $name ) ;
206 $subPanel = $spd->load_subpanel ( $name ) ;
207 if (! isset ( $subPanel->_instance_properties [ 'collection_list' ] ))
209 if ($sourceModuleName == $subPanel->_instance_properties [ 'module' ])
211 return $subPanel->_instance_properties [ 'subpanel_name' ] ;
221 * Return the name of the first (currently only) relate field of $thisModuleName sourced from by $sourceModuleName
222 * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
223 * @param string $thisModuleName Name of the related module
224 * @param string $sourceModuleName Name of the primary module
225 * @return string Name of the relate field, if found; null otherwise
228 static private function identifyRelateField ($thisModuleName , $sourceModuleName)
230 $module = get_module_info ( $thisModuleName ) ;
232 foreach ( $module->field_defs as $field )
234 if ($field [ 'type' ] == 'relate' && isset ( $field [ 'module' ] ) && $field [ 'module' ] == $sourceModuleName)
235 return $field [ 'name' ] ;
241 * As of SugarCRM 5.1 the subpanel code and the widgets have difficulty handling multiple subpanels or relate fields from the same module
242 * Until this is fixed, we new relationships which will trigger this problem must be flagged as "relationship_only" and built without a UI component
243 * This function is called from the view when constructing a new relationship
244 * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
245 * @param AbstractRelationship $relationship The relationship to be enforced
247 public function enforceRelationshipOnly ($relationship)
249 $lhs = $relationship->lhs_module ;
250 $rhs = $relationship->rhs_module ;
251 // if the lhs_module already has a subpanel or relate field sourced from the rhs_module,
252 // or the rhs_module already has a subpanel or relate field sourced from the lhs_module,
253 // then set "relationship_only" in the relationship
256 // if (($relationship->getType() != MB_ONETOONE && ! is_null ( self::identifySubpanel ( $lhs, $rhs ) )) || ($relationship->getType() == MB_MANYTOMANY && ! is_null ( self::identifySubpanel ( $rhs, $lhs ) )) || ($relationship->getType() == MB_ONETOONE && ! is_null ( self::identifyRelateField ( $rhs, $lhs ) )) || ($relationship->getType() != MB_MANYTOMANY && ! is_null ( self::identifyRelateField ( $lhs, $rhs ) )))
257 // $relationship->setRelationship_only () ;
265 * Implement all of the Relationships in this set of relationships
266 * This is more general than it needs to be given that deployed relationships are built immediately - there should only be one relationship to build here...
267 * We use the Extension mechanism to do this for DeployedRelationships
268 * All metadata is placed in the modules Ext directory, and then Rebuild is called to activate them
272 $basepath = "custom/Extension/modules" ;
274 $this->activitiesToAdd = false ;
276 // and mark all as built so that the next time we come through we'll know and won't build again
277 foreach ( $this->relationships as $name => $relationship )
279 $definition = $relationship->getDefinition () ;
280 // activities will always appear on the rhs only - lhs will be always be this module in MB
281 if (strtolower ( $definition [ 'rhs_module' ] ) == 'activities')
283 $this->activitiesToAdd = true ;
284 $relationshipName = $definition [ 'relationship_name' ] ;
285 foreach ( self::$activities as $activitiesSubModuleLower => $activitiesSubModuleName )
287 $definition [ 'rhs_module' ] = $activitiesSubModuleName ;
288 $definition [ 'for_activities' ] = true ;
289 $definition [ 'relationship_name' ] = $relationshipName . '_' . $activitiesSubModuleLower ;
290 $this->relationships [ $definition [ 'relationship_name' ] ] = RelationshipFactory::newRelationship ( $definition ) ;
292 unset ( $this->relationships [ $name ] ) ;
296 $GLOBALS [ 'log' ]->info ( get_class ( $this ) . "->build(): installing relationships" ) ;
298 $MBModStrings = $GLOBALS [ 'mod_strings' ] ;
299 $adminModStrings = return_module_language ( '', 'Administration' ) ; // required by ModuleInstaller
301 foreach ( $this->relationships as $name => $relationship )
303 $relationship->setFromStudio();
304 $GLOBALS [ 'mod_strings' ] = $MBModStrings ;
305 $installDefs = parent::build ( $basepath, "<basepath>", array ($name => $relationship ) ) ;
307 // and mark as built so that the next time we come through we'll know and won't build again
308 $relationship->setReadonly () ;
309 $this->relationships [ $name ] = $relationship ;
311 // now install the relationship - ModuleInstaller normally only does this as part of a package load where it installs the relationships defined in the manifest. However, we don't have a manifest or a package, so...
313 // If we were to chose to just use the Extension mechanism, without using the ModuleInstaller install_...() methods, we must :
314 // 1) place the information for each side of the relationship in the appropriate Ext directory for the module, which means specific $this->save...() methods for DeployedRelationships, and
315 // 2) we must also manually add the relationship into the custom/application/Ext/TableDictionary/tabledictionary.ext.php file as install_relationship doesn't handle that (install_relationships which requires the manifest however does)
316 // Relationships must be in tabledictionary.ext.php for the Admin command Rebuild Relationships to reliably work:
317 // Rebuild Relationships looks for relationships in the modules vardefs.php, in custom/modules/<modulename>/Ext/vardefs/vardefs.ext.php, and in modules/TableDictionary.php and custom/application/Ext/TableDictionary/tabledictionary.ext.php
318 // if the relationship is not defined in one of those four places it could be deleted during a rebuilt, or during a module installation (when RebuildRelationships.php deletes all entries in the Relationships table)
319 // So instead of doing this, we use common save...() methods between DeployedRelationships and UndeployedRelationships that will produce installDefs,
320 // and rather than building a full manifest file to carry them, we manually add these installDefs to the ModuleInstaller, and then
321 // individually call the appropriate ModuleInstaller->install_...() methods to take our relationship out of our staging area and expand it out to the individual module Ext areas
323 $GLOBALS [ 'mod_strings' ] = $adminModStrings ;
324 require_once 'ModuleInstall/ModuleInstaller.php' ;
325 $mi = new ModuleInstaller ( ) ;
327 $mi->id_name = 'custom' . $name ; // provide the moduleinstaller with a unique name for this relationship - normally this value is set to the package key...
328 $mi->installdefs = $installDefs ;
329 $mi->base_dir = $basepath ;
333 VardefManager::clearVardef () ;
335 $mi->install_relationships () ;
336 $mi->install_languages () ;
337 $mi->install_vardefs () ;
338 $mi->install_layoutdefs () ;
342 // now clear all caches so that our changes are visible
343 require_once ('modules/Administration/QuickRepairAndRebuild.php') ;
344 $rac = new RepairAndClear ( ) ;
345 $rac->repairAndClearAll ( array ( 'clearAll' ), array ( $GLOBALS [ 'mod_strings' ] [ 'LBL_ALL_MODULES' ] ), true, false ) ;
347 $GLOBALS [ 'mod_strings' ] = $MBModStrings ; // finally, restore the ModuleBuilder mod_strings
349 // save out the updated definitions so that we keep track of the change in built status
352 $GLOBALS [ 'log' ]->info ( get_class ( $this ) . "->build(): finished relationship installation" ) ;
360 * Add any fields to the DetailView and EditView of the appropriate modules
361 * @param string $basepath Basepath location for this module (not used)
362 * @param string $relationshipName Name of this relationship (for uniqueness)
363 * @param array $layoutAdditions An array of module => fieldname
366 protected function saveFieldsToLayouts ($basepath , $dummy , $relationshipName , $layoutAdditions)
368 require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
370 // these modules either lack editviews/detailviews or use custom mechanisms for the editview/detailview. In either case, we don't want to attempt to add a relate field to them
371 // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
372 $invalidModules = array ( 'emails' , 'kbdocuments' ) ;
374 foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
376 if (! in_array ( strtolower ( $deployedModuleName ), $invalidModules ))
377 foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
379 $GLOBALS [ 'log' ]->info ( get_class ( $this ) . ": adding $fieldName to $view layout for module $deployedModuleName" ) ;
380 $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
381 $parser->addField ( array ( 'name' => $fieldName ) ) ;
382 $parser->handleSave ( false ) ;
388 * Added for bug #40941
389 * Deletes the field from DetailView and editView of the appropriate module
390 * after the relatioship is deleted in delete() function above.
391 * @param $relationship The relationship that is getting deleted
394 private function removeFieldsFromDeployedLayout ($relationship)
397 // many-to-many relationships don't have fields so if we have a many-to-many we can just skip this...
398 if ($relationship->getType () == MB_MANYTOMANY)
402 $layoutAdditions = $relationship->buildFieldsToLayouts () ;
404 require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
405 foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
407 foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
409 $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
410 $parser->removeField ( $fieldName );
411 $parser->handleSave ( false ) ;