]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Link2.php
Release 6.5.8
[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-2012 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 bean's perspective.
43 * Does not actively do work but is used by sugarbean to manipulate relationships.
44 * Work is deferred 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         // Fix to restore functionality from Link.php that needs to be rewritten but for now this will do.
124         $this->relationship_fields = (!empty($this->def['rel_fields']))?$this->def['rel_fields']: array();
125
126         if (!$this->loadedSuccesfully())
127         {
128             $GLOBALS['log']->fatal("{$this->name} for {$this->def['relationship']} failed to load\n");
129         }
130         //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.
131         //the property values can be altered again before any requests are made.
132         if (!empty($this->def) && is_array($this->def)) {
133             if ( isset($this->def['ignore_role']) ) {
134                 if ($this->def['ignore_role']) {
135                     $this->ignore_role_filter=true;
136                     $this->add_distinct=true;
137                 }
138             }
139         }
140     }
141
142     /**
143      * Returns false if no relationship was found for this link
144      * @return bool
145      */
146     public function loadedSuccesfully()
147     {
148         return !empty($this->relationship);
149     }
150
151     /**
152      *  Forces the link to load the relationship rows.
153      * Will be called internally when the $rows property is accessed or get() is called
154      * @return void
155      */
156     public function load($params = array())
157     {
158         $data = $this->query($params);
159         $this->rows = $data['rows'];
160         $this->beans = null;
161         $this->loaded = true;
162     }
163
164     /**
165      *  Perform a query on this relationship.
166      *
167      * @param array $params An array that can contain the following parameters:<br/>
168      * <ul><li><b>where:</b> An array with 3 key/value pairs.
169      *  lhs_field: The name of the field to check search.
170      *  operator: The operator to use in the search.
171      *  rhs_value: The value to search for.<br/>
172      *  Example:<pre>
173      *  'where' => array(
174              'lhs_field' => 'source',
175              'operator' => '=',
176              'rhs_value' => 'external'
177          )</pre>
178      *  </li>
179      * <li><b>limit:</b> The maximum number of rows</li>
180      * <li><b>deleted:</b> If deleted is set to 1, only deleted records related to the current record will be returned.</li></ul>
181      */
182     public function query($params){
183         return $this->relationship->load($this, $params);
184     }
185
186     /**
187      * @return array ids of records related through this link
188      */
189     public function get($role = false) {
190         if (!$this->loaded)
191             $this->load();
192
193         return array_keys($this->rows);
194     }
195
196     /**
197      * @deprecated
198      *
199      * @return string name of table for the relationship of this link
200      */
201     public function getRelatedTableName() {
202         return BeanFactory::getBean($this->getRelatedModuleName())->table_name;
203     }
204
205     /**
206      * @return string the name of the module on the other side of this link
207      */
208     public function getRelatedModuleName() {
209         if (!$this->relationship) return false;
210
211         if ($this->getSide() == REL_LHS) {
212             return $this->relationship->getRHSModule();
213         } else {
214             return $this->relationship->getLHSModule();
215         }
216     }
217
218     /**
219      * @return string the name of the link field used on the other side of the rel
220      */
221     public function getRelatedModuleLinkName() {
222         if (!$this->relationship) return false;
223
224         if ($this->getSide() == REL_LHS) {
225             return $this->relationship->getRHSLink();
226         } else {
227             return $this->relationship->getLHSLink();
228         }
229     }
230
231     /**
232      *
233      * @return string "many" if multiple records can be related through this link
234      * or "one" if at most, one record can be related.
235      */
236     public function getType()
237     {
238         switch ($this->relationship->type)
239         {
240             case REL_MANY_MANY:
241                 return "many";
242             case REL_ONE_ONE:
243                 return "one";
244             case REL_ONE_MANY:
245                 return $this->getSide() == REL_LHS ? "many" : "one";
246         }
247         return "many";
248     }
249
250     /**
251      * @return SugarBean The parent Bean this link references
252      */
253     public function getFocus()
254     {
255         return $this->focus;
256     }
257
258     /**
259      * @deprecated
260      * @return list of fields that exist only on the relationship
261      */
262     public function getRelatedFields(){
263         return $this->relationship_fields;
264     }
265
266     /**
267      * @param $name
268      * @return The value for the relationship field $name
269      */
270     public function getRelatedField($name){
271         if (!empty($this->relationship_fields) && !empty($this->relationship_fields[$name]))
272             return $this->relationship_fields[$name];
273         else
274             return null; //For now return null. Later try the relationship object directly.
275     }
276
277     /**
278      * @return SugarRelationship the relationship object this link references
279      */
280     public function getRelationshipObject() {
281        return $this->relationship;
282     }
283
284     /**
285      * @return string "LHS" or "RHS" depending on the side of the relationship this link represents
286      */
287     public function getSide() {
288         //First try the relationship
289         if ($this->relationship->getLHSLink() == $this->name &&
290             ($this->relationship->getLHSModule() == $this->focus->module_name)
291         ){
292             return REL_LHS;
293         }
294
295         if ($this->relationship->getRHSLink() == $this->name &&
296             ($this->relationship->getRHSModule() == $this->focus->module_name)
297         ){
298             return REL_RHS;
299         }
300
301         //Next try the vardef
302         if (!empty($this->def['side']))
303         {
304             if ((strtolower($this->def['side']) == 'left' || $this->def['side'] == REL_LHS)
305                 //Some relationships make have left in the vardef erroneously if generated by module builder
306                 && (empty($this->relationship->def['join_key_lhs']) || $this->name != $this->relationship->def['join_key_lhs']))
307             {
308                 return REL_LHS ;
309             }
310             else {
311                 return REL_RHS;
312             }
313         }
314         //Next try using the id_name and relationship join keys
315         else if (!empty($this->def['id_name']))
316         {
317             if (isset($this->relationship->def['join_key_lhs']) && $this->def['id_name'] == $this->relationship->def['join_key_lhs'])
318                 return REL_RHS;
319             else if (isset($this->relationship->def['join_key_rhs']) && $this->def['id_name'] == $this->relationship->def['join_key_rhs'])
320                 return REL_LHS;
321         }
322
323         $GLOBALS['log']->error("Unable to get proper side for link {$this->name}");
324     }
325
326     /**
327      * @return bool true if LHSModule == RHSModule
328      */
329     protected function is_self_relationship() {
330         return $this->relationship->isSelfReferencing();
331     }
332
333     /**
334      * @return bool true if this link represents a relationship where the parent could be one of multiple modules. (ex. Activities parent)
335      */
336     public function isParentRelationship(){
337         return $this->relationship->isParentRelationship();
338     }
339
340     /**
341      * @param $params array of parameters. Possible parameters include:
342      * 'join_table_link_alias': alias the relationship join table in the query (for M2M relationships),
343      * 'join_table_alias': alias for the final table to be joined against (usually a module main table)
344      * @param bool $return_array if true the query is returned as a array broken up into
345      * select, join, where, type, rel_key, and joined_tables
346      * @return string/array join query for this link
347      */
348     function getJoin($params, $return_array =false)
349     {
350         return $this->relationship->getJoin($this, $params, $return_array);
351     }
352
353     /**
354      * @param array $params optional parameters. Possible Values;
355      * 'return_as_array': returns the query broken into
356      * @return String/Array query to grab just ids for this relationship
357      */
358     function getQuery($params = array())
359     {
360         return $this->relationship->getQuery($this, $params);
361     }
362
363     /**
364      * This function is similair getJoin except for M2m relationships it won't join agaist the final table.
365      * Its used to retrieve the ID of the related beans only
366      * @param $params array of parameters. Possible parameters include:
367      * 'return_as_array': returns the query broken into
368      * @param bool $return_array same as passing 'return_as_array' into parameters
369      * @return string/array query to use when joining for subpanels
370      */
371     public function getSubpanelQuery($params = array(), $return_array = false)
372     {
373         if (!empty($this->def['ignore_role']))
374             $params['ignore_role'] = true;
375         return $this->relationship->getSubpanelQuery($this, $params, $return_array);
376     }
377
378     /**
379      * Use with caution as if you have a large list of beans in the relationship,
380      * it can cause the app to timeout or run out of memory.
381      *
382      * @param array $params An array that can contain the following parameters:<br/>
383      * <ul><li><b>where:</b> An array with 3 key/value pairs.
384      *  lhs_field: The name of the field to check search.
385      *  operator: The operator to use in the search.
386      *  rhs_value: The value to search for.<br/>
387      *  Example:<pre>
388      *  'where' => array(
389              'lhs_field' => 'source',
390              'operator' => '=',
391              'rhs_value' => 'external'
392          )</pre>
393      *  </li>
394      * <li><b>limit:</b> The maximum number of beans to load.</li>
395      * <li><b>deleted:</b> If deleted is set to 1, only deleted records related to the current record will be returned.</li></ul>
396      * @return array of SugarBeans related through this link.
397      */
398     function getBeans($params = array()) {
399         //Some depricated code attempts to pass in the old format to getBeans with a large number of useless paramters.
400         //reset the parameters if they are not in the new array format.
401         if (!is_array($params))
402             $params = array();
403
404         if (!$this->loaded && empty($params)) {
405             $this->load();
406         }
407
408         $rows = $this->rows;
409         //If params is set, we are doing a query rather than a complete load of the relationship
410         if (!empty($params)) {
411             $data = $this->query($params);
412             $rows = $data['rows'];
413         }
414
415         $result = array();
416         if(!$this->beansAreLoaded() || !empty($params))
417         {
418             if (!is_array($this->beans))
419                 $this->beans = array();
420
421             $rel_module = $this->getRelatedModuleName();
422
423             //First swap in the temp loaded beans, only if we are doing a complete load (no params)
424             if (empty($params)) {
425                 $result = $this->tempBeans;
426                 $this->tempBeans = array();
427             }
428
429             //now load from the rows
430             foreach ($rows as $id => $vals)
431             {
432                 if (empty($this->beans[$id]))
433                 {
434                     $tmpBean = BeanFactory::getBean($rel_module, $id);
435                     if($tmpBean !== FALSE)
436                         $result[$id] = $tmpBean;
437                 } else {
438                     $result[$id] = $this->beans[$id];
439                 }
440             }
441
442             //If we did a compelte load, cache the result in $this->beans
443             if (empty($params))
444                 $this->beans = $result;
445         } else {
446             $result = $this->beans;
447         }
448
449         return $result;
450     }
451
452     /**
453      * @return bool true if this link has initialized its related beans.
454      */
455     public function beansAreLoaded() {
456         return is_array($this->beans);
457     }
458
459     /**
460      * use this function to create link between 2 objects
461      * 1:1 will be treated like 1 to many.
462      *
463      * the function also allows for setting of values for additional field in the table being
464      * updated to save the relationship, in case of many-to-many relationships this would be the join table.
465      *
466      * @param array $rel_keys array of ids or SugarBean objects. If you have the bean in memory, pass it in.
467      * @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.
468      *
469      * @return boolean|array          Return true if all relationships were added.  Return an array with the failed keys if any of them failed.
470      */
471     function add($rel_keys,$additional_values=array()) {
472         if (!is_array($rel_keys))
473             $rel_keys = array($rel_keys);
474
475         $failures = array();
476
477         foreach($rel_keys as $key)
478         {
479             //We must use beans for LogicHooks and other business logic to fire correctly
480             if (!($key instanceof SugarBean)) {
481                 $key = $this->getRelatedBean($key);
482                 if (!($key instanceof SugarBean)) {
483                     $GLOBALS['log']->error("Unable to load related bean by id");
484                     return false;
485                 }
486             }
487
488             if(empty($key->id) || empty($this->focus->id))
489                 return false;
490
491             if ($this->getSide() == REL_LHS) {
492                 $success = $this->relationship->add($this->focus, $key, $additional_values);
493             }
494             else {
495                 $success = $this->relationship->add($key, $this->focus, $additional_values);
496             }
497
498             if($success == false) {
499                 $failures[] = $key->id;
500             }
501         }
502
503         if(!empty($failures)) {
504             return $failures;
505         }
506
507         return true;
508     }
509
510
511
512     /**
513      * Marks the relationship delted for this given record pair.
514      * @param $id id of the Parent/Focus SugarBean
515      * @param string $related_id id or SugarBean to unrelate. Pass a SugarBean if you have it.
516      * @return boolean          true if delete was successful or false if it was not
517      */
518     function delete($id, $related_id='') {
519         if (empty($this->focus->id))
520             $this->focus = BeanFactory::getBean($this->focus->module_name, $id);
521         if (!empty($related_id))
522         {
523             if (!($related_id instanceof SugarBean)) {
524                 $related_id = $this->getRelatedBean($related_id);
525             }
526             if ($this->getSide() == REL_LHS) {
527                 return $this->relationship->remove($this->focus, $related_id);
528             }
529             else {
530                 return $this->relationship->remove($related_id, $this->focus);
531             }
532         }
533         else
534         {
535             return $this->relationship->removeAll($this);
536         }
537     }
538
539     /**
540      * Returns a SugarBean with the given ID from the related module.
541      * @param bool $id id of related record to retrieve
542      * @return SugarBean
543      */
544     protected function getRelatedBean($id = false)
545     {
546         $params = array('encode' => true, 'deleted' => true);
547         return BeanFactory::getBean($this->getRelatedModuleName(), $id, $params);
548     }
549
550     public function &__get($name)
551     {
552         switch($name)
553         {
554             case "relationship_type":
555                 return $this->relationship->type;
556             case "_relationship":
557                 return $this->relationship;
558             case "beans":
559                 if (!is_array($this->beans))
560                     $this->getBeans();
561                 return $this->beans;
562             case "rows":
563                 if (!is_array($this->rows))
564                     $this->load();
565                 return $this->rows;
566         }
567         return $this->$name;
568     }
569
570     public function __set($name, $val)
571     {
572         if($name == "beans")
573             $this->beans = $val;
574
575     }
576
577     /**
578      * Add a bean object to the list of beans currently loaded to this relationship.
579      * This for the most part should not need to be called except by the relatipnship implementation classes.
580      * @param SugarBean $bean
581      * @return void
582      */
583     public function addBean($bean)
584     {
585         if (!is_array($this->beans))
586         {
587             $this->tempBeans[$bean->id] = $bean;
588         }
589         else {
590             $this->beans[$bean->id] = $bean;
591         }
592
593     }
594
595     /**
596      * Remove a bean object from the list of beans currently loaded to this relationship.
597      * This for the most part should not need to be called except by the relatipnship implementation classes.
598      *
599      * @param SugarBean $bean
600      * @return void
601      */
602     public function removeBean($bean)
603     {
604         if (!is_array($this->beans) && isset($this->tempBeans[$bean->id]))
605         {
606             unset($this->tempBeans[$bean->id]);
607         } else {
608             unset($this->beans[$bean->id]);
609             unset($this->rows[$bean->id]);
610         }
611     }
612
613
614     /**
615      * Directly queries the databse for set of values. The relationship classes and not link should handle this.
616      * @deprecated
617      * @param $table_name string relationship table
618      * @param $join_key_values array of key=>values to identify this relationship by
619      * @return bool true if the given join key set exists in the relationship table
620      */
621     public function relationship_exists($table_name, $join_key_values) {
622
623         //find the key values for the table.
624         $dup_keys=$this->_get_alternate_key_fields($table_name);
625         if (empty($dup_keys)) {
626             $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
627             return false;
628         }
629
630         $delimiter='';
631         $this->_duplicate_where=' WHERE ';
632         foreach ($dup_keys as $field) {
633             //look for key in  $join_key_values, if found add to filter criteria else abort duplicate checking.
634             if (isset($join_key_values[$field])) {
635
636                 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
637                 $delimiter='AND';
638             } else {
639                 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
640                 return false;
641             }
642         }
643         //add deleted check.
644         $this->_duplicate_where .= $delimiter.' deleted=0';
645
646         $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
647
648         $GLOBALS['log']->debug("relationship_exists query(".$query.')');
649
650         $result=$this->_db->query($query, true);
651         $row = $this->_db->fetchByAssoc($result);
652
653         if ($row == null) {
654             return false;
655         }
656         else {
657             $this->_duplicate_key=$row['id'];
658             return true;
659         }
660     }
661
662     //Below are functions not used directly and exist for backwards compatibility with customizations, will be removed in a later version
663
664     /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
665      * primary key.
666      *
667      */
668     public function _get_alternate_key_fields($table_name) {
669         $indices=Link::_get_link_table_definition($table_name,'indices');
670         if (!empty($indices)) {
671             foreach ($indices as $index) {
672                 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
673                     return $index['fields'];
674                 }
675             }
676         }
677         //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.
678         $relDef = $this->relationship->def;
679         if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
680             return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
681     }
682
683     /**
684      * @deprecated
685      * Gets the vardef for the relationship of this link.
686      */
687     public function _get_link_table_definition($table_name, $def_name) {
688
689         if (isset($this->relationship->def[$def_name]))
690             return $this->relationship->def[$def_name];
691
692         return null;
693     }
694
695     /**
696      * @deprecated
697      * Return the name of the role field for the passed many to many table.
698      * if there is no role filed : return false
699      * @param $table_name name of relationship table to inspect
700      * @return bool|string
701      */
702     public function _get_link_table_role_field($table_name) {
703         $varDefs = $this->_get_link_table_definition($table_name, 'fields');
704         $role_field = false;
705         if(!empty($varDefs)){
706             $role_field = '';
707             foreach($varDefs as $v){
708                 if(strpos($v['name'], '_role') !== false){
709                     $role_field = $v['name'];
710                 }
711             }
712         }
713         return $role_field;
714     }
715
716     /**
717      * @deprecated
718      *
719      * @return boolean returns true if this link is LHS
720      */
721     public function _get_bean_position()
722     {
723         return $this->getSide() == REL_LHS;
724     }
725 }