]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/ModuleBuilder/parsers/relationships/DeployedRelationships.php
Release 6.5.15
[Github/sugarcrm.git] / modules / ModuleBuilder / parsers / relationships / DeployedRelationships.php
1 <?php
2 if (! defined ( 'sugarEntry' ) || ! sugarEntry)
3     die ( 'Not A Valid Entry Point' ) ;
4 /*********************************************************************************
5  * SugarCRM Community Edition is a customer relationship management program developed by
6  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
7  * 
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.
14  * 
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
18  * details.
19  * 
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
23  * 02110-1301 USA.
24  * 
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.
27  * 
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.
31  * 
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  ********************************************************************************/
38
39
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' ;
43
44
45 class DeployedRelationships extends AbstractRelationships implements RelationshipsInterface
46 {
47
48     function __construct ($moduleName)
49     {
50         $this->moduleName = $moduleName ;
51         $this->load () ;
52     }
53
54     static function findRelatableModules ()
55     {
56         return parent::findRelatableModules ( true ) ;
57     }
58
59     /*
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
64      * 
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
70      */ 
71     function load ()
72     {
73         
74         $relationships = $this->getDeployedRelationships () ;
75         
76         if (! empty ( $relationships ))
77         {
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' ))
81             {
82                 include ('custom/application/Ext/TableDictionary/tabledictionary.ext.php') ;
83             }
84             
85             $invalidModules = array();
86             $validModules = array_keys ( self::findRelatableModules () ) ;
87             
88             // now convert the relationships array into an array of AbstractRelationship objects
89             foreach ( $relationships as $name => $definition )
90             {
91                 if (($definition [ 'lhs_module' ] == $this->moduleName) || ($definition [ 'rhs_module' ] == $this->moduleName))
92                 {
93                     if (in_array ( $definition [ 'lhs_module' ], $validModules ) && in_array ( $definition [ 'rhs_module' ], $validModules )
94                         && ! in_array ( $definition [ 'lhs_module' ], $invalidModules ) && ! in_array ( $definition [ 'rhs_module' ], $invalidModules ))
95                     {
96                         // identify the subpanels for this relationship - TODO: optimize this - currently does m x n scans through the subpanel list...
97                         $definition [ 'rhs_subpanel' ] = self::identifySubpanel ( $definition [ 'lhs_module' ], $definition [ 'rhs_module' ] ) ;
98                         $definition [ 'lhs_subpanel' ] = self::identifySubpanel ( $definition [ 'rhs_module' ], $definition [ 'lhs_module' ] ) ;
99                         
100                         // now adjust the cardinality with the true cardinality found in the relationships metadata (see method comment above)
101                         
102
103                         if (! empty ( $dictionary ) && ! empty ( $dictionary [ $name ] ) ) {
104                                 if (! empty ( $dictionary [ $name ] [ 'true_relationship_type' ] )) {
105                                         $definition [ 'relationship_type' ] = $dictionary [ $name ] [ 'true_relationship_type' ] ;
106                                 }
107                             if (! empty ( $dictionary [ $name ] [ 'from_studio' ] )) {
108                                 $definition [ 'from_studio' ] = $dictionary [ $name ] [ 'from_studio' ] ;
109                             }
110                                 $definition [ 'is_custom' ] = true;
111                         }
112                             
113                         
114                         $this->relationships [ $name ] = RelationshipFactory::newRelationship ( $definition ) ;
115                     }
116                 }
117             }
118         
119         }
120         
121     /*        // Now override with any definitions from the working directory
122         // 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()
123         $overrides = parent::_load ( "custom/working/modules/{$this->moduleName}" ) ;
124         foreach ( $overrides as $name => $relationship )
125         {
126             $this->relationships [ $name ] = $relationship ;
127         }*/
128     
129     }
130
131     /*
132      * Save this modules relationship definitions out to a working file
133      */
134     function save ()
135     {
136         parent::_save ( $this->relationships, "custom/working/modules/{$this->moduleName}" ) ;
137     }
138
139     /*
140      * Update pre-5.1 relationships to the 5.1 relationship definition
141      * There is nothing to do for Deployed relationships as these were only introduced in 5.1
142      * @param array definition  The 5.0 relationship definition
143      * @return array            The definition updated to 5.1 format
144      */
145     protected function _updateRelationshipDefinition ($definition)
146     {
147         return $definition ;
148     }
149
150     /*
151      * Use the module Loader to delete the relationship from the instance.
152      */
153     function delete ($rel_name)
154     {
155         //Remove any fields from layouts
156         $rel = $this->get($rel_name);
157         if (!empty($rel))
158         {
159             $this->removeFieldsFromDeployedLayout($rel);
160         }
161         require_once("ModuleInstall/ModuleInstaller.php");
162         require_once ('modules/Administration/QuickRepairAndRebuild.php') ;
163         $mi = new ModuleInstaller();
164         $mi->silent = true;
165         $mi->id_name = 'custom' . $rel_name; // provide the moduleinstaller with a unique name for this relationship - normally this value is set to the package key...
166         $mi->uninstall_relationship("custom/metadata/{$rel_name}MetaData.php");
167         $mi->uninstallLabels('custom/Extension/modules/relationships/language/',$rel->buildLabels());
168         $mi->uninstallExtLabels($rel->buildLabels());
169         
170         // now clear all caches so that our changes are visible
171         Relationship::delete_cache();
172         $mi->rebuild_tabledictionary();
173         
174         $MBmodStrings = $GLOBALS [ 'mod_strings' ];
175         $GLOBALS [ 'mod_strings' ] = return_module_language ( '', 'Administration' ) ;
176         $rac = new RepairAndClear ( ) ;
177         $rac->repairAndClearAll ( array ( 'clearAll', 'rebuildExtensions',  ), array ( $GLOBALS [ 'mod_strings' ] [ 'LBL_ALL_MODULES' ] ), true, false ) ;
178         $GLOBALS [ 'mod_strings' ] = $MBmodStrings;
179
180         //Bug 41070, supercedes the previous 40941 fix in this section
181         if (isset($this->relationships[$rel_name]))
182         {
183             unset($this->relationships[$rel_name]);
184         }
185     }
186
187     /*
188      * Return the set of all known relevant relationships for a deployed module
189      * The set is made up of the relationships held in this class, plus all those already deployed in the application
190      * @return array Set of all relevant relationships
191      */
192     protected function getAllRelationships ()
193     {
194         return array_merge ( $this->relationships, parent::getDeployedRelationships () ) ;
195     }
196
197     /*
198      * Return the name of the first (currently only) subpanel displayed in the DetailView of $thisModuleName provided by $sourceModuleName
199      * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
200      * @param string $thisModuleName    Name of the related module
201      * @param string $sourceModuleName  Name of the primary module
202      * @return string Name of the subpanel if found; null otherwise
203      */
204     static private function identifySubpanel ($thisModuleName , $sourceModuleName)
205     {
206         $module = get_module_info ( $thisModuleName ) ;
207         require_once ('include/SubPanel/SubPanelDefinitions.php') ;
208         $spd = new SubPanelDefinitions ( $module ) ;
209         $subpanelNames = $spd->get_available_tabs () ; // actually these are the displayed subpanels
210         
211         foreach ( $subpanelNames as $key => $name )
212         {
213             $GLOBALS [ 'log' ]->debug ( $thisModuleName . " " . $name ) ;
214             
215             $subPanel = $spd->load_subpanel ( $name ) ;
216             if ($subPanel && ! isset ( $subPanel->_instance_properties [ 'collection_list' ] ))
217             {
218                 if ($sourceModuleName == $subPanel->_instance_properties [ 'module' ])
219                 {
220                     return $subPanel->_instance_properties [ 'subpanel_name' ] ;
221                 }
222             }
223         }
224         
225         return null ;
226     
227     }
228
229     /*
230      * Return the name of the first (currently only) relate field of $thisModuleName sourced from by $sourceModuleName
231      * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
232      * @param string $thisModuleName    Name of the related module
233      * @param string $sourceModuleName  Name of the primary module
234      * @return string Name of the relate field, if found; null otherwise    
235      */
236     
237     static private function identifyRelateField ($thisModuleName , $sourceModuleName)
238     {
239         $module = get_module_info ( $thisModuleName ) ;
240         
241         foreach ( $module->field_defs as $field )
242         {
243             if ($field [ 'type' ] == 'relate' && isset ( $field [ 'module' ] ) && $field [ 'module' ] == $sourceModuleName)
244                 return $field [ 'name' ] ;
245         }
246         return null ;
247     }
248
249     /*
250      * As of SugarCRM 5.1 the subpanel code and the widgets have difficulty handling multiple subpanels or relate fields from the same module
251      * Until this is fixed, we new relationships which will trigger this problem must be flagged as "relationship_only" and built without a UI component
252      * This function is called from the view when constructing a new relationship
253      * We can assume that both sides of the relationship are deployed modules as this is only called within the context of DeployedRelationships
254      * @param AbstractRelationship $relationship The relationship to be enforced
255      */
256     public function enforceRelationshipOnly ($relationship)
257     {
258         $lhs = $relationship->lhs_module ;
259         $rhs = $relationship->rhs_module ;
260         // if the lhs_module already has a subpanel or relate field sourced from the rhs_module, 
261     // or the rhs_module already has a subpanel or relate field sourced from the lhs_module,
262     // then set "relationship_only" in the relationship
263     
264
265     //        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 ) )))
266     //            $relationship->setRelationship_only () ;
267     }
268
269     /*
270      * BUILD FUNCTIONS
271      */
272     
273     /*
274      * Implement all of the Relationships in this set of relationships
275      * 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...
276      * We use the Extension mechanism to do this for DeployedRelationships
277      * All metadata is placed in the modules Ext directory, and then Rebuild is called to activate them
278      */
279     function build ()
280     {
281         $basepath = "custom/Extension/modules" ;
282         
283         $this->activitiesToAdd = false ;
284         
285         // and mark all as built so that the next time we come through we'll know and won't build again
286         foreach ( $this->relationships as $name => $relationship )
287         {
288             $definition = $relationship->getDefinition () ;
289             // activities will always appear on the rhs only - lhs will be always be this module in MB
290             if (strtolower ( $definition [ 'rhs_module' ] ) == 'activities')
291             {
292                 $this->activitiesToAdd = true ;
293                 $relationshipName = $definition [ 'relationship_name' ] ;
294                 foreach ( self::$activities as $activitiesSubModuleLower => $activitiesSubModuleName )
295                 {
296                     $definition [ 'rhs_module' ] = $activitiesSubModuleName ;
297                     $definition [ 'for_activities' ] = true ;
298                     $definition [ 'relationship_name' ] = $relationshipName . '_' . $activitiesSubModuleLower ;
299                     $this->relationships [ $definition [ 'relationship_name' ] ] = RelationshipFactory::newRelationship ( $definition ) ;
300                 }
301                 unset ( $this->relationships [ $name ] ) ;
302             }
303         }
304         
305         $GLOBALS [ 'log' ]->info ( get_class ( $this ) . "->build(): installing relationships" ) ;
306
307         $MBModStrings = $GLOBALS [ 'mod_strings' ] ;
308         $adminModStrings = return_module_language ( '', 'Administration' ) ; // required by ModuleInstaller
309             
310         foreach ( $this->relationships as $name => $relationship )
311         {
312             $relationship->setFromStudio();
313                 $GLOBALS [ 'mod_strings' ] = $MBModStrings ;
314             $installDefs = parent::build ( $basepath, "<basepath>",  array ($name => $relationship ) ) ;
315
316             // and mark as built so that the next time we come through we'll know and won't build again
317             $relationship->setReadonly () ;
318             $this->relationships [ $name ] = $relationship ;
319
320             // 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...
321             
322             // If we were to chose to just use the Extension mechanism, without using the ModuleInstaller install_...() methods, we must : 
323             // 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
324             // 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)
325             //      Relationships must be in tabledictionary.ext.php for the Admin command Rebuild Relationships to reliably work:
326             //      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
327             //      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)
328             // So instead of doing this, we use common save...() methods between DeployedRelationships and UndeployedRelationships that will produce installDefs,
329             // and rather than building a full manifest file to carry them, we manually add these installDefs to the ModuleInstaller, and then
330             // 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       
331
332             $GLOBALS [ 'mod_strings' ] = $adminModStrings ;
333             require_once 'ModuleInstall/ModuleInstaller.php' ;
334             $mi = new ModuleInstaller ( ) ;
335
336             $mi->id_name = 'custom' . $name ; // provide the moduleinstaller with a unique name for this relationship - normally this value is set to the package key...
337             $mi->installdefs = $installDefs ;
338             $mi->base_dir = $basepath ;
339             $mi->silent = true ;
340
341             
342             VardefManager::clearVardef () ;
343
344             $mi->install_relationships () ;
345             $mi->install_languages () ;
346             $mi->install_vardefs () ;
347             $mi->install_layoutdefs () ;
348             $mi->install_extensions();
349
350         }
351         
352         // Run through the module installer to rebuild the relationships once after everything is done.
353         require_once 'ModuleInstall/ModuleInstaller.php' ;
354         $mi = new ModuleInstaller ( ) ;
355         $mi->silent = true;
356         $mi->rebuild_relationships();
357
358         // now clear all caches so that our changes are visible
359         require_once ('modules/Administration/QuickRepairAndRebuild.php') ;
360         $rac = new RepairAndClear ( ) ;
361         $rac->repairAndClearAll ( array ( 'clearAll' ), array ( $GLOBALS [ 'mod_strings' ] [ 'LBL_ALL_MODULES' ] ), true, false ) ;
362
363         $GLOBALS [ 'mod_strings' ] = $MBModStrings ; // finally, restore the ModuleBuilder mod_strings
364
365         // save out the updated definitions so that we keep track of the change in built status
366         $this->save () ;
367         
368         $GLOBALS [ 'log' ]->info ( get_class ( $this ) . "->build(): finished relationship installation" ) ;
369
370     }
371
372     /*
373      * Add any fields to the DetailView and EditView of the appropriate modules
374      * @param string $basepath              Basepath location for this module (not used)
375      * @param string $relationshipName      Name of this relationship (for uniqueness)
376      * @param array $layoutAdditions  An array of module => fieldname
377      * return null
378      */
379     protected function saveFieldsToLayouts ($basepath , $dummy , $relationshipName , $layoutAdditions)
380     {
381         require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
382         
383         // 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
384         // would be better if GridLayoutMetaDataParser could handle this gracefully, so we don't have to maintain this list here
385         $invalidModules = array ( 'emails' , 'kbdocuments' ) ;
386         
387         foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
388         {
389             if (! in_array ( strtolower ( $deployedModuleName ), $invalidModules ))
390                 foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
391                 {
392                     $GLOBALS [ 'log' ]->info ( get_class ( $this ) . ": adding $fieldName to $view layout for module $deployedModuleName" ) ;
393                     $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
394                     $parser->addField ( array ( 'name' => $fieldName ) ) ;
395                     $parser->handleSave ( false ) ;
396                 }
397         }
398     }
399     
400     /**
401      * Added for bug #40941
402      * Deletes the field from DetailView and editView of the appropriate module
403      * after the relatioship is deleted in delete() function above.
404      * @param $relationship    The relationship that is getting deleted
405      * return null
406      */
407         private function removeFieldsFromDeployedLayout ($relationship)
408     {
409         
410         // many-to-many relationships don't have fields so if we have a many-to-many we can just skip this...
411         if ($relationship->getType () == MB_MANYTOMANY)
412             return false ;
413         
414         $successful = true ;
415         $layoutAdditions = $relationship->buildFieldsToLayouts () ;
416         
417         require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
418         foreach ( $layoutAdditions as $deployedModuleName => $fieldName )
419         {
420             foreach ( array ( MB_EDITVIEW , MB_DETAILVIEW ) as $view )
421             {
422                 $parser = new GridLayoutMetaDataParser ( $view, $deployedModuleName ) ;
423                 $parser->removeField ( $fieldName );
424                 $parser->handleSave ( false ) ;
425              
426             }
427         }
428         
429         return $successful ;
430     }
431
432 }
433
434 ?>