2 if (! defined ( 'sugarEntry' ) || ! sugarEntry)
3 die ( 'Not A Valid Entry Point' ) ;
5 /*********************************************************************************
6 * SugarCRM Community Edition is a customer relationship management program developed by
7 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
9 * This program is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU Affero General Public License version 3 as published by the
11 * Free Software Foundation with the addition of the following permission added
12 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
13 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
14 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
21 * You should have received a copy of the GNU Affero General Public License along with
22 * this program; if not, see http://www.gnu.org/licenses or write to the Free
23 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
27 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
29 * The interactive user interfaces in modified source and object code versions
30 * of this program must display Appropriate Legal Notices, as required under
31 * Section 5 of the GNU Affero General Public License version 3.
33 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
34 * these Appropriate Legal Notices must retain the display of the "Powered by
35 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
36 * technical reasons, the Appropriate Legal Notices must display the words
37 * "Powered by SugarCRM".
38 ********************************************************************************/
42 * Abstract class for managing a set of Relationships
43 * The Relationships we're managing consist of metadata about relationships, rather than relationship implementations used by the application
44 * Relationships defined here are implemented by the build() method to become a relationship that the application can use
45 * Note that the modules/Relationships/Relationship.php contains some methods that look similar; remember though that the methods in that file are acting on implemented relationships, not the metadata that we deal with here
47 class AbstractRelationships
50 static $methods = array (
51 'Labels' => 'language' ,
52 'RelationshipMetaData' => 'relationships' ,
53 'SubpanelDefinitions' => 'layoutdefs' ,
54 'Vardefs' => 'vardefs' ,
55 'FieldsToLayouts' => 'layoutfields',
57 static $activities = array ( 'calls' => 'Calls' , 'meetings' => 'Meetings' , 'notes' => 'Notes' , 'tasks' => 'Tasks' , 'emails' => 'Emails' ) ;
59 protected $relationships = array ( ) ; // array containing all the AbstractRelationship objects that are in this set of relationships
60 protected $moduleName ;
62 // bug33522 - the following relationship names that you would find in $dictionary[ <relationshipName> ]
63 // have different actual relationship names other than <relationshipName>
64 // e.g $dictionary[ 'quotes_accounts' ] has two relationships: quotes_billto_accounts, quotes_shipto_accounts
65 protected $specialCaseBaseNames = array( 'quotes_accounts',
70 'prospect_lists_prospects',
76 * Find all deployed modules that can participate in a relationship
77 * Return a list of modules with associated subpanels
78 * @param boolean $includeActivitiesSubmodules True if the list should include Calls, Meetings etc; false if they should be replaced by the parent, Activities
79 * @return array Array of [$module][$subpanel]
81 static function findRelatableModules ($includeActivitiesSubmodules = true)
83 $relatableModules = array ( ) ;
85 // add in activities automatically if required
86 $relatableModules [ 'Activities' ] [ 'default' ] = translate( 'LBL_DEFAULT' ) ;
88 // find all deployed modules
89 require_once 'modules/ModuleBuilder/Module/StudioBrowser.php' ;
90 $browser = new StudioBrowser() ;
91 $browser->loadRelatableModules();
92 reset($browser->modules) ;
94 while ( list( $moduleName , $module ) = each($browser->modules) )
96 // do not include the submodules of Activities as already have the parent...
97 if (! $includeActivitiesSubmodules && in_array ( $module->module, self::$activities ))
99 $providedSubpanels = $module->getProvidedSubpanels();
100 if ( $providedSubpanels !== false ) {
101 $relatableModules [ $module->module ] = $providedSubpanels;
105 return $relatableModules ;
109 static function validSubpanel ($filename)
111 if (! file_exists ( $filename ))
115 return (isset ( $subpanel_layout ) && (isset ( $subpanel_layout [ 'top_buttons' ] ) && isset ( $subpanel_layout [ 'list_fields' ] ))) ;
119 * Get a list of all relationships (which have not been deleted)
120 * @return array Array of relationship names, ready for use in get()
122 function getRelationshipList ()
125 foreach ( $this->relationships as $name => $relationship )
127 if (! $relationship->deleted ())
128 $list [ $name ] = $name ;
134 * Get a relationship by name
135 * @param string $relationshipName The unique name for this relationship, as returned by $relationship->getName()
136 * @return AbstractRelationship or false if $relationshipName is not in this set of relationships
138 function get ($relationshipName)
140 if (isset ( $this->relationships [ $relationshipName ] ))
142 return $this->relationships [ $relationshipName ] ;
148 * Construct a relationship from the information in the $_REQUEST array
149 * If a relationship_name is provided, and that relationship is not read only, then modify the existing relationship, overriding the definition with any AbstractRelationship::$definitionkeys entries set in the $_REQUEST
150 * Otherwise, create and add a new relationship with the information in the $_REQUEST
151 * @return AbstractRelationship
153 function addFromPost ()
155 $definition = array ( ) ;
157 require_once 'modules/ModuleBuilder/parsers/relationships/AbstractRelationship.php' ;
158 foreach ( AbstractRelationship::$definitionKeys as $key )
160 if (! empty ( $_REQUEST [ $key ] ))
162 $definition [ $key ] = ($key == 'relationship_type') ? AbstractRelationship::parseRelationshipType ( $_REQUEST [ $key ] ) : $_REQUEST [ $key ] ;
166 // if this is a change to an existing relationship, and it is not readonly, then delete the old one
167 if (! empty ( $_REQUEST [ 'relationship_name' ] ))
169 if ($relationship = $this->get ( $_REQUEST [ 'relationship_name' ] ))
171 unset( $definition[ 'relationship_name' ] ) ; // in case the related modules have changed; this name is probably no longer appropriate
172 if (! $relationship->readonly ())
173 $this->delete ( $_REQUEST [ 'relationship_name' ] ) ;
177 $newRelationship = RelationshipFactory::newRelationship ( $definition ) ;
178 // TODO: error handling in case we get a badly formed definition and hence relationship
179 $this->add ( $newRelationship ) ;
180 return $newRelationship ;
184 * Add a relationship to the set
185 * @param AbstractRelationship $relationship The relationship to add
187 function add ($relationship)
189 $name = $this->getUniqueName ( $relationship ) ;
190 $relationship->setName ( $name ) ;
191 $this->relationships [ $name ] = $relationship ;
195 * Load a set of relationships from a file
196 * The saved relationships are stored as AbstractRelationship objects, which isn't the same as the old MBRelationships definition
197 * @param string $basepath Base directory in which to store the relationships information
198 * @return Array of AbstractRelationship objects
200 protected function _load ($basepath)
202 $GLOBALS [ 'log' ]->info ( get_class ( $this ) . ": loading relationships from " . $basepath . '/relationships.php' ) ;
203 $objects = array ( ) ;
204 if (file_exists ( $basepath . '/relationships.php' ))
206 include ($basepath . '/relationships.php') ;
207 foreach ( $relationships as $name => $definition )
209 // update any pre-5.1 relationships to the new definitions
210 // we do this here, rather than when upgrading from 5.0 to 5.1, as modules exported from MB in 5.0 may be loaded into 5.1 at any time
211 // note also that since these definitions are only found in the relationships.php working file they only occur for deployed or exported modules, not published then loaded modules
212 $definition = $this->_updateRelationshipDefinition( $definition ) ;
213 $relationship = RelationshipFactory::newRelationship ( $definition ) ;
214 // make sure it has a unique name
215 if (! isset( $definition [ 'relationship_name' ] ) )
217 $name = $this->getUniqueName ( $relationship ) ;
218 $relationship->setName ( $name ) ;
220 $objects [ $name ] = $relationship ;
227 * Save the set of relationships to a file
228 * @param string $basepath Base directory in which to store the relationships information
230 protected function _save ($relationships , $basepath)
232 $GLOBALS [ 'log' ]->info ( get_class ( $this ) . ": saving relationships to " . $basepath . '/relationships.php' ) ;
233 $header = file_get_contents ( 'modules/ModuleBuilder/MB/header.php' ) ;
235 $definitions = array ( ) ;
237 foreach ( $relationships as $relationship )
239 // if (! $relationship->readonly ())
240 $definitions [ $relationship->getName () ] = $relationship->getDefinition () ;
243 mkdir_recursive ( $basepath ) ;
244 // replace any existing relationships.php
245 write_array_to_file ( 'relationships', $definitions, $basepath . '/relationships.php', 'w', $header ) ;
249 * Return all known deployed relationships
250 * All are set to read-only - the assumption for now is that we can't directly modify a deployed relationship
251 * However, if it was created through this AbstractRelationships class a modifiable version will be held in the relationships working file,
252 * and that one will override the readonly version in load()
254 * TODO: currently we ignore the value of the 'reverse' field in the relationships definition. This is safe to do as only one
255 * relationship (products-products) uses it (and there it makes no difference from our POV) and we don't use it when creating new ones
256 * @return array Array of $relationshipName => $relationshipDefinition as an array
258 protected function getDeployedRelationships ()
261 $db = DBManagerFactory::getInstance () ;
262 $query = "SELECT * FROM relationships WHERE deleted = 0" ;
263 $result = $db->query ( $query ) ;
264 while ( $row = $db->fetchByAssoc ( $result ) )
266 // set this relationship to readonly
267 $row [ 'readonly' ] = true ;
268 $relationships [ $row [ 'relationship_name' ] ] = $row ;
271 return $relationships ;
275 * Get a name for this relationship that is unique across all of the relationships we are aware of
276 * We make the name unique by simply adding on a suffix until we achieve uniqueness
277 * @param AbstractRelationship The relationship object
278 * @return string A globally unique relationship name
280 protected function getUniqueName ($relationship)
282 $allRelationships = $this->getRelationshipList () ;
283 $basename = $relationship->getName () ;
285 if (empty ( $basename ))
287 // start off with the proposed name being simply lhs_module_rhs_module
288 $definition = $relationship->getDefinition () ;
289 $basename = strtolower ( $definition [ 'lhs_module' ] . '_' . $definition [ 'rhs_module' ] ) ;
292 // Bug #49024 : Relationships Created in Earlier Versions Cause Conflicts and AJAX Errors After Upgrade
293 // ...all custom relationships created via Studio should always have a numeric identifier attached.
294 if ( $this instanceof DeployedRelationships )
296 $name = $basename . '_1' ;
305 while ( isset ( $allRelationships [ $name ] ) )
307 $name = $basename . "_" . ( string ) ($suffix ++) ;
310 // bug33522 - if our relationship basename is in the special cases
311 if( in_array( $name , $this->specialCaseBaseNames ) ) {
312 //add a _1 (or _suffix#) and check to see if it already exists
313 $name = $name . "_" . ( string ) ($suffix ++);
314 while ( isset ( $allRelationships [ $name ] ) )
316 // if it does exist, strip off the _1 previously added and try again
317 $name = substr( $name , 0 , -2 ) . "_" . ( string ) ($suffix ++);
325 * Translate the set of relationship objects into files that the Module Loader can work with
326 * @param string $basepath Pathname of the directory to contain the build
327 * @param string $installDefPrefix Pathname prefix for the installdefs, for example for ModuleBuilder use "<basepath>/SugarModules"
328 * @param array $relationships Relationships to implement
330 protected function build ($basepath , $installDefPrefix , $relationships )
332 global $sugar_config;
333 // keep the relationships data separate from any other build data by ading /relationships to the basepath
334 $basepath .= '/relationships' ;
336 $installDefs = array ( ) ;
337 $compositeAdded = false ;
338 foreach ( self::$methods as $method => $key )
340 $buildMethod = 'build' . $method ;
341 $saveMethod = 'save' . $method ;
343 foreach ( $relationships as $name => $relationship )
345 if (! ($relationship->readonly () || $relationship->deleted ()))
347 if (method_exists ( $relationship, $buildMethod ) && method_exists ( $this, $saveMethod ))
349 $metadata = $relationship->$buildMethod () ;
351 if (count ( $metadata ) > 0) // don't clutter up the filesystem with empty files...
353 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . ": BUILD is running METHOD $saveMethod" ) ;
354 $installDef = $this->$saveMethod ( $basepath, $installDefPrefix, $name, $metadata ) ;
356 // some save methods (e.g., saveRelateFieldDefinition) handle the installDefs internally and so return null
359 if (! is_null ( $installDef ))
361 foreach ( $installDef as $moduleName => $def )
363 $installDefs [ $key ] [ ] = $def ;
373 return $installDefs ;
377 * SAVE methods called during the build to translate the metadata provided by each relationship into files for the module installer
378 * Note that the installer expects only one file for each module in each section of the manifest - multiple files result in only the last one being implemented!
382 * Add a set of labels to the module
383 * @param string $basepath Basepath location for this module
384 * @param $installDefPrefix Pathname prefix for the installdefs, for example for ModuleBuilder use "<basepath>/SugarModules"
385 * @param string $relationshipName Name of this relationship (for uniqueness)
386 * @param array $labelDefinitions Array of System label => Display label pairs
387 * @return null Nothing to be added to the installdefs for an undeployed module
389 protected function saveLabels ($basepath , $installDefPrefix , $relationshipName , $labelDefinitions)
391 global $sugar_config;
393 mkdir_recursive ( "$basepath/language" ) ;
395 $headerString = "<?php\n//THIS FILE IS AUTO GENERATED, DO NOT MODIFY\n" ;
396 $installDefs = array ( ) ;
397 foreach ( $labelDefinitions as $definition )
399 $mod_strings = array();
400 $app_list_strings = array();
402 $out = $headerString;
404 $filename = "{$basepath}/language/{$definition['module']}.php" ;
406 if (file_exists ( $filename ))
410 //Check for app strings
411 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->saveLabels(): saving the following to {$filename}"
412 . print_r ( $definition, true ) ) ;
413 if ($definition['module'] == 'application') {
414 $app_list_strings[$definition [ 'system_label' ]] = $definition [ 'display_label' ];
415 foreach ($app_list_strings as $key => $val)
416 $out .= override_value_to_string_recursive2('app_list_strings', $key, $val);
418 $mod_strings[ $definition [ 'system_label' ]] = $definition [ 'display_label' ];
419 foreach ($mod_strings as $key => $val)
420 $out .= override_value_to_string_recursive2('mod_strings', $key, $val);
423 $fh = fopen ( $filename, 'w' ) ;
424 fputs ( $fh, $out, strlen ( $out ) ) ;
428 foreach($sugar_config['languages'] as $lk => $lv)
430 $installDefs [ $definition [ 'module' ] . "_$lk" ] = array (
431 'from' => "{$installDefPrefix}/relationships/language/{$definition [ 'module' ]}.php" ,
432 'to_module' => $definition [ 'module' ] ,
437 /* do not use the following write_array_to_file method to write the label file -
438 * module installer appends each of the label files together (as it does for all files)
439 * into a combined label file and so the last $mod_strings is the only one received by the application */
440 // write_array_to_file ( 'mod_strings', array ( $definition [ 'system_label' ] => $definition [ 'display_label' ] ), $filename, "a" ) ;
443 return $installDefs ;
447 * Translate a set of relationship metadata definitions into files for the Module Loader
448 * @param string $basepath Basepath location for this module
449 * @param $installDefPrefix Pathname prefix for the installdefs, for example for ModuleBuilder use "<basepath>/SugarModules"
450 * @param string $relationshipName Name of this relationship (for uniqueness)
451 * @param array $relationshipMetaData Set of metadata definitions in the form $relationshipMetaData[$relationshipName]
452 * @return array $installDefs Set of new installDefs
454 protected function saveRelationshipMetaData ($basepath , $installDefPrefix , $relationshipName , $relationshipMetaData)
456 mkdir_recursive ( "$basepath/relationships" ) ;
458 $installDefs = array ( ) ;
459 list ( $rhs_module, $properties ) = each ( $relationshipMetaData ) ;
460 $filename = "$basepath/relationships/{$relationshipName}MetaData.php" ;
461 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->saveRelationshipMetaData(): saving the following to {$filename}" . print_r ( $properties, true ) ) ;
462 write_array_to_file ( 'dictionary["' . $relationshipName . '"]', $properties, "{$filename}", 'w' ) ;
463 $installDefs [ $relationshipName ] = array ( /*'module' => $rhs_module , 'module_vardefs' => "<basepath>/Vardefs/{$relationshipName}.php" ,*/ 'meta_data' => "{$installDefPrefix}/relationships/relationships/{$relationshipName}MetaData.php" ) ;
465 return $installDefs ;
469 * Translate a set of subpanelDefinitions into files for the Module Loader
470 * @param string $basepath Basepath location for this module
471 * @param $installDefPrefix Pathname prefix for the installdefs, for example for ModuleBuilder use "<basepath>/SugarModules"
472 * @param array $subpanelDefinitions Set of subpanel definitions in the form $subpanelDefinitions[$for_module][]
473 * @return array $installDefs Set of new installDefs
475 protected function saveSubpanelDefinitions ($basepath , $installDefPrefix , $relationshipName , $subpanelDefinitions)
477 mkdir_recursive ( "$basepath/layoutdefs/" ) ;
479 foreach ( $subpanelDefinitions as $moduleName => $definitions )
481 $filename = "$basepath/layoutdefs/{$relationshipName}_{$moduleName}.php" ;
482 $subpanelVarname = 'layout_defs["' . $moduleName . '"]["subpanel_setup"]';
484 foreach ( $definitions as $definition )
486 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->saveSubpanelDefinitions(): saving the following to {$filename}" . print_r ( $definition, true ) ) ;
487 if (empty($definition ['get_subpanel_data']) || $definition ['subpanel_name'] == 'history' || $definition ['subpanel_name'] == 'activities') {
488 $definition ['get_subpanel_data'] = $definition ['subpanel_name'];
490 $out .= override_value_to_string($subpanelVarname, strtolower ( $definition [ 'get_subpanel_data' ] ), $definition) . "\n";
493 $out = "<?php\n // created: " . date('Y-m-d H:i:s') . "\n" . $out;
494 sugar_file_put_contents($filename, $out);
497 $installDefs [ $moduleName ] = array ( 'from' => "{$installDefPrefix}/relationships/layoutdefs/{$relationshipName}_{$moduleName}.php" , 'to_module' => $moduleName ) ;
499 return $installDefs ;
504 * Translate a set of linkFieldDefinitions into files for the Module Loader
505 * Note that the Module Loader will only accept one entry in the vardef section of the Manifest for each module
506 * This means that we cannot simply build a file for each relationship as relationships that involve the same module will end up overwriting each other when installed
507 * So we have to append the vardefs for each relationship to a single file for each module
508 * @param string $basepath Basepath location for this module
509 * @param $installDefPrefix Pathname prefix for the installdefs, for example for ModuleBuilder use "<basepath>/SugarModules"
510 * @param string $relationshipName Name of this relationship (for uniqueness)
511 * @param array $linkFieldDefinitions Set of link field definitions in the form $linkFieldDefinitions[$for_module]
512 * @return array $installDefs Set of new installDefs
514 protected function saveVardefs ($basepath , $installDefPrefix , $relationshipName , $vardefs)
516 mkdir_recursive ( "$basepath/vardefs/" ) ;
517 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->saveVardefs(): vardefs =" . print_r ( $vardefs, true ) ) ;
519 foreach ( $vardefs as $moduleName => $definitions )
521 // find this module's Object name - the object name, not the module name, is used as the key in the vardefs...
522 if (isset ( $GLOBALS [ 'beanList' ] [ $moduleName ] ))
524 $module = get_module_info ( $moduleName ) ;
525 $object = $module->object_name ;
528 $object = $moduleName ;
531 $relName = $moduleName;
532 foreach ( $definitions as $definition )
534 if (!empty($definition['relationship']))
536 $relName = $definition['relationship'];
541 $filename = "$basepath/vardefs/{$relName}_{$moduleName}.php" ;
543 $out = "<?php\n// created: " . date('Y-m-d H:i:s') . "\n";
544 foreach ( $definitions as $definition )
546 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->saveVardefs(): saving the following to {$filename}" . print_r ( $definition, true ) ) ;
547 $out .= '$dictionary["' . $object . '"]["fields"]["' . $definition [ 'name' ] . '"] = '
548 . var_export_helper($definition) . ";\n";
550 file_put_contents($filename, $out);
552 $installDefs [ $moduleName ] = array (
553 'from' => "{$installDefPrefix}/relationships/vardefs/{$relName}_{$moduleName}.php" ,
554 'to_module' => $moduleName
558 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->saveVardefs(): installDefs =" . print_r ( $installDefs, true ) ) ;
560 return $installDefs ;
565 * Determine if we're dealing with a deployed or undeployed module based on the name
566 * Undeployed modules are those known to ModuleBuilder; the twist is that the deployed names of modulebuilder modules are keyname_modulename not packagename_modulename
567 * and ModuleBuilder doesn't have any accessor methods based around keys, so we must convert keynames to packagenames
568 * @param $deployedName Name of the module in the deployed form - that is, keyname_modulename or modulename
569 * @return array ('moduleName'=>name, 'packageName'=>package) if undeployed, ('moduleName'=>name) if deployed
571 static function parseDeployedModuleName ($deployedName)
573 require_once 'modules/ModuleBuilder/MB/ModuleBuilder.php' ;
574 $mb = new ModuleBuilder ( ) ;
577 $moduleName = $deployedName ;
579 foreach ( $mb->getPackageList () as $name )
581 // convert the keyName into a packageName, needed for checking to see if this is really an undeployed module, or just a module with a _ in the name...
582 $package = $mb->getPackage ( $name ) ; // seem to need to call getPackage twice to get the key correctly... TODO: figure out why...
583 $key = $mb->getPackage ( $name )->key ;
584 if (strlen ( $key ) < strlen ( $deployedName ))
586 $position = stripos ( $deployedName, $key ) ;
587 $moduleName = trim( substr( $deployedName , strlen($key) ) , '_' ); //use trim rather than just assuming that _ is between packageName and moduleName in the deployedName
588 if ( $position !== false && $position == 0 && (isset ( $mb->packages [ $name ]->modules [ $moduleName ] )))
590 $packageName = $name ;
596 if (! empty ( $packageName ))
598 return array ( 'moduleName' => $moduleName , 'packageName' => $packageName ) ;
601 return array ( 'moduleName' => $deployedName ) ;