]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Link2.php
Release 6.4.0
[Github/sugarcrm.git] / data / Link2.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-2011 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 /*********************************************************************************
41
42 * Description:  Represents a relationship from a single beans perspective.
43 * Does not activly do work but is used by sugarbean to manipulate relationships.
44 * Work is defered to the relationship classes.
45  *
46 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
47 * All Rights Reserved.
48 * Contributor(s): ______________________________________..
49 ********************************************************************************/
50 global $dictionary;
51 require_once("data/Relationships/RelationshipFactory.php");
52
53 /**
54  * Represents a relationship from a single beans perspective.
55  * @api
56  */
57 class Link2 {
58
59     protected $relationship; //relationship object this link is tied to.
60     protected $focus;  //SugarBean this link uses as the context for its calls.
61     protected $def;  //Field def for this link
62     protected $name;  //Field name for this link
63     protected $beans;  //beans on the other side of the link
64     protected $rows;   //any additional fields on the relationship
65     protected $loaded; //true if this link has been loaded from the database
66     protected $relationship_fields = array();
67     //Used to store unsaved beans on this relationship that will be combined with the ones pulled from the DB if getBeans() is called.
68     protected $tempBeans = array();
69
70     /**
71      * @param  $linkName String name of a link field in the module's vardefs
72      * @param  $bean SugarBean focus bean for this link (one half of a relationship)
73      * @param  $linkDef Array Optional vardef for the link in case it can't be found in the passed in bean for the global dictionary
74      * @return void
75      *
76      */
77     function __construct($linkName, $bean, $linkDef = false){
78         $this->focus = $bean;
79         //Try to load the link vardef from the beans field defs. Otherwise start searching
80         if (empty($bean->field_defs) || empty($bean->field_defs[$linkName]) || empty($bean->field_defs[$linkName]['relationship']))
81         {
82             if (empty($linkDef))
83             {
84                 //Assume $linkName is really relationship_name, and find the link name with the vardef manager
85                 $this->def = VardefManager::getLinkFieldForRelationship($bean->module_dir, $bean->object_name, $linkName);
86             }
87             else {
88                 $this->def = $linkDef;
89             }
90             //Check if multiple links were found for a given relationship
91             if (is_array($this->def) && !isset($this->def['name']))
92             {
93                 //More than one link found, we need to figure out if we are currently on the LHS or RHS
94                 //default to lhs for now
95                 if (isset($this->def[0]['side']) && $this->def[0]['side'] == 'left')
96                 {
97                     $this->def = $this->def[0];
98                 }
99                 else if (isset($this->def[1]['side']) && $this->def[1]['side'] == 'left')
100                 {
101                     $this->def = $this->def[1];
102                 }
103                 else {
104                     $this->def = $this->def[0];
105                 }
106             }
107             if (empty($this->def['name']))
108             {
109                 $GLOBALS['log']->fatal("failed to find link for $linkName");
110                 return false;
111             }
112
113             $this->name = $this->def['name'];
114         }
115         else {
116             //Linkdef was found in the bean (this is the normal expectation)
117             $this->def = $bean->field_defs[$linkName];
118             $this->name = $linkName;
119         }
120         //Instantiate the relationship for this link.
121         $this->relationship = SugarRelationshipFactory::getInstance()->getRelationship($this->def['relationship']);
122
123         if (!$this->loadedSuccesfully())
124         {
125             $GLOBALS['log']->fatal("{$this->name} for {$this->def['relationship']} failed to load\n");
126         }
127         //Following behavior is tied to a property(ignore_role) value in the vardef. It alters the values of 2 properties, ignore_role_filter and add_distinct.
128         //the property values can be altered again before any requests are made.
129         if (!empty($this->def) && is_array($this->def)) {
130             if (array_key_exists('ignore_role', $this->def)) {
131                 if ($this->def['ignore_role']) {
132                     $this->ignore_role_filter=true;
133                     $this->add_distinct=true;
134                 }
135             }
136         }
137     }
138
139     /**
140      * Returns false if no relationship was found for this link
141      * @return bool
142      */
143     public function loadedSuccesfully()
144     {
145         return !empty($this->relationship);
146     }
147
148     /**
149      *  Forces the link to load the relationship rows.
150      * Will be called internally when the $rows property is accessed or get() is called
151      * @return void
152      */
153     public function load()
154     {
155         $data = $this->relationship->load($this);
156         $this->rows = $data['rows'];
157         $this->beans = null;
158         $this->loaded = true;
159     }
160
161     /**
162      * @return array ids of records related through this link
163      */
164     public function get($role = false) {
165         if (!$this->loaded)
166             $this->load();
167
168         return array_keys($this->rows);
169     }
170
171     /**
172      * @deprecated
173      *
174      * @return string name of table for the relationship of this link
175      */
176     public function getRelatedTableName() {
177         return BeanFactory::getBean($this->getRelatedModuleName())->table_name;
178     }
179
180     /**
181      * @return string the name of the module on the other side of this link
182      */
183     public function getRelatedModuleName() {
184         if (!$this->relationship) return false;
185
186         if ($this->getSide() == REL_LHS) {
187             return $this->relationship->getRHSModule();
188         } else {
189             return $this->relationship->getLHSModule();
190         }
191     }
192
193     /**
194      * @return string the name of the link field used on the other side of the rel
195      */
196     public function getRelatedModuleLinkName() {
197         if (!$this->relationship) return false;
198
199         if ($this->getSide() == REL_LHS) {
200             return $this->relationship->getRHSLink();
201         } else {
202             return $this->relationship->getLHSLink();
203         }
204     }
205
206     /**
207      *
208      * @return string "many" if multiple records can be related through this link
209      * or "one" if at most, one record can be related.
210      */
211     public function getType()
212     {
213         switch ($this->relationship->type)
214         {
215             case REL_MANY_MANY:
216                 return "many";
217             case REL_ONE_ONE:
218                 return "one";
219             case REL_ONE_MANY:
220                 return $this->getSide() == REL_LHS ? "many" : "one";
221         }
222         return "many";
223     }
224
225     /**
226      * @return SugarBean The parent Bean this link references
227      */
228     public function getFocus()
229     {
230         return $this->focus;
231     }
232
233     /**
234      * @deprecated
235      * @return list of fields that exist only on the relationship
236      */
237     public function getRelatedFields(){
238         return $this->relationship_fields;
239     }
240
241     /**
242      * @param $name
243      * @return The value for the relationship field $name
244      */
245     public function getRelatedField($name){
246         if (!empty($this->relationship_fields) && !empty($this->relationship_fields[$name]))
247             return $this->relationship_fields[$name];
248         else
249             return null; //For now return null. Later try the relationship object directly.
250     }
251
252     /**
253      * @return SugarRelationship the relationship object this link references
254      */
255     public function getRelationshipObject() {
256        return $this->relationship;
257     }
258
259     /**
260      * @return string "LHS" or "RHS" depending on the side of the relationship this link represents
261      */
262     public function getSide() {
263         //First try the relationship
264         if ($this->relationship->getLHSLink() == $this->name &&
265             ($this->relationship->getLHSModule() == $this->focus->module_name)
266         ){
267             return REL_LHS;
268         }
269
270         if ($this->relationship->getRHSLink() == $this->name &&
271             ($this->relationship->getRHSModule() == $this->focus->module_name)
272         ){
273             return REL_RHS;
274         }
275
276         //Next try the vardef
277         if (!empty($this->def['side']))
278         {
279             if ((strtolower($this->def['side']) == 'left' || $this->def['side'] == REL_LHS)
280                 //Some relationships make have left in the vardef errorneously if generated by module builder
281                 && $this->name != $this->relationship->def['join_key_lhs'])
282             {
283                 return REL_LHS ;
284             }
285             else {
286                 return REL_RHS;
287             }
288         }
289         //Next try using the id_name and relationship join keys
290         else if (!empty($this->def['id_name']))
291         {
292             if (isset($this->relationship->def['join_key_lhs']) && $this->def['id_name'] == $this->relationship->def['join_key_lhs'])
293                 return REL_RHS;
294             else if (isset($this->relationship->def['join_key_rhs']) && $this->def['id_name'] == $this->relationship->def['join_key_rhs'])
295                 return REL_LHS;
296         }
297
298         $GLOBALS['log']->error("Unable to get proper side for link {$this->name}");
299     }
300
301     /**
302      * @return bool true if LHSModule == RHSModule
303      */
304     protected function is_self_relationship() {
305         return $this->relationship->isSelfReferencing();
306     }
307
308     /**
309      * @return bool true if this link represents a relationship where the parent could be one of multiple modules. (ex. Activities parent)
310      */
311     public function isParentRelationship(){
312         return $this->relationship->isParentRelationship();
313     }
314
315     /**
316      * @param $params array of parameters. Possible parameters include:
317      * 'join_table_link_alias': alias the relationship join table in the query (for M2M relationships),
318      * 'join_table_alias': alias for the final table to be joined against (usually a module main table)
319      * @param bool $return_array if true the query is returned as a array broken up into
320      * select, join, where, type, rel_key, and joined_tables
321      * @return string/array join query for this link
322      */
323     function getJoin($params, $return_array =false)
324     {
325         return $this->relationship->getJoin($this, $params, $return_array);
326     }
327
328     /**
329      * @param array $params optional parameters. Possible Values;
330      * 'return_as_array': returns the query broken into
331      * @return String/Array query to grab just ids for this relationship
332      */
333     function getQuery($params = array())
334     {
335         return $this->relationship->getQuery($this, $params);
336     }
337
338     /**
339      * This function is similair getJoin except for M2m relationships it won't join agaist the final table.
340      * Its used to retrieve the ID of the related beans only
341      * @param $params array of parameters. Possible parameters include:
342      * 'return_as_array': returns the query broken into
343      * @param bool $return_array same as passing 'return_as_array' into parameters
344      * @return string/array query to use when joining for subpanels
345      */
346     public function getSubpanelQuery($params = array(), $return_array = false)
347     {
348         if (!empty($this->def['ignore_role']))
349             $params['ignore_role'] = true;
350         return $this->relationship->getSubpanelQuery($this, $params, $return_array);
351     }
352
353     /**
354      * @return array of SugarBeans related through this link. Use with caution.
355      */
356     function getBeans() {
357         if (!$this->loaded) {
358             $this->load();
359         }
360         if(!is_array($this->beans))
361         {
362             $this->beans = array();
363             $rel_module = $this->getRelatedModuleName();
364             //First swap in the temp loaded beans
365             $this->beans = $this->tempBeans;
366             $this->tempBeans = array();
367             //now load from the rows
368             foreach ($this->rows as $id => $vals)
369             {
370                 if (empty($this->beans[$id]))
371                 {
372                     $tmpBean = BeanFactory::getBean($rel_module, $id);
373                     if($tmpBean !== FALSE)
374                         $this->beans[$id] = $tmpBean;
375                 }
376             }
377         }
378
379         return $this->beans;
380     }
381
382     /**
383      * @return bool true if this link has initialized its related beans.
384      */
385     public function beansAreLoaded() {
386         return is_array($this->beans);
387     }
388
389     /**
390      * use this function to create link between 2 objects
391      * 1:1 will be treated like 1 to many.
392      *
393      * the function also allows for setting of values for additional field in the table being
394      * updated to save the relationship, in case of many-to-many relationships this would be the join table.
395      *
396      * @param array $rel_keys array of ids or SugarBean objects. If you have the bean in memory, pass it in.
397      * @param array $additional_values the values should be passed as key value pairs with column name as the key name and column value as key value.
398      *
399      * @return void
400      */
401     function add($rel_keys,$additional_values=array()) {
402         if (!is_array($rel_keys))
403             $rel_keys = array($rel_keys);
404
405         foreach($rel_keys as $key)
406         {
407             //We must use beans for LogicHooks and other buisiness logic to fire correctly
408             if (!($key instanceof SugarBean)) {
409                 $key = $this->getRelatedBean($key);
410                 if (!($key instanceof SugarBean)) {
411                     $GLOBALS['log']->error("Unable to load related bean by id");
412                     return false;
413                 }
414             }
415
416             if(empty($key->id) || empty($this->focus->id))
417                 return false;
418
419             if ($this->getSide() == REL_LHS) {
420                 $this->relationship->add($this->focus, $key, $additional_values);
421             }
422             else {
423                 $this->relationship->add($key, $this->focus, $additional_values);
424             }
425         }
426     }
427
428
429
430     /**
431      * Marks the relationship delted for this given record pair.
432      * @param $id id of the Parent/Focus SugarBean
433      * @param string $related_id id or SugarBean to unrelate. Pass a SugarBean if you have it.
434      * @return void
435      */
436     function delete($id, $related_id='') {
437         if (empty($this->focus->id))
438             $this->focus = BeanFactory::getBean($this->focus->module_name, $id);
439         if (!empty($related_id))
440         {
441             if (!($related_id instanceof SugarBean)) {
442                 $related_id = $this->getRelatedBean($related_id);
443             }
444             if ($this->getSide() == REL_LHS) {
445                 $this->relationship->remove($this->focus, $related_id);
446             }
447             else {
448                 $this->relationship->remove($related_id, $this->focus);
449             }
450         }
451         else
452         {
453             $this->relationship->removeAll($this);
454         }
455     }
456
457     /**
458      * Returns a SugarBean with the given ID from the related module.
459      * @param bool $id id of related record to retrieve
460      * @return SugarBean
461      */
462     protected function getRelatedBean($id = false)
463     {
464         return BeanFactory::getBean($this->getRelatedModuleName(), $id);
465     }
466
467     public function &__get($name)
468     {
469         switch($name)
470         {
471             case "relationship_type":
472                 return $this->relationship->type;
473             case "_relationship":
474                 return $this->relationship;
475             case "beans":
476                 if (!is_array($this->beans))
477                     $this->getBeans();
478                 return $this->beans;
479             case "rows":
480                 if (!is_array($this->rows))
481                     $this->load();
482                 return $this->rows;
483         }
484         return $this->$name;
485     }
486
487     public function __set($name, $val)
488     {
489         if($name == "beans")
490             $this->beans = $val;
491
492     }
493
494     /**
495      * Add a bean object to the list of beans currently loaded to this relationship.
496      * This for the most part should not need to be called except by the relatipnship implementation classes.
497      * @param SugarBean $bean
498      * @return void
499      */
500     public function addBean($bean)
501     {
502         if (!is_array($this->beans))
503         {
504             $this->tempBeans[$bean->id] = $bean;
505         }
506         else {
507             $this->beans[$bean->id] = $bean;
508         }
509
510     }
511
512     /**
513      * Remove a bean object from the list of beans currently loaded to this relationship.
514      * This for the most part should not need to be called except by the relatipnship implementation classes.
515      *
516      * @param SugarBean $bean
517      * @return void
518      */
519     public function removeBean($bean)
520     {
521         if (!is_array($this->beans) && isset($this->tempBeans[$bean->id]))
522         {
523             unset($this->tempBeans[$bean->id]);
524         } else {
525             unset($this->beans[$bean->id]);
526             unset($this->rows[$bean->id]);
527         }
528     }
529
530
531     /**
532      * Directly queries the databse for set of values. The relationship classes and not link should handle this.
533      * @deprecated
534      * @param $table_name string relationship table
535      * @param $join_key_values array of key=>values to identify this relationship by
536      * @return bool true if the given join key set exists in the relationship table
537      */
538     public function relationship_exists($table_name, $join_key_values) {
539
540         //find the key values for the table.
541         $dup_keys=$this->_get_alternate_key_fields($table_name);
542         if (empty($dup_keys)) {
543             $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
544             return false;
545         }
546
547         $delimiter='';
548         $this->_duplicate_where=' WHERE ';
549         foreach ($dup_keys as $field) {
550             //look for key in  $join_key_values, if found add to filter criteria else abort duplicate checking.
551             if (isset($join_key_values[$field])) {
552
553                 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
554                 $delimiter='AND';
555             } else {
556                 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
557                 return false;
558             }
559         }
560         //add deleted check.
561         $this->_duplicate_where .= $delimiter.' deleted=0';
562
563         $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
564
565         $GLOBALS['log']->debug("relationship_exists query(".$query.')');
566
567         $result=$this->_db->query($query, true);
568         $row = $this->_db->fetchByAssoc($result);
569
570         if ($row == null) {
571             return false;
572         }
573         else {
574             $this->_duplicate_key=$row['id'];
575             return true;
576         }
577     }
578
579     //Below are functions not used directly and exist for backwards compatiblity with customizations, will be removed in a later version
580
581     /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
582      * primary key.
583      *
584      */
585     public function _get_alternate_key_fields($table_name) {
586         $indices=Link::_get_link_table_definition($table_name,'indices');
587         if (!empty($indices)) {
588             foreach ($indices as $index) {
589                 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
590                     return $index['fields'];
591                 }
592             }
593         }
594         //bug 32623, when the relationship is built in old version, there is no alternate_key. we have to use join_key_lhs and join_key_lhs.
595         $relDef = $this->relationship->def;
596         if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
597             return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
598     }
599
600     /**
601      * @deprecated
602      * Gets the vardef for the relationship of this link.
603      */
604     public function _get_link_table_definition($table_name, $def_name) {
605
606         if (isset($this->relationship->def[$def_name]))
607             return $this->relationship->def[$def_name];
608
609         return null;
610     }
611
612     /**
613      * @deprecated
614      * Return the name of the role field for the passed many to many table.
615      * if there is no role filed : return false
616      * @param $table_name name of relationship table to inspect
617      * @return bool|string
618      */
619     public function _get_link_table_role_field($table_name) {
620         $varDefs = $this->_get_link_table_definition($table_name, 'fields');
621         $role_field = false;
622         if(!empty($varDefs)){
623             $role_field = '';
624             foreach($varDefs as $v){
625                 if(strpos($v['name'], '_role') !== false){
626                     $role_field = $v['name'];
627                 }
628             }
629         }
630         return $role_field;
631     }
632
633     /**
634      * @deprecated
635      *
636      * @return boolean returns true if this link is LHS
637      */
638     public function _get_bean_position()
639     {
640         return $this->getSide() == REL_LHS;
641     }
642 }