]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/ModuleBuilder/parsers/relationships/AbstractRelationship.php
Release 6.5.13
[Github/sugarcrm.git] / modules / ModuleBuilder / parsers / relationships / AbstractRelationship.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 /*
40  * A mechanism to dynamically define new Relationships between modules
41  * This differs from the classes in modules/Relationships and data/Link in that they contain the implementation for pre-defined Relationships
42  * Those classes use the metadata in the dictionary and layout definitions to implement the relationships; this class allows you to manage and manipulate that metadata
43  */
44 class AbstractRelationship
45 {
46     
47     protected $definition ; // enough information to rebuild this relationship
48     
49
50     /*
51      * These are the elements that fully define any Relationship
52      * Any subclass of AbstractRelationship uses an array with a subset of the following keys as metadata to describe the Relationship it will implement
53      * The base set of keys are those used in the Relationships table 
54      * Defined as Public as MBRelationship uses these to read the _POST data
55      */
56     public static $definitionKeys = array ( 
57         // atttributes of this relationship - here in the definition so they are preserved across saves and loads
58         'for_activities',
59         'is_custom',
60         'from_studio',
61         'readonly' , // a readonly relationship cannot be Built by subclasses of AbstractRelationships
62         'deleted' , // a deleted relationship will not be built, and if it had been built previously the built relationship will be removed
63         'relationship_only' , // means that we won't build any UI components for this relationship - required while the Subpanel code is restricted to one subpanel only from any module, and probably useful afterwards also for developers to build relationships for new code - it's a feature!
64         // keys not found in Relationships table
65         'label' , // optional
66         'rhs_label', // optional
67         'lhs_label', // optional
68         'lhs_subpanel' , // subpanel FROM the lhs_module to display on the rhs_module detail view
69         'rhs_subpanel' , // subpanel FROM the rhs_module to display on the lhs_module detail view
70         // keys from Relationships table
71         'relationship_name' ,
72         'lhs_module' , 
73         'lhs_table' , 
74         'lhs_key' , 
75         'rhs_module' , 
76         'rhs_table' , 
77         'rhs_key' , 
78         'join_table' , 
79         'join_key_lhs' , 
80         'join_key_rhs' , 
81         'relationship_type' , 
82         'relationship_role_column' , 
83         'relationship_role_column_value' , 
84         'reverse' ) ;
85
86     /*
87      * Relationship_role_column and relationship_role_column_value:
88      * These two values define an additional condition on the relationship. If present, the value in relationship_role_column in the relationship table must equal relationship_role_column_value
89      * Any update to the relationship made using a link field tied to the relationship (as is proper) will automatically (in Link.php) add in the relationship_role_column_value
90      * The relationship table must of course contain a column with the name given in relationship_role_column
91      * 
92      * relationship_role_column and relationship_role_column_value are here implemented in a slightly less optimized form than in the standard OOB application
93      * In the OOB application, multiple relationships can, and do, share the same relationship table. Therefore, each variant of the relationship does not require its own table
94      * Here for simplicity in implementation each relationship has its own unique table. Therefore, the relationship_role_column in these tables will only contain the value relationship_role_column_value
95      * In the OOB relationships, the relationship_role_column will contain any of the relationship_role_column_values from the relationships that share the table
96      * TODO: implement this optimization
97      * 
98      */
99     
100     /*
101      * Constructor
102      * @param string $definition    Definition array for this relationship. Parameters are given in self::keys
103      */
104     function __construct ($definition)
105     {
106         // set any undefined attributes to the default value
107         foreach ( array ( 'readonly' , 'deleted' , 'relationship_only', 'for_activities', 'is_custom', 'from_studio' ) as $key )
108             if (! isset ( $definition [ $key ] ))
109                 $definition [ $key ] = false ;
110         
111         foreach ( self::$definitionKeys as $key )
112         {
113             $this->$key = isset ( $definition [ $key ] ) ? $definition [ $key ] : '' ;
114         }
115         $this->definition = $definition ;
116     }
117
118     /*
119      * Get the unique name of this relationship
120      * @return string   The unique name (actually just that given to the constructor)
121      */
122     public function getName ()
123     {
124         return isset ( $this->definition [ 'relationship_name' ] ) ? $this->definition [ 'relationship_name' ] : null ;
125     }
126
127     public function setName ($relationshipName)
128     {
129         $this->relationship_name = $this->definition [ 'relationship_name' ] = $relationshipName ;
130     }
131
132     /*
133      * Is this relationship readonly or not?
134      * @return boolean True if cannot be changed; false otherwise
135      */
136     public function readonly ()
137     {
138         return $this->definition [ 'readonly' ] ;
139     }
140
141     public function setReadonly ($set = true)
142     {
143         $this->readonly = $this->definition [ 'readonly' ] = $set ;
144     }
145
146     public function setFromStudio ()
147     {
148         $this->from_studio = $this->definition [ 'from_studio' ] = true ;
149     }
150
151     /*
152      * Has this relationship been deleted? A deleted relationship does not get built, and is no longer visible in the list of relationships
153      * @return boolean True if it has been deleted; false otherwise
154      */
155     public function deleted ()
156     {
157         return $this->definition [ 'deleted' ] ;
158     }
159
160     public function delete ()
161     {
162         $this->deleted = $this->definition [ 'deleted' ] = true ;
163     }
164     
165     public function getFromStudio()
166     {
167         return $this->from_studio;
168     }
169
170     public function getLhsModule()
171     {
172         return $this->lhs_module;
173     }
174
175     public function getRhsModule()
176     {
177         return $this->rhs_module;
178     }
179
180     public function getType ()
181     {
182         return $this->relationship_type ;
183     }
184     
185     public function relationship_only ()
186     {
187         return $this->definition [ 'relationship_only' ] ;   
188     }
189     
190     public function setRelationship_only ()
191     {
192         $this->relationship_only = $this->definition [ 'relationship_only' ] = true ;
193     }
194
195     /*
196      * Get a complete description of this relationship, sufficient to pass back to a constructor to reestablish the relationship
197      * Each subclass must provide enough information in $this->definition for its constructor
198      * Used by UndeployedRelationships to save out a set of AbstractRelationship descriptions
199      * The format is the same as the schema for the Relationships table for convenience, and is defined in self::keys. That is,
200      * `relationship_name`, `lhs_module`, `lhs_table`, `lhs_key`, `rhs_module`, `rhs_table`,`rhs_key`, `join_table`, `join_key_lhs`, `join_key_rhs`, `relationship_type`, `relationship_role_column`, `relationship_role_column_value`, `reverse`,
201      * @return array    Set of parameters to pass to an AbstractRelationship constructor - must contain at least ['relationship_type']='OneToOne' or 'OneToMany' or 'ManyToMany'
202      */
203     function getDefinition ()
204     {
205         return $this->definition ;
206     }
207
208     /*
209      * BUILD methods called during the build
210      */
211     
212     /*
213      * Define the labels to be added to the module for the new relationships
214      * @return array    An array of system value => display value
215      */
216     function buildLabels ($update=false)
217     {
218         $labelDefinitions = array ( ) ;
219         if (!$this->relationship_only)
220         {
221                 if(!$this->is_custom && $update && file_exists("modules/{$this->rhs_module}/metadata/subpaneldefs.php")){
222                         include("modules/{$this->rhs_module}/metadata/subpaneldefs.php");
223                         if(isset($layout_defs[$this->rhs_module]['subpanel_setup'][strtolower($this->lhs_module)]['title_key'])){
224                                 $rightSysLabel = $layout_defs[$this->rhs_module]['subpanel_setup'][strtolower($this->lhs_module)]['title_key'];
225                         }
226                         $layout_defs = array();
227                 }
228                 if(!$this->is_custom && $update && file_exists("modules/{$this->lhs_module}/metadata/subpaneldefs.php")){
229                         include("modules/{$this->lhs_module}/metadata/subpaneldefs.php");
230                         if(isset($layout_defs[$this->lhs_module]['subpanel_setup'][strtolower($this->rhs_module)]['title_key'])){
231                                 $leftSysLabel = $layout_defs[$this->lhs_module]['subpanel_setup'][strtolower($this->rhs_module)]['title_key'];
232                         }
233                         $layout_defs = array();
234                 }
235                 $labelDefinitions [] = array (
236                         'module' => $this->rhs_module ,
237                         'system_label' => isset($rightSysLabel)?$rightSysLabel : 'LBL_' . strtoupper ( $this->relationship_name . '_FROM_' . $this->getLeftModuleSystemLabel() ) . '_TITLE' ,
238                         'display_label' => ($update && !empty($_REQUEST [ 'lhs_label' ] ))?$_REQUEST [ 'lhs_label' ] :(empty($this->lhs_label) ? translate ( $this->lhs_module ) : $this->lhs_label),
239                 ) ;
240             $labelDefinitions [] = array (
241                 'module' => $this->lhs_module ,
242                 'system_label' =>  isset($leftSysLabel)?$leftSysLabel :'LBL_' . strtoupper ( $this->relationship_name . '_FROM_' . $this->getRightModuleSystemLabel() ) . '_TITLE' ,
243                 'display_label' => ($update && !empty($_REQUEST [ 'rhs_label' ] ))?$_REQUEST [ 'rhs_label' ] :(empty($this->rhs_label) ? translate ( $this->rhs_module ) : $this->rhs_label),
244             ) ;
245         }
246         return $labelDefinitions ;
247     }
248
249         function getLeftModuleSystemLabel()
250     {
251                 if($this->lhs_module == $this->rhs_module){
252                         return $this->lhs_module.'_L';
253                 }
254                 return $this->lhs_module;
255     }
256
257     function getRightModuleSystemLabel()
258     {
259                 if($this->lhs_module == $this->rhs_module){
260                         return $this->rhs_module.'_R';
261                 }
262                 return $this->rhs_module;
263     }
264
265     /**
266      * Returns a key=>value set of labels used in this relationship for use when desplaying the relationship in MB
267      * @return array labels used in this relationship
268      */
269     public function getLabels() {
270         $labels = array();
271         $labelDefinitions = $this->buildLabels();
272         foreach($labelDefinitions as $def){
273             $labels[$def['module']][$def['system_label']] = $def['display_label'];
274         }
275
276         return $labels;
277     }
278         
279     /*
280      * GET methods called by the BUILD methods of the subclasses to construct the relationship metadata
281      */
282     
283     /*
284      * Build a description of a Subpanel that can be turned into an actual Subpanel by saveSubpanelDefinition in the implementation
285      * Note that we assume that the subpanel name we are given is valid - that is, a subpanel definition by that name exists, and that a module won't have attempt to define multiple subpanels with the same name
286      * Among the elements we construct is get_subpanel_data which is used as follows in SugarBean:
287      *          $related_field_name = $this_subpanel->get_data_source_name();
288      *          $parentbean->load_relationship($related_field_name);
289      * ...where $related_field_name must be the name of a link field that references the Relationship used to obtain the subpanel data
290      * @param string $sourceModule      Name of the source module for this field
291      * @param string $relationshipName  Name of the relationship
292      * @param string $subpanelName      Name of the subpanel provided by the sourceModule
293      * @param string $titleKeyName      Name of the subpanel title , if none, we will use the module name as the subpanel title.
294      */
295     protected function getSubpanelDefinition ($relationshipName , $sourceModule , $subpanelName, $titleKeyName = '', $source = "")
296     {
297         if (empty($source)) 
298                 $source = $this->getValidDBName($relationshipName);
299         $subpanelDefinition = array ( ) ;
300         $subpanelDefinition [ 'order' ] = 100 ;
301         $subpanelDefinition [ 'module' ] = $sourceModule ;
302         $subpanelDefinition [ 'subpanel_name' ] = $subpanelName ;
303         // following two lines are required for the subpanel pagination code in ListView.php->processUnionBeans() to correctly determine the relevant field for sorting
304         $subpanelDefinition [ 'sort_order' ] = 'asc' ;
305         $subpanelDefinition [ 'sort_by' ] = 'id' ;
306                 if(!empty($titleKeyName)){
307                         $subpanelDefinition [ 'title_key' ] = 'LBL_' . strtoupper ( $relationshipName . '_FROM_' . $titleKeyName ) . '_TITLE' ;
308                 }else{
309                         $subpanelDefinition [ 'title_key' ] = 'LBL_' . strtoupper ( $relationshipName . '_FROM_' . $sourceModule ) . '_TITLE' ;
310                 }
311         $subpanelDefinition [ 'get_subpanel_data' ] = $source ;
312         $subpanelDefinition [ 'top_buttons' ] = array(
313                     array('widget_class' => "SubPanelTopButtonQuickCreate"),
314                     array('widget_class' => 'SubPanelTopSelectButton', 'mode'=>'MultiSelect')
315                 );
316         
317         return array ( $subpanelDefinition );
318     }
319
320     
321
322     /*
323      * Construct a first link id field for the relationship for use in Views
324      * It is used during the save from an edit view in SugarBean->save_relationship_changes(): for each relate field, $this->$linkfieldname->add( $this->$def['id_name'] )
325      * @param string $sourceModule      Name of the source module for this field
326      * @param string $relationshipName  Name of the relationship
327      */
328     protected function getLinkFieldDefinition ($sourceModule , $relationshipName, $right_side = false, $vname = "", $id_name = false)
329     {
330         $vardef = array ( ) ;
331
332         $vardef [ 'name' ] = $this->getValidDBName($relationshipName) ;
333         $vardef [ 'type' ] = 'link' ;
334         $vardef [ 'relationship' ] = $relationshipName ;
335         $vardef [ 'source' ] = 'non-db' ;
336         $vardef [ 'module' ] = $sourceModule ;
337         $vardef [ 'bean_name' ] = BeanFactory::getObjectName($sourceModule) ;
338         if ($right_side)
339                 $vardef [ 'side' ] = 'right' ;
340         if (!empty($vname))
341             $vardef [ 'vname' ] = $vname;
342         if (!empty($id_name))
343             $vardef['id_name'] = $id_name;
344
345         return $vardef ;
346     }
347
348     /*
349      * Construct a second link id field for the relationship for use in Views
350      * It is used in two places:
351      *    - the editview.tpl for Relate fields requires that a field with the same name as the relate field's id_name exists
352      *    - it is loaded in SugarBean->fill_in_link_field while SugarBean processes the relate fields in fill_in_relationship_fields
353      * @param string $sourceModule      Name of the source module for this field
354      * @param string $relationshipName  Name of the relationship
355      */
356     protected function getLink2FieldDefinition ($sourceModule , $relationshipName, $right_side = false,  $vname = "")
357     {
358         $vardef = array ( ) ;
359
360         $vardef [ 'name' ] = $this->getIDName( $sourceModule ) ; // must match the id_name field value in the relate field definition
361         $vardef [ 'type' ] = 'link' ;
362         $vardef [ 'relationship' ] = $relationshipName ;
363         $vardef [ 'source' ] = 'non-db' ;
364                 $vardef ['reportable'] = false;
365         if ($right_side)
366                 $vardef [ 'side' ] = 'right' ;
367         else
368                 $vardef [ 'side' ] = 'left' ;
369         if (!empty($vname))
370             $vardef [ 'vname' ] = $vname;
371
372         return $vardef ;
373     }
374
375     /*
376      * Construct a relate field for the vardefs
377      * The relate field is the element that is shown in the UI
378      * @param string $sourceModule      Name of the source module for this field
379      * @param string $relationshipName  Name of the relationship
380      * @param string $moduleType        Optional - "Types" of the module - array of SugarObject types such as "file" or "basic"
381      */
382     protected function getRelateFieldDefinition ($sourceModule , $relationshipName , $vnameLabel='')
383     {
384         $vardef = array ( ) ;
385         $vardef [ 'name' ] = $this->getValidDBName($relationshipName . "_name") ; // must end in _name for the QuickSearch code in TemplateHandler->createQuickSearchCode
386         $vardef [ 'type' ] = 'relate' ;
387
388         $vardef [ 'source' ] = 'non-db' ;
389                 if(!empty($vnameLabel)){
390                         $vardef [ 'vname' ] = 'LBL_' . strtoupper ( $relationshipName . '_FROM_' . $vnameLabel ) . '_TITLE' ;
391                 }else{
392                         $vardef [ 'vname' ] = 'LBL_' . strtoupper ( $relationshipName . '_FROM_' . $sourceModule ) . '_TITLE' ;
393                 }
394         
395         $vardef [ 'save' ] = true; // the magic value to tell SugarBean to save this relate field even though it is not listed in the $relationship_fields array
396        
397         // id_name matches the join_key_ column in the relationship table for the sourceModule - that is, the column in the relationship table containing the id of the corresponding field in the source module's table (vardef['table'])
398         $vardef [ 'id_name' ] = $this->getIDName( $sourceModule ) ;
399         
400         // link cannot match id_name otherwise the $bean->$id_name value set from the POST is overwritten by the Link object created by this 'link' entry
401         $vardef [ 'link' ] = $this->getValidDBName($relationshipName) ; // the name of the link field that points to the relationship - required for the save to function
402         $vardef [ 'table' ] = $this->getTablename( $sourceModule ) ;
403         $vardef [ 'module' ] = $sourceModule ;
404         
405         require_once 'modules/ModuleBuilder/parsers/relationships/AbstractRelationships.php' ;
406         $parsedModuleName = AbstractRelationships::parseDeployedModuleName( $sourceModule ) ;
407
408         // now determine the appropriate 'rname' field for this relate
409         // the 'rname' points to the field in source module that contains the displayable name for the record
410         // usually this is 'name' but sometimes it is not...
411         
412         $vardef [ 'rname' ] = 'name' ;
413         if ( isset( $parsedModuleName['packageName'] ) )
414         {
415             require_once 'modules/ModuleBuilder/MB/ModuleBuilder.php' ;
416             $mb = new ModuleBuilder ( ) ;
417             $module = $mb->getPackageModule ( $parsedModuleName['packageName'] , $parsedModuleName['moduleName'] ) ;
418             if (in_array( 'file' , array_keys ( $module->config [ 'templates' ] ) ) ){
419                 $vardef [ 'rname' ] = 'document_name' ;
420             }elseif(in_array ( 'person' , array_keys ( $module->config [ 'templates' ] ) ) ){
421                 $vardef [ 'db_concat_fields' ] = array( 0 =>'first_name', 1 =>'last_name') ;
422             }
423         }
424         else
425         {
426             switch ( strtolower( $sourceModule ) )
427             {
428                 case 'prospects' :
429                     $vardef [ 'rname' ] = 'account_name' ;
430                     break ;
431                 case 'documents' :
432                     $vardef [ 'rname' ] = 'document_name' ;
433                     break ;
434                 case 'kbdocuments' :
435                     $vardef [ 'rname' ] = 'kbdocument_name' ;
436                     break ;
437                 case 'leads' :
438                 case 'contacts' : 
439                     // special handling as these modules lack a name column in the database; instead 'name' refers to a non-db field that concatenates first_name and last_name
440                     // luckily, the relate field mechanism can handle this with an equivalent additional db_concat_fields entry
441                     $vardef [ 'rname' ] = 'name' ;
442                     $vardef [ 'db_concat_fields' ] = array( 0 =>'first_name', 1 =>'last_name') ;
443                     break ;
444                 default :
445                     // now see if we have any module inheriting from the 'file' template - records in file-type modules are named by the document_name field, not the usual 'name' field
446                     $object = $GLOBALS ['beanList'] [ $sourceModule ];
447                     require_once ( $GLOBALS ['beanFiles'] [ $object ] );
448                     $bean = new $object();
449                     if ( isset ( $GLOBALS [ 'dictionary' ] [ $object ] [ 'templates'] )){
450                         if(in_array ( 'file' , $GLOBALS [ 'dictionary' ] [ $object ] [ 'templates'] )){
451                                 $vardef [ 'rname' ] = 'document_name' ;
452                         }elseif(in_array ( 'person' , $GLOBALS [ 'dictionary' ] [ $object ] [ 'templates'] )){
453                                  $vardef [ 'db_concat_fields' ] = array( 0 =>'first_name', 1 =>'last_name') ;
454                         }
455                     }
456                         
457             }
458             
459         }
460             
461         return $vardef ;
462     }
463
464     /*
465      * Construct the contents of the Relationships MetaData entry in the dictionary for a generic relationship
466      * The entry we build will have three sections:
467      * 1. relationships: the relationship definition
468      * 2. table: name of the join table for this many-to-many relationship
469      * 3. fields: fields within the join table
470      * 4. indicies: indicies on the join table
471      * @param string $relationshipType  Cardinality of the relationship, for example, MB_ONETOONE or MB_ONETOMANY or MB_MANYTOMANY
472      * @param bool $checkExisting check if a realtionship with the given name is already depolyed in this instance. If so, we will clones its table and column names to preserve existing data.
473      */
474     function getRelationshipMetaData ($relationshipType, $checkExisting = true)
475     {
476         global $dictionary;
477         $relationshipName = $this->definition [ 'relationship_name' ] ;
478         $lhs_module = $this->lhs_module ;
479         $rhs_module = $this->rhs_module ;
480         
481         $lhs_table = $this->getTablename ( $lhs_module ) ;
482         $rhs_table = $this->getTablename ( $rhs_module ) ;
483         
484         $properties = array ( ) ;
485
486         //bug 47903
487         if ($checkExisting && !empty($dictionary[$relationshipName])
488             && !empty($dictionary[$relationshipName][ 'true_relationship_type' ])
489             && $dictionary[$relationshipName][ 'true_relationship_type' ]  == $relationshipType
490             && !empty($dictionary[$relationshipName]['relationships'][$relationshipName]))
491         {
492             //bug 51336
493             $properties [ 'true_relationship_type' ] = $relationshipType ;
494             $rel_properties = $dictionary[$relationshipName]['relationships'][$relationshipName];
495         } else
496         {
497             // first define section 1, the relationship element of the metadata entry
498
499             $rel_properties = array ( ) ;
500             $rel_properties [ 'lhs_module' ] = $lhs_module ;
501             $rel_properties [ 'lhs_table' ] = $lhs_table ;
502             $rel_properties [ 'lhs_key' ] = 'id' ;
503             $rel_properties [ 'rhs_module' ] = $rhs_module ;
504             $rel_properties [ 'rhs_table' ] = $rhs_table ;
505             $rel_properties [ 'rhs_key' ] = 'id' ;
506
507             // because the implementation of one-to-many relationships within SugarBean does not use a join table and so requires schema changes to add a foreign key for each new relationship,
508             // we currently implement all new relationships as many-to-many regardless of the real type and enforce cardinality through the relate fields and subpanels
509             $rel_properties [ 'relationship_type' ] = MB_MANYTOMANY ;
510             // but as we need to display the true cardinality in Studio and ModuleBuilder we also record the actual relationship type
511             // this property is only used by Studio/MB
512             $properties [ 'true_relationship_type' ] = $relationshipType ;
513             if ($this->from_studio)
514                 $properties [ 'from_studio' ] = true;
515
516             $rel_properties [ 'join_table' ] = $this->getValidDBName ( $relationshipName."_c" ) ;
517             // a and b are in case the module relates to itself
518             $rel_properties [ 'join_key_lhs' ] = $this->getJoinKeyLHS() ;
519             $rel_properties [ 'join_key_rhs' ] = $this->getJoinKeyRHS() ;
520         }
521         
522         // set the extended properties if they exist = for now, many-to-many definitions do not have to contain a role_column even if role_column_value is set; we'll just create a likely name if missing
523         if (isset ( $this->definition [ 'relationship_role_column_value' ] ))
524         {
525             if (! isset ( $this->definition [ 'relationship_role_column' ] ))
526                 $this->definition [ 'relationship_role_column' ] = 'relationship_role_column' ;
527             $rel_properties [ 'relationship_role_column' ] = $this->definition [ 'relationship_role_column' ] ;
528             $rel_properties [ 'relationship_role_column_value' ] = $this->definition [ 'relationship_role_column_value' ] ;
529         }
530         
531         $properties [ 'relationships' ] [ $relationshipName ] = $rel_properties ;
532         
533         // construct section 2, the name of the join table
534         
535         $properties [ 'table' ] = $rel_properties [ 'join_table' ] ;
536         
537         // now construct section 3, the fields in the join table
538         
539         $properties [ 'fields' ] [] = array ( 'name' => 'id' , 'type' => 'varchar' , 'len' => 36 ) ;
540         $properties [ 'fields' ] [] = array ( 'name' => 'date_modified' , 'type' => 'datetime' ) ;
541         $properties [ 'fields' ] [] = array ( 'name' => 'deleted' , 'type' => 'bool' , 'len' => '1' , 'default' => '0' , 'required' => true ) ;
542         $properties [ 'fields' ] [] = array ( 'name' => $rel_properties [ 'join_key_lhs' ] , 'type' => 'varchar' , 'len' => 36 ) ;
543         $properties [ 'fields' ] [] = array ( 'name' => $rel_properties [ 'join_key_rhs' ] , 'type' => 'varchar' , 'len' => 36 ) ;
544         if (strtolower ( $lhs_module ) == 'documents' || strtolower ( $rhs_module ) == 'documents' )
545         {
546             $properties [ 'fields' ] [] = array ( 'name' => 'document_revision_id' , 'type' => 'varchar' , 'len' => '36' ) ;
547         }
548         // if we have an extended relationship condition, then add in the corresponding relationship_role_column to the relationship (join) table
549         // for now this is restricted to extended relationships that can be specified by a varchar
550         if (isset ( $this->definition [ 'relationship_role_column_value' ] ))
551         {
552             $properties [ 'fields' ] [] = array ( 'name' => $this->definition [ 'relationship_role_column' ] , 'type' => 'varchar' ) ;
553         }
554         
555         // finally, wrap up with section 4, the indices on the join table
556         
557         $indexBase = $this->getValidDBName ( $relationshipName ) ;
558         $properties [ 'indices' ] [] = array ( 'name' => $indexBase . 'spk' , 'type' => 'primary' , 'fields' => array ( 'id' ) ) ;
559
560         switch ($relationshipType)
561         {
562             case MB_ONETOONE:
563                 $alternateKeys = array () ;
564                 $properties [ 'indices' ] [] = array ( 'name' => $indexBase . '_ida1' , 'type' => 'index' , 'fields' => array ( $rel_properties [ 'join_key_lhs' ] ) ) ;
565                 $properties [ 'indices' ] [] = array ( 'name' => $indexBase . '_idb2' , 'type' => 'index' , 'fields' => array ( $rel_properties [ 'join_key_rhs' ] ) ) ;
566                 break;
567             case MB_ONETOMANY :
568                 $alternateKeys = array ( $rel_properties [ 'join_key_rhs' ] ) ;
569                 $properties [ 'indices' ] [] = array ( 'name' => $indexBase . '_ida1' , 'type' => 'index' , 'fields' => array ( $rel_properties [ 'join_key_lhs' ] ) ) ;
570                 break;
571             default:
572                 $alternateKeys = array ( $rel_properties [ 'join_key_lhs' ] , $rel_properties [ 'join_key_rhs' ] ) ;
573         }
574         
575         if (count($alternateKeys)>0)
576             $properties [ 'indices' ] [] = array ( 'name' => $indexBase . '_alt' , 'type' => 'alternate_key' , 'fields' => $alternateKeys ) ; // type must be set to alternate_key for Link.php to correctly update an existing record rather than inserting a copy - it uses the fields in this array as the keys to check if a duplicate record already exists
577         
578         return $properties ;
579     }
580     
581     
582     /*
583      * UTILITY methods
584      */
585     
586     /*
587      * Method to build a name for a relationship between a module and an Activities submodule
588      * Used primarily in UndeployedRelationships to ensure that the subpanels we construct for Activities get their data from the correct relationships
589      * @param string $activitiesSubModuleName Name of the activities submodule, such as Tasks
590      */
591     function getActivitiesSubModuleRelationshipName ( $activitiesSubModuleName )
592     {
593         return $this->lhs_module . "_" . strtolower ( $activitiesSubModuleName ) ;
594     }
595
596     /*
597      * Return a version of $proposed that can be used as a column name in any of our supported databases
598      * Practically this means no longer than 25 characters as the smallest identifier length for our supported DBs is 30 chars for Oracle plus we add on at least four characters in some places (for indicies for example)
599      * TODO: Ideally this should reside in DBHelper as it is such a common db function...
600      * @param string $name Proposed name for the column
601      * @param string $ensureUnique 
602      * @return string Valid column name trimmed to right length and with invalid characters removed
603      */
604     static function getValidDBName ($name, $ensureUnique = true)
605     {
606
607         require_once 'modules/ModuleBuilder/parsers/constants.php' ;
608         return getValidDBName($name, $ensureUnique, MB_MAXDBIDENTIFIERLENGTH);
609     }
610
611     /*
612      * Tidy up any provided relationship type - convert all the variants of a name to the canonical type - for example, One To Many = MB_ONETOMANY
613      * @param string $type Relationship type
614      * @return string Canonical type
615      */
616     static function parseRelationshipType ($type)
617     {
618         $type = strtolower ( $type ) ;
619         $type = preg_replace ( '/[^\w]+/i', '', strtolower ( $type ) ) ;
620         $canonicalTypes = array ( ) ;
621         foreach ( array ( MB_ONETOONE , MB_ONETOMANY , MB_MANYTOMANY , MB_MANYTOONE) as $canonicalType )
622         {
623             if ($type == preg_replace ( '/[^\w]+/i', '', strtolower ( $canonicalType ) ))
624                 return $canonicalType ;
625         }
626         // ok, we give up...
627         return MB_MANYTOMANY ;
628     }
629
630     
631     function getJoinKeyLHS()
632     {
633         if (!isset($this->joinKeyLHS))
634                 $this->joinKeyLHS = $this->getValidDBName ( $this->relationship_name . $this->lhs_module . "_ida"  , true) ;
635         
636         return $this->joinKeyLHS;
637     }
638     
639     function getJoinKeyRHS()
640     {
641         if (!isset($this->joinKeyRHS))
642                 $this->joinKeyRHS = $this->getValidDBName ( $this->relationship_name . $this->rhs_module . "_idb"  , true) ;
643         
644         return $this->joinKeyRHS;
645     }
646     
647     /*
648      * Return the name of the ID field that will be used to link the subpanel, the link field and the relationship metadata
649      * @param string $sourceModule  The name of the primary module in the relationship
650      * @return string Name of the id field
651      */
652     function getIDName( $sourceModule )
653     {
654         return ($sourceModule == $this->lhs_module ) ? $this->getJoinKeyLHS() : $this->getJoinKeyRHS() ;
655     }
656     
657     /*
658      * Return the name of a module's standard (non-cstm) table in the database
659      * @param string $moduleName    Name of the module for which we are to find the table
660      * @return string Tablename
661      */
662     protected function getTablename ($moduleName)
663     {
664         // Check the moduleName exists in the beanList before calling get_module_info - Activities is the main culprit here
665         if (isset ( $GLOBALS [ 'beanList' ] [ $moduleName ] ))
666         {
667             $module = get_module_info ( $moduleName ) ;
668             return $module->table_name ;
669         }
670         return strtolower ( $moduleName ) ;
671     }
672
673     public function getTitleKey($left=false){
674                 if(!$this->is_custom && !$left && file_exists("modules/{$this->rhs_module}/metadata/subpaneldefs.php")){
675                 include("modules/{$this->rhs_module}/metadata/subpaneldefs.php");
676                 if(isset($layout_defs[$this->rhs_module]['subpanel_setup'][strtolower($this->lhs_module)]['title_key'])){
677                         return $layout_defs[$this->rhs_module]['subpanel_setup'][strtolower($this->lhs_module)]['title_key'];
678                 }
679         }else if(!$this->is_custom &&  file_exists("modules/{$this->lhs_module}/metadata/subpaneldefs.php")){
680                 include("modules/{$this->lhs_module}/metadata/subpaneldefs.php");
681                 if(isset($layout_defs[$this->lhs_module]['subpanel_setup'][strtolower($this->rhs_module)]['title_key'])){
682                         return $layout_defs[$this->lhs_module]['subpanel_setup'][strtolower($this->rhs_module)]['title_key'];
683                 }
684         }
685         
686         if($left){
687                 $titleKeyName = $this->getRightModuleSystemLabel();
688                 $sourceModule = $this->rhs_module;
689         }else{
690                 $titleKeyName = $this->getLeftModuleSystemLabel();
691                 $sourceModule = $this->lhs_module;
692         }
693         
694                 if(!empty($titleKeyName)){
695                         $title_key = 'LBL_' . strtoupper ( $this->relationship_name . '_FROM_' . $titleKeyName ) . '_TITLE' ;
696                 }else{
697                         $title_key = 'LBL_' . strtoupper ( $this->relationship_name . '_FROM_' . $sourceModule ) . '_TITLE' ;
698                 }
699                 
700                 return $title_key;
701         }
702 }