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.
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.
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
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
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.
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.
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 ********************************************************************************/
38 require_once('soap/SoapHelperFunctions.php');
39 require_once('soap/SoapTypes.php');
41 /*************************************************************************************
43 THIS IS FOR SUGARCRM USERS
46 *************************************************************************************/
47 $disable_date_format = true;
51 array('session'=>'xsd:string'),
52 array('return'=>'xsd:int'),
56 * Return if the user is an admin or not
58 * @param String $session -- Session ID returned by a previous call to login.
59 * @return int 1 or 0 depending on if the user is an admin
61 function is_user_admin($session){
62 if(validate_authenticated($session)){
64 return is_admin($current_user);
74 array('user_auth'=>'tns:user_auth', 'application_name'=>'xsd:string'),
75 array('return'=>'tns:set_entry_result'),
79 * Log the user into the application
81 * @param UserAuth array $user_auth -- Set user_name and password (password needs to be
82 * in the right encoding for the type of authentication the user is setup for. For Base
83 * sugar validation, password is the MD5 sum of the plain text password.
84 * @param String $application -- The name of the application you are logging in from. (Currently unused).
85 * @return Array(session_id, error) -- session_id is the id of the session that was
86 * created. Error is set if there was any error during creation.
88 function login($user_auth, $application){
89 global $sugar_config, $system_config;
91 $error = new SoapError();
95 $system_config = new Administration();
96 $system_config->retrieveSettings('system');
97 $authController = new AuthenticationController((!empty($sugar_config['authenticationClass'])? $sugar_config['authenticationClass'] : 'SugarAuthenticate'));
99 $isLoginSuccess = $authController->login($user_auth['user_name'], $user_auth['password'], array('passwordEncrypted' => true));
100 $usr_id=$user->retrieve_user_id($user_auth['user_name']);
102 $user->retrieve($usr_id);
105 if ($isLoginSuccess) {
106 if ($_SESSION['hasExpiredPassword'] =='1') {
107 $error->set_error('password_expired');
108 $GLOBALS['log']->fatal('password expired for user ' . $user_auth['user_name']);
109 LogicHook::initialize();
110 $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
111 return array('id'=>-1, 'error'=>$error);
113 if(!empty($user) && !empty($user->id) && !$user->is_group) {
115 global $current_user;
116 $current_user = $user;
118 } else if($usr_id && isset($user->user_name) && ($user->getPreference('lockout') == '1')) {
119 $error->set_error('lockout_reached');
120 $GLOBALS['log']->fatal('Lockout reached for user ' . $user_auth['user_name']);
121 LogicHook::initialize();
122 $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
123 return array('id'=>-1, 'error'=>$error);
124 } else if(function_exists('mcrypt_cbc')){
125 $password = decrypt_string($user_auth['password']);
126 $authController = new AuthenticationController((!empty($sugar_config['authenticationClass'])? $sugar_config['authenticationClass'] : 'SugarAuthenticate'));
127 if($authController->login($user_auth['user_name'], $password) && isset($_SESSION['authenticated_user_id'])){
134 global $current_user;
135 //$current_user = $user;
137 $current_user->loadPreferences();
138 $_SESSION['is_valid_session']= true;
139 $_SESSION['ip_address'] = query_client_ip();
140 $_SESSION['user_id'] = $current_user->id;
141 $_SESSION['type'] = 'user';
142 $_SESSION['avail_modules']= get_user_module_list($current_user);
143 $_SESSION['authenticated_user_id'] = $current_user->id;
144 $_SESSION['unique_key'] = $sugar_config['unique_key'];
146 $current_user->call_custom_logic('after_login');
147 return array('id'=>session_id(), 'error'=>$error);
149 $error->set_error('invalid_login');
150 $GLOBALS['log']->fatal('SECURITY: User authentication for '. $user_auth['user_name']. ' failed');
151 LogicHook::initialize();
152 $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
153 return array('id'=>-1, 'error'=>$error);
157 //checks if the soap server and client are running on the same machine
161 array('return'=>'xsd:int'),
165 * Check to see if the soap server and client are on the same machine.
166 * We don't allow a server to sync to itself.
168 * @return true -- if the SOAP server and client are on the same machine
169 * @return false -- if the SOAP server and client are not on the same machine.
171 function is_loopback(){
172 if(query_client_ip() == $_SERVER['SERVER_ADDR'])
178 * Validate the provided session information is correct and current. Load the session.
180 * @param String $session_id -- The session ID that was returned by a call to login.
181 * @return true -- If the session is valid and loaded.
182 * @return false -- if the session is not valid.
184 function validate_authenticated($session_id){
185 if(!empty($session_id)){
186 session_id($session_id);
189 if(!empty($_SESSION['is_valid_session']) && is_valid_ip_address('ip_address') && $_SESSION['type'] == 'user'){
191 global $current_user;
193 $current_user = new User();
194 $current_user->retrieve($_SESSION['user_id']);
201 LogicHook::initialize();
202 $GLOBALS['log']->fatal('SECURITY: The session ID is invalid');
203 $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
208 * Use the same logic as in SugarAuthenticate to validate the ip address
210 * @param string $session_var
211 * @return bool - true if the ip address is valid, false otherwise.
213 function is_valid_ip_address($session_var){
214 global $sugar_config;
215 // grab client ip address
216 $clientIP = query_client_ip();
218 // check to see if config entry is present, if not, verify client ip
219 if (!isset ($sugar_config['verify_client_ip']) || $sugar_config['verify_client_ip'] == true) {
220 // check to see if we've got a current ip address in $_SESSION
221 // and check to see if the session has been hijacked by a foreign ip
222 if (isset ($_SESSION[$session_var])) {
223 $session_parts = explode(".", $_SESSION[$session_var]);
224 $client_parts = explode(".", $clientIP);
225 if(count($session_parts) < 4) {
228 // match class C IP addresses
229 for ($i = 0; $i < 3; $i ++) {
230 if ($session_parts[$i] == $client_parts[$i]) {
239 // we have a different IP address
240 if ($_SESSION[$session_var] != $clientIP && empty ($classCheck)) {
241 $GLOBALS['log']->fatal("IP Address mismatch: SESSION IP: {$_SESSION[$session_var]} CLIENT IP: {$clientIP}");
253 array('session'=>'xsd:string'),
254 array('return'=>'xsd:int'),
258 * Perform a seamless login. This is used internally during the sync process.
260 * @param String $session -- Session ID returned by a previous call to login.
261 * @return true -- if the session was authenticated
262 * @return false -- if the session could not be authenticated
264 function seamless_login($session){
265 if(!validate_authenticated($session)){
268 $_SESSION['seamless_login'] = true;
274 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'query'=>'xsd:string', 'order_by'=>'xsd:string','offset'=>'xsd:int', 'select_fields'=>'tns:select_fields', 'max_results'=>'xsd:int', 'deleted'=>'xsd:int'),
275 array('return'=>'tns:get_entry_list_result'),
279 * Retrieve a list of beans. This is the primary method for getting list of SugarBeans from Sugar using the SOAP API.
281 * @param String $session -- Session ID returned by a previous call to login.
282 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
283 * @param String $query -- SQL where clause without the word 'where'
284 * @param String $order_by -- SQL order by clause without the phrase 'order by'
285 * @param String $offset -- The record offset to start from.
286 * @param Array $select_fields -- A list of the fields to be included in the results. This optional parameter allows for only needed fields to be retrieved.
287 * @param String $max_results -- The maximum number of records to return. The default is the sugar configuration value for 'list_max_entries_per_page'
288 * @param Number $deleted -- false if deleted records should not be include, true if deleted records should be included.
289 * @return Array 'result_count' -- The number of records returned
290 * 'next_offset' -- The start of the next page (This will always be the previous offset plus the number of rows returned. It does not indicate if there is additional data unless you calculate that the next_offset happens to be closer than it should be.
291 * 'field_list' -- The vardef information on the selected fields.
292 * Array -- 'field'=> 'name' -- the name of the field
293 * 'type' -- the data type of the field
294 * 'label' -- the translation key for the label of the field
295 * 'required' -- Is the field required?
296 * 'options' -- Possible values for a drop down field
297 * 'entry_list' -- The records that were retrieved
298 * 'error' -- The SOAP error, if any
300 function get_entry_list($session, $module_name, $query, $order_by,$offset, $select_fields, $max_results, $deleted ){
301 global $beanList, $beanFiles;
302 $error = new SoapError();
303 if(!validate_authenticated($session)){
304 $error->set_error('invalid_login');
305 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
308 if($module_name == 'CampaignProspects'){
309 $module_name = 'Prospects';
312 if(empty($beanList[$module_name])){
313 $error->set_error('no_module');
314 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
316 global $current_user;
317 if(!check_modules_access($current_user, $module_name, 'read')){
318 $error->set_error('no_access');
319 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
322 // If the maximum number of entries per page was specified, override the configuration value.
323 if($max_results > 0){
324 global $sugar_config;
325 $sugar_config['list_max_entries_per_page'] = $max_results;
329 $class_name = $beanList[$module_name];
330 require_once($beanFiles[$class_name]);
331 $seed = new $class_name();
332 if(! ($seed->ACLAccess('Export') && $seed->ACLAccess('list')))
334 $error->set_error('no_access');
335 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
338 require_once 'include/SugarSQLValidate.php';
339 $valid = new SugarSQLValidate();
340 if(!$valid->validateQueryClauses($query, $order_by)) {
341 $GLOBALS['log']->error("Bad query: $query $order_by");
342 $error->set_error('no_access');
344 'result_count' => -1,
345 'error' => $error->get_soap_array()
351 if($offset == '' || $offset == -1){
355 $response = $seed->retrieveTargetList($query, $select_fields, $offset,-1,-1,$deleted);
357 $response = $seed->get_list($order_by, $query, $offset,-1,-1,$deleted,true);
359 $list = $response['list'];
362 $output_list = array();
364 $isEmailModule = false;
365 if($module_name == 'Emails'){
366 $isEmailModule = true;
368 // retrieve the vardef information on the bean's fields.
369 $field_list = array();
370 foreach($list as $value)
372 if(isset($value->emailAddress)){
373 $value->emailAddress->handleLegacyRetrieve($value);
376 $value->retrieveEmailText();
378 $value->fill_in_additional_detail_fields();
379 $output_list[] = get_return_value($value, $module_name);
380 if(empty($field_list)){
381 $field_list = get_field_list($value);
385 // Filter the search results to only include the requested fields.
386 $output_list = filter_return_list($output_list, $select_fields, $module_name);
388 // Filter the list of fields to only include information on the requested fields.
389 $field_list = filter_return_list($field_list,$select_fields, $module_name);
391 // Calculate the offset for the start of the next page
392 $next_offset = $offset + sizeof($output_list);
394 return array('result_count'=>sizeof($output_list), 'next_offset'=>$next_offset,'field_list'=>$field_list, 'entry_list'=>$output_list, 'error'=>$error->get_soap_array());
399 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'id'=>'xsd:string', 'select_fields'=>'tns:select_fields'),
400 array('return'=>'tns:get_entry_result'),
404 * Retrieve a single SugarBean based on ID.
406 * @param String $session -- Session ID returned by a previous call to login.
407 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
408 * @param String $id -- The SugarBean's ID value.
409 * @param Array $select_fields -- A list of the fields to be included in the results. This optional parameter allows for only needed fields to be retrieved.
412 function get_entry($session, $module_name, $id,$select_fields ){
413 return get_entries($session, $module_name, array($id), $select_fields);
418 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'ids'=>'tns:select_fields', 'select_fields'=>'tns:select_fields'),
419 array('return'=>'tns:get_entry_result'),
423 * Retrieve a list of SugarBean's based on provided IDs.
425 * @param String $session -- Session ID returned by a previous call to login.
426 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
427 * @param Array $ids -- An array of SugarBean IDs.
428 * @param Array $select_fields -- A list of the fields to be included in the results. This optional parameter allows for only needed fields to be retrieved.
429 * @return Array 'field_list' -- Var def information about the returned fields
430 * 'entry_list' -- The records that were retrieved
431 * 'error' -- The SOAP error, if any
433 function get_entries($session, $module_name, $ids,$select_fields ){
434 global $beanList, $beanFiles;
435 $error = new SoapError();
436 $field_list = array();
437 $output_list = array();
438 if(!validate_authenticated($session)){
439 $error->set_error('invalid_login');
440 return array('field_list'=>$field_list, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
443 if($module_name == 'CampaignProspects'){
444 $module_name = 'Prospects';
447 if(empty($beanList[$module_name])){
448 $error->set_error('no_module');
449 return array('field_list'=>$field_list, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
451 global $current_user;
452 if(!check_modules_access($current_user, $module_name, 'read')){
453 $error->set_error('no_access');
454 return array('field_list'=>$field_list, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
457 $class_name = $beanList[$module_name];
458 require_once($beanFiles[$class_name]);
460 //todo can modify in there to call bean->get_list($order_by, $where, 0, -1, -1, $deleted);
461 //that way we do not have to call retrieve for each bean
462 //perhaps also add a select_fields to this, so we only get the fields we need
463 //and not do a select *
464 foreach($ids as $id){
465 $seed = new $class_name();
468 $seed = $seed->retrieveTarget($id);
470 if ($seed->retrieve($id) == null)
474 if ($seed->deleted == 1) {
476 $list[] = array('name'=>'warning', 'value'=>'Access to this object is denied since it has been deleted or does not exist');
477 $list[] = array('name'=>'deleted', 'value'=>'1');
478 $output_list[] = Array('id'=>$id,
479 'module_name'=> $module_name,
480 'name_value_list'=>$list,
484 if(! $seed->ACLAccess('DetailView')){
485 $error->set_error('no_access');
486 return array('field_list'=>$field_list, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
488 $output_list[] = get_return_value($seed, $module_name);
490 if(empty($field_list)){
491 $field_list = get_field_list($seed);
496 $output_list = filter_return_list($output_list, $select_fields, $module_name);
497 $field_list = filter_field_list($field_list,$select_fields, $module_name);
499 return array( 'field_list'=>$field_list, 'entry_list'=>$output_list, 'error'=>$error->get_soap_array());
504 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'name_value_list'=>'tns:name_value_list'),
505 array('return'=>'tns:set_entry_result'),
509 * Update or create a single SugarBean.
511 * @param String $session -- Session ID returned by a previous call to login.
512 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
513 * @param Array $name_value_list -- The keys of the array are the SugarBean attributes, the values of the array are the values the attributes should have.
514 * @return Array 'id' -- the ID of the bean that was written to (-1 on error)
515 * 'error' -- The SOAP error if any.
517 function set_entry($session,$module_name, $name_value_list){
518 global $beanList, $beanFiles;
520 $error = new SoapError();
521 if(!validate_authenticated($session)){
522 $error->set_error('invalid_login');
523 return array('id'=>-1, 'error'=>$error->get_soap_array());
525 if(empty($beanList[$module_name])){
526 $error->set_error('no_module');
527 return array('id'=>-1, 'error'=>$error->get_soap_array());
529 global $current_user;
530 if(!check_modules_access($current_user, $module_name, 'write')){
531 $error->set_error('no_access');
532 return array('id'=>-1, 'error'=>$error->get_soap_array());
535 $class_name = $beanList[$module_name];
536 require_once($beanFiles[$class_name]);
537 $seed = new $class_name();
539 foreach($name_value_list as $value){
540 if($value['name'] == 'id' && isset($value['value']) && strlen($value['value']) > 0){
541 $result = $seed->retrieve($value['value']);
542 //bug: 44680 - check to ensure the user has access before proceeding.
545 $error->set_error('no_access');
546 return array('id'=>-1, 'error'=>$error->get_soap_array());
555 foreach($name_value_list as $value){
556 $GLOBALS['log']->debug($value['name']." : ".$value['value']);
557 $seed->$value['name'] = $value['value'];
559 if(! $seed->ACLAccess('Save') || ($seed->deleted == 1 && !$seed->ACLAccess('Delete')))
561 $error->set_error('no_access');
562 return array('id'=>-1, 'error'=>$error->get_soap_array());
565 if($seed->deleted == 1){
566 $seed->mark_deleted($seed->id);
568 return array('id'=>$seed->id, 'error'=>$error->get_soap_array());
574 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'name_value_lists'=>'tns:name_value_lists'),
575 array('return'=>'tns:set_entries_result'),
579 * Update or create a list of SugarBeans
581 * @param String $session -- Session ID returned by a previous call to login.
582 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
583 * @param Array $name_value_lists -- Array of Bean specific Arrays where the keys of the array are the SugarBean attributes, the values of the array are the values the attributes should have.
584 * @return Array 'ids' -- Array of the IDs of the beans that was written to (-1 on error)
585 * 'error' -- The SOAP error if any.
587 function set_entries($session,$module_name, $name_value_lists){
588 $error = new SoapError();
590 if(!validate_authenticated($session)){
591 $error->set_error('invalid_login');
595 'error' => $error->get_soap_array()
599 return handle_set_entries($module_name, $name_value_lists, FALSE);
606 'set_note_attachment',
607 array('session'=>'xsd:string','note'=>'tns:note_attachment'),
608 array('return'=>'tns:set_entry_result'),
612 * Add or replace the attachment on a Note.
614 * @param String $session -- Session ID returned by a previous call to login.
615 * @param Binary $note -- The flie contents of the attachment.
616 * @return Array 'id' -- The ID of the new note or -1 on error
617 * 'error' -- The SOAP error if any.
619 function set_note_attachment($session,$note)
622 $error = new SoapError();
623 if(!validate_authenticated($session)){
624 $error->set_error('invalid_login');
625 return array('id'=>-1, 'error'=>$error->get_soap_array());
628 require_once('modules/Notes/NoteSoap.php');
629 $ns = new NoteSoap();
630 return array('id'=>$ns->saveFile($note), 'error'=>$error->get_soap_array());
635 'get_note_attachment',
636 array('session'=>'xsd:string', 'id'=>'xsd:string'),
637 array('return'=>'tns:return_note_attachment'),
641 * Retrieve an attachment from a note
642 * @param String $session -- Session ID returned by a previous call to login.
643 * @param Binary $note -- The flie contents of the attachment.
644 * @return Array 'id' -- The ID of the new note or -1 on error
645 * 'error' -- The SOAP error if any.
647 * @param String $session -- Session ID returned by a previous call to login.
648 * @param String $id -- The ID of the appropriate Note.
649 * @return Array 'note_attachment' -- Array String 'id' -- The ID of the Note containing the attachment
650 * String 'filename' -- The file name of the attachment
651 * Binary 'file' -- The binary contents of the file.
652 * 'error' -- The SOAP error if any.
654 function get_note_attachment($session,$id)
656 $error = new SoapError();
657 if(!validate_authenticated($session)){
658 $error->set_error('invalid_login');
659 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
664 $note->retrieve($id);
665 if(!$note->ACLAccess('DetailView')){
666 $error->set_error('no_access');
667 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
669 require_once('modules/Notes/NoteSoap.php');
670 $ns = new NoteSoap();
671 if(!isset($note->filename)){
672 $note->filename = '';
674 $file= $ns->retrieveFile($id,$note->filename);
676 $error->set_error('no_file');
680 return array('note_attachment'=>array('id'=>$id, 'filename'=>$note->filename, 'file'=>$file), 'error'=>$error->get_soap_array());
684 'relate_note_to_module',
685 array('session'=>'xsd:string', 'note_id'=>'xsd:string', 'module_name'=>'xsd:string', 'module_id'=>'xsd:string'),
686 array('return'=>'tns:error_value'),
690 * Attach a note to another bean. Once you have created a note to store an
691 * attachment, the note needs to be related to the bean.
693 * @param String $session -- Session ID returned by a previous call to login.
694 * @param String $note_id -- The ID of the note that you want to associate with a bean
695 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
696 * @param String $module_id -- The ID of the bean that you want to associate the note with
697 * @return no error for success, error for failure
699 function relate_note_to_module($session,$note_id, $module_name, $module_id){
700 global $beanList, $beanFiles;
701 $error = new SoapError();
702 if(!validate_authenticated($session)){
703 $error->set_error('invalid_login');
704 return $error->get_soap_array();
706 if(empty($beanList[$module_name])){
707 $error->set_error('no_module');
708 return $error->get_soap_array();
710 global $current_user;
711 if(!check_modules_access($current_user, $module_name, 'read')){
712 $error->set_error('no_access');
713 return $error->get_soap_array();
715 $class_name = $beanList['Notes'];
716 require_once($beanFiles[$class_name]);
717 $seed = new $class_name();
718 $seed->retrieve($note_id);
719 if(!$seed->ACLAccess('ListView')){
720 $error->set_error('no_access');
721 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
724 if($module_name != 'Contacts'){
725 $seed->parent_type=$module_name;
726 $seed->parent_id = $module_id;
730 $seed->contact_id=$module_id;
736 return $error->get_soap_array();
741 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'module_id'=>'xsd:string', 'select_fields'=>'tns:select_fields'),
742 array('return'=>'tns:get_entry_result'),
746 * Retrieve the collection of notes that are related to a bean.
748 * @param String $session -- Session ID returned by a previous call to login.
749 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
750 * @param String $module_id -- The ID of the bean that you want to associate the note with
751 * @param Array $select_fields -- A list of the fields to be included in the results. This optional parameter allows for only needed fields to be retrieved.
752 * @return Array 'result_count' -- The number of records returned (-1 on error)
753 * 'next_offset' -- The start of the next page (This will always be the previous offset plus the number of rows returned. It does not indicate if there is additional data unless you calculate that the next_offset happens to be closer than it should be.
754 * 'field_list' -- The vardef information on the selected fields.
755 * 'entry_list' -- The records that were retrieved
756 * 'error' -- The SOAP error, if any
758 function get_related_notes($session,$module_name, $module_id, $select_fields){
759 global $beanList, $beanFiles;
760 $error = new SoapError();
761 if(!validate_authenticated($session)){
762 $error->set_error('invalid_login');
763 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
765 if(empty($beanList[$module_name])){
766 $error->set_error('no_module');
767 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
769 global $current_user;
770 if(!check_modules_access($current_user, $module_name, 'read')){
771 $error->set_error('no_access');
772 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
775 $class_name = $beanList[$module_name];
776 require_once($beanFiles[$class_name]);
777 $seed = new $class_name();
778 $seed->retrieve($module_id);
779 if(!$seed->ACLAccess('DetailView')){
780 $error->set_error('no_access');
781 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
783 $list = $seed->get_linked_beans('notes','Note', array(), 0, -1, 0);
785 $output_list = Array();
786 $field_list = Array();
787 foreach($list as $value)
789 $output_list[] = get_return_value($value, 'Notes');
790 if(empty($field_list))
792 $field_list = get_field_list($value);
795 $output_list = filter_return_list($output_list, $select_fields, $module_name);
796 $field_list = filter_field_list($field_list,$select_fields, $module_name);
798 return array('result_count'=>sizeof($output_list), 'next_offset'=>0,'field_list'=>$field_list, 'entry_list'=>$output_list, 'error'=>$error->get_soap_array());
803 array('session'=>'xsd:string'),
804 array('return'=>'tns:error_value'),
808 * Log out of the session. This will destroy the session and prevent other's from using it.
810 * @param String $session -- Session ID returned by a previous call to login.
811 * @return Empty error on success, Error on failure
813 function logout($session){
814 global $current_user;
816 $error = new SoapError();
817 LogicHook::initialize();
818 if(validate_authenticated($session)){
819 $current_user->call_custom_logic('before_logout');
821 $GLOBALS['logic_hook']->call_custom_logic('Users', 'after_logout');
822 return $error->get_soap_array();
824 $error->set_error('no_session');
825 $GLOBALS['logic_hook']->call_custom_logic('Users', 'after_logout');
826 return $error->get_soap_array();
831 array('session'=>'xsd:string', 'module_name'=>'xsd:string'),
832 array('return'=>'tns:module_fields'),
836 * Retrieve vardef information on the fields of the specified bean.
838 * @param String $session -- Session ID returned by a previous call to login.
839 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
840 * @return Array 'module_fields' -- The vardef information on the selected fields.
841 * 'error' -- The SOAP error, if any
843 function get_module_fields($session, $module_name){
844 global $beanList, $beanFiles;
845 $error = new SoapError();
846 $module_fields = array();
847 if(! validate_authenticated($session)){
848 $error->set_error('invalid_session');
849 return array('module_fields'=>$module_fields, 'error'=>$error->get_soap_array());
851 if(empty($beanList[$module_name])){
852 $error->set_error('no_module');
853 return array('module_fields'=>$module_fields, 'error'=>$error->get_soap_array());
855 global $current_user;
856 if(!check_modules_access($current_user, $module_name, 'read')){
857 $error->set_error('no_access');
858 return array('module_fields'=>$module_fields, 'error'=>$error->get_soap_array());
860 $class_name = $beanList[$module_name];
862 if(empty($beanFiles[$class_name]))
864 $error->set_error('no_file');
865 return array('module_fields'=>$module_fields, 'error'=>$error->get_soap_array());
868 require_once($beanFiles[$class_name]);
869 $seed = new $class_name();
870 if($seed->ACLAccess('ListView', true) || $seed->ACLAccess('DetailView', true) || $seed->ACLAccess('EditView', true) )
872 return get_return_module_fields($seed, $module_name, $error);
876 $error->set_error('no_access');
877 return array('module_fields'=>$module_fields, 'error'=>$error->get_soap_array());
882 'get_available_modules',
883 array('session'=>'xsd:string'),
884 array('return'=>'tns:module_list'),
888 * Retrieve the list of available modules on the system available to the currently logged in user.
890 * @param String $session -- Session ID returned by a previous call to login.
891 * @return Array 'modules' -- An array of module names
892 * 'error' -- The SOAP error, if any
894 function get_available_modules($session){
895 $error = new SoapError();
897 if(! validate_authenticated($session)){
898 $error->set_error('invalid_session');
899 return array('modules'=> $modules, 'error'=>$error->get_soap_array());
901 $modules = array_keys($_SESSION['avail_modules']);
903 return array('modules'=> $modules, 'error'=>$error->get_soap_array());
908 'update_portal_user',
909 array('session'=>'xsd:string', 'portal_name'=>'xsd:string', 'name_value_list'=>'tns:name_value_list'),
910 array('return'=>'tns:error_value'),
914 * Update the properties of a contact that is portal user. Add the portal user name to the user's properties.
916 * @param String $session -- Session ID returned by a previous call to login.
917 * @param String $portal_name -- The portal user_name of the contact
918 * @param Array $name_value_list -- collection of 'name'=>'value' pairs for finding the contact
919 * @return Empty error on success, Error on failure
921 function update_portal_user($session,$portal_name, $name_value_list){
922 global $beanList, $beanFiles;
923 $error = new SoapError();
924 if(! validate_authenticated($session)){
925 $error->set_error('invalid_session');
926 return $error->get_soap_array();
928 $contact = new Contact();
930 $searchBy = array('deleted'=>0);
931 foreach($name_value_list as $name_value){
932 $searchBy[$name_value['name']] = $name_value['value'];
934 if($contact->retrieve_by_string_fields($searchBy) != null){
935 if(!$contact->duplicates_found){
936 $contact->portal_name = $portal_name;
937 $contact->portal_active = 1;
938 if($contact->ACLAccess('Save')){
941 $error->set_error('no_access');
943 return $error->get_soap_array();
945 $error->set_error('duplicates');
946 return $error->get_soap_array();
948 $error->set_error('no_records');
949 return $error->get_soap_array();
954 array('session'=>'xsd:string'),
955 array('return'=>'xsd:string'),
959 * Return the user_id of the user that is logged into the current session.
961 * @param String $session -- Session ID returned by a previous call to login.
962 * @return String -- the User ID of the current session
965 function get_user_id($session){
966 if(validate_authenticated($session)){
967 global $current_user;
968 return $current_user->id;
977 array('session'=>'xsd:string'),
978 array('return'=>'xsd:string'),
982 * Return the ID of the default team for the user that is logged into the current session.
984 * @param String $session -- Session ID returned by a previous call to login.
985 * @return String -- the Team ID of the current user's default team
986 * 1 for Community Edition
989 function get_user_team_id($session){
990 if(validate_authenticated($session))
1001 array('return'=>'xsd:string'),
1005 * Return the current time on the server in the format 'Y-m-d H:i:s'. This time is in the server's default timezone.
1007 * @return String -- The current date/time 'Y-m-d H:i:s'
1009 function get_server_time(){
1010 return date('Y-m-d H:i:s');
1016 array('return'=>'xsd:string'),
1020 * Return the current time on the server in the format 'Y-m-d H:i:s'. This time is in GMT.
1022 * @return String -- The current date/time 'Y-m-d H:i:s'
1024 function get_gmt_time(){
1025 return TimeDate::getInstance()->nowDb();
1031 array('return'=>'xsd:string'),
1035 * Retrieve the specific flavor of sugar.
1037 * @return String 'CE' -- For Community Edition
1038 * 'PRO' -- For Professional
1039 * 'ENT' -- For Enterprise
1041 function get_sugar_flavor(){
1042 global $sugar_flavor;
1044 return $sugar_flavor;
1049 'get_server_version',
1051 array('return'=>'xsd:string'),
1055 * Retrieve the version number of Sugar that the server is running.
1057 * @return String -- The current sugar version number.
1060 function get_server_version(){
1062 $admin = new Administration();
1063 $admin->retrieveSettings('info');
1064 if(isset($admin->settings['info_sugar_version'])){
1065 return $admin->settings['info_sugar_version'];
1073 'get_relationships',
1074 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'module_id'=>'xsd:string', 'related_module'=>'xsd:string', 'related_module_query'=>'xsd:string', 'deleted'=>'xsd:int'),
1075 array('return'=>'tns:get_relationships_result'),
1079 * Retrieve a collection of beans tha are related to the specified bean.
1080 * As of 4.5.1c, all combinations of related modules are supported
1082 * @param String $session -- Session ID returned by a previous call to login.
1083 * @param String $module_name -- The name of the module that the primary record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1084 * @param String $module_id -- The ID of the bean in the specified module
1085 * @param String $related_module -- The name of the related module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1086 * @param String $related_module_query -- A portion of the where clause of the SQL statement to find the related items. The SQL query will already be filtered to only include the beans that are related to the specified bean.
1087 * @param Number $deleted -- false if deleted records should not be include, true if deleted records should be included.
1090 function get_relationships($session, $module_name, $module_id, $related_module, $related_module_query, $deleted){
1091 $error = new SoapError();
1093 if(!validate_authenticated($session)){
1094 $error->set_error('invalid_login');
1095 return array('ids'=>$ids,'error'=> $error->get_soap_array());
1097 global $beanList, $beanFiles;
1098 $error = new SoapError();
1100 if(empty($beanList[$module_name]) || empty($beanList[$related_module])){
1101 $error->set_error('no_module');
1102 return array('ids'=>$ids, 'error'=>$error->get_soap_array());
1104 $class_name = $beanList[$module_name];
1105 require_once($beanFiles[$class_name]);
1106 $mod = new $class_name();
1107 $mod->retrieve($module_id);
1108 if(!$mod->ACLAccess('DetailView')){
1109 $error->set_error('no_access');
1110 return array('ids'=>$ids, 'error'=>$error->get_soap_array());
1113 require_once 'include/SugarSQLValidate.php';
1114 $valid = new SugarSQLValidate();
1115 if(!$valid->validateQueryClauses($related_module_query)) {
1116 $GLOBALS['log']->error("Bad query: $related_module_query");
1117 $error->set_error('no_access');
1119 'result_count' => -1,
1120 'error' => $error->get_soap_array()
1124 $id_list = get_linked_records($related_module, $module_name, $module_id);
1126 if ($id_list === FALSE) {
1127 $error->set_error('no_relationship_support');
1128 return array('ids'=>$ids, 'error'=>$error->get_soap_array());
1130 elseif (count($id_list) == 0) {
1131 return array('ids'=>$ids, 'error'=>$error->get_soap_array());
1136 $in = "'".implode("', '", $id_list)."'";
1138 $related_class_name = $beanList[$related_module];
1139 require_once($beanFiles[$related_class_name]);
1140 $related_mod = new $related_class_name();
1142 $sql = "SELECT {$related_mod->table_name}.id FROM {$related_mod->table_name} ";
1145 $sql .= " WHERE {$related_mod->table_name}.id IN ({$in}) ";
1147 if (!empty($related_module_query)) {
1148 $sql .= " AND ( {$related_module_query} )";
1151 $result = $related_mod->db->query($sql);
1152 while ($row = $related_mod->db->fetchByAssoc($result)) {
1153 $list[] = $row['id'];
1156 $return_list = array();
1158 foreach($list as $id) {
1159 $related_class_name = $beanList[$related_module];
1160 $related_mod = new $related_class_name();
1161 $related_mod->retrieve($id);
1163 $return_list[] = array(
1165 'date_modified' => $related_mod->date_modified,
1166 'deleted' => $related_mod->deleted
1170 return array('ids' => $return_list, 'error' => $error->get_soap_array());
1176 array('session'=>'xsd:string','set_relationship_value'=>'tns:set_relationship_value'),
1177 array('return'=>'tns:error_value'),
1181 * Set a single relationship between two beans. The items are related by module name and id.
1183 * @param String $session -- Session ID returned by a previous call to login.
1184 * @param Array $set_relationship_value --
1185 * 'module1' -- The name of the module that the primary record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1186 * 'module1_id' -- The ID of the bean in the specified module
1187 * 'module2' -- The name of the module that the related record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1188 * 'module2_id' -- The ID of the bean in the specified module
1189 * @return Empty error on success, Error on failure
1191 function set_relationship($session, $set_relationship_value){
1192 $error = new SoapError();
1193 if(!validate_authenticated($session)){
1194 $error->set_error('invalid_login');
1195 return $error->get_soap_array();
1197 return handle_set_relationship($set_relationship_value);
1201 'set_relationships',
1202 array('session'=>'xsd:string','set_relationship_list'=>'tns:set_relationship_list'),
1203 array('return'=>'tns:set_relationship_list_result'),
1207 * Setup several relationships between pairs of beans. The items are related by module name and id.
1209 * @param String $session -- Session ID returned by a previous call to login.
1210 * @param Array $set_relationship_list -- One for each relationship to setup. Each entry is itself an array.
1211 * 'module1' -- The name of the module that the primary record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1212 * 'module1_id' -- The ID of the bean in the specified module
1213 * 'module2' -- The name of the module that the related record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1214 * 'module2_id' -- The ID of the bean in the specified module
1215 * @return Empty error on success, Error on failure
1217 function set_relationships($session, $set_relationship_list){
1218 $error = new SoapError();
1219 if(!validate_authenticated($session)){
1220 $error->set_error('invalid_login');
1225 foreach($set_relationship_list as $set_relationship_value){
1226 $reter = handle_set_relationship($set_relationship_value);
1227 if($reter['number'] == 0){
1233 return array('created'=>$count , 'failed'=>$failed, 'error'=>$error);
1238 //INTERNAL FUNCTION NOT EXPOSED THROUGH SOAP
1240 * (Internal) Create a relationship between two beans.
1242 * @param Array $set_relationship_value --
1243 * 'module1' -- The name of the module that the primary record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1244 * 'module1_id' -- The ID of the bean in the specified module
1245 * 'module2' -- The name of the module that the related record is from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1246 * 'module2_id' -- The ID of the bean in the specified module
1247 * @return Empty error on success, Error on failure
1249 function handle_set_relationship($set_relationship_value)
1251 global $beanList, $beanFiles;
1252 $error = new SoapError();
1254 $module1 = $set_relationship_value['module1'];
1255 $module1_id = $set_relationship_value['module1_id'];
1256 $module2 = $set_relationship_value['module2'];
1257 $module2_id = $set_relationship_value['module2_id'];
1259 if(empty($beanList[$module1]) || empty($beanList[$module2]) )
1261 $error->set_error('no_module');
1262 return $error->get_soap_array();
1264 $class_name = $beanList[$module1];
1265 require_once($beanFiles[$class_name]);
1266 $mod = new $class_name();
1267 $mod->retrieve($module1_id);
1268 if(!$mod->ACLAccess('DetailView')){
1269 $error->set_error('no_access');
1270 return $error->get_soap_array();
1272 if($module1 == "Contacts" && $module2 == "Users"){
1273 $key = 'contacts_users_id';
1276 $key = array_search(strtolower($module2),$mod->relationship_fields);
1278 $key = Relationship::retrieve_by_modules($module1, $module2, $GLOBALS['db']);
1280 // BEGIN SnapLogic fix for bug 32064
1281 if ($module1 == "Quotes" && $module2 == "ProductBundles") {
1282 // Alternative solution is perhaps to
1283 // do whatever Sugar does when the same
1284 // request is received from the web:
1285 $pb_cls = $beanList[$module2];
1286 $pb = new $pb_cls();
1287 $pb->retrieve($module2_id);
1289 // Check if this relationship already exists
1290 $query = "SELECT count(*) AS count FROM product_bundle_quote WHERE quote_id = '{$module1_id}' AND bundle_id = '{$module2_id}' AND deleted = '0'";
1291 $result = $GLOBALS['db']->query($query, true, "Error checking for previously existing relationship between quote and product_bundle");
1292 $row = $GLOBALS['db']->fetchByAssoc($result);
1293 if(isset($row['count']) && $row['count'] > 0){
1294 return $error->get_soap_array();
1297 $query = "SELECT MAX(bundle_index)+1 AS idx FROM product_bundle_quote WHERE quote_id = '{$module1_id}' AND deleted='0'";
1298 $result = $GLOBALS['db']->query($query, true, "Error getting bundle_index");
1299 $GLOBALS['log']->debug("*********** Getting max bundle_index");
1300 $GLOBALS['log']->debug($query);
1301 $row = $GLOBALS['db']->fetchByAssoc($result);
1308 $pb->set_productbundle_quote_relationship($module1_id,$module2_id,$idx);
1310 return $error->get_soap_array();
1312 } else if ($module1 == "ProductBundles" && $module2 == "Products") {
1313 // And, well, similar things apply in this case
1314 $pb_cls = $beanList[$module1];
1315 $pb = new $pb_cls();
1316 $pb->retrieve($module1_id);
1318 // Check if this relationship already exists
1319 $query = "SELECT count(*) AS count FROM product_bundle_product WHERE bundle_id = '{$module1_id}' AND product_id = '{$module2_id}' AND deleted = '0'";
1320 $result = $GLOBALS['db']->query($query, true, "Error checking for previously existing relationship between quote and product_bundle");
1321 $row = $GLOBALS['db']->fetchByAssoc($result);
1322 if(isset($row['count']) && $row['count'] > 0){
1323 return $error->get_soap_array();
1326 $query = "SELECT MAX(product_index)+1 AS idx FROM product_bundle_product WHERE bundle_id='{$module1_id}'";
1327 $result = $GLOBALS['db']->query($query, true, "Error getting bundle_index");
1328 $GLOBALS['log']->debug("*********** Getting max bundle_index");
1329 $GLOBALS['log']->debug($query);
1330 $row = $GLOBALS['db']->fetchByAssoc($result);
1336 $pb->set_productbundle_product_relationship($module2_id,$idx,$module1_id);
1339 $prod_cls = $beanList[$module2];
1340 $prod = new $prod_cls();
1341 $prod->retrieve($module2_id);
1342 $prod->quote_id = $pb->quote_id;
1344 return $error->get_soap_array();
1346 // END SnapLogic fix for bug 32064
1349 $mod->load_relationship($key);
1350 $mod->$key->add($module2_id);
1351 return $error->get_soap_array();
1358 $error->set_error('no_module');
1359 return $error->get_soap_array();
1362 if(($module1 == 'Meetings' || $module1 == 'Calls') && ($module2 == 'Contacts' || $module2 == 'Users')){
1363 $key = strtolower($module2);
1364 $mod->load_relationship($key);
1365 $mod->$key->add($module2_id);
1367 $mod->$key = $module2_id;
1368 $mod->save_relationship_changes(false);
1371 return $error->get_soap_array();
1376 'set_document_revision',
1377 array('session'=>'xsd:string','note'=>'tns:document_revision'),
1378 array('return'=>'tns:set_entry_result'),
1382 * Enter description here...
1384 * @param String $session -- Session ID returned by a previous call to login.
1385 * @param unknown_type $document_revision
1388 function set_document_revision($session,$document_revision)
1391 $error = new SoapError();
1392 if(!validate_authenticated($session)){
1393 $error->set_error('invalid_login');
1394 return array('id'=>-1, 'error'=>$error->get_soap_array());
1397 require_once('modules/Documents/DocumentSoap.php');
1398 $dr = new DocumentSoap();
1399 return array('id'=>$dr->saveFile($document_revision), 'error'=>$error->get_soap_array());
1405 array('user_name'=>'xsd:string','password'=>'xsd:string','search_string'=>'xsd:string', 'modules'=>'tns:select_fields', 'offset'=>'xsd:int', 'max_results'=>'xsd:int'),
1406 array('return'=>'tns:get_entry_list_result'),
1410 * Given a list of modules to search and a search string, return the id, module_name, along with the fields
1411 * as specified in the $query_array
1413 * @param string $user_name - username of the Sugar User
1414 * @param string $password - password of the Sugar User
1415 * @param string $search_string - string to search
1416 * @param string[] $modules - array of modules to query
1417 * @param int $offset - a specified offset in the query
1418 * @param int $max_results - max number of records to return
1419 * @return get_entry_list_result - id, module_name, and list of fields from each record
1421 function search_by_module($user_name, $password, $search_string, $modules, $offset, $max_results){
1422 global $beanList, $beanFiles;
1424 $error = new SoapError();
1425 $hasLoginError = false;
1427 if(empty($user_name) && !empty($password))
1429 if(!validate_authenticated($password))
1431 $hasLoginError = true;
1433 } else if(!validate_user($user_name, $password)) {
1434 $hasLoginError = true;
1437 //If there is a login error, then return the error here
1440 $error->set_error('invalid_login');
1441 return array('result_count'=>-1, 'entry_list'=>array(), 'error'=>$error->get_soap_array());
1444 global $current_user;
1445 if($max_results > 0){
1446 global $sugar_config;
1447 $sugar_config['list_max_entries_per_page'] = $max_results;
1449 // MRF - BUG:19552 - added a join for accounts' emails below
1450 $query_array = array('Accounts'=>array('where'=>array('Accounts' => array(0 => "accounts.name like '{0}%'"), 'EmailAddresses' => array(0 => "ea.email_address like '{0}%'")),'fields'=>"accounts.id, accounts.name"),
1451 'Bugs'=>array('where'=>array('Bugs' => array(0 => "bugs.name like '{0}%'", 1 => "bugs.bug_number = {0}")),'fields'=>"bugs.id, bugs.name, bugs.bug_number"),
1452 'Cases'=>array('where'=>array('Cases' => array(0 => "cases.name like '{0}%'", 1 => "cases.case_number = {0}")),'fields'=>"cases.id, cases.name, cases.case_number"),
1453 'Leads'=>array('where'=>array('Leads' => array(0 => "leads.first_name like '{0}%'",1 => "leads.last_name like '{0}%'"), 'EmailAddresses' => array(0 => "ea.email_address like '{0}%'")), 'fields'=>"leads.id, leads.first_name, leads.last_name, leads.status"),
1454 'Project'=>array('where'=>array('Project' => array(0 => "project.name like '{0}%'")), 'fields'=>"project.id, project.name"),
1455 'ProjectTask'=>array('where'=>array('ProjectTask' => array(0 => "project.id = '{0}'")), 'fields'=>"project_task.id, project_task.name"),
1456 'Contacts'=>array('where'=>array('Contacts' => array(0 => "contacts.first_name like '{0}%'", 1 => "contacts.last_name like '{0}%'"), 'EmailAddresses' => array(0 => "ea.email_address like '{0}%'")),'fields'=>"contacts.id, contacts.first_name, contacts.last_name"),
1457 'Opportunities'=>array('where'=>array('Opportunities' => array(0 => "opportunities.name like '{0}%'")), 'fields'=>"opportunities.id, opportunities.name"),
1458 'Users'=>array('where'=>array('EmailAddresses' => array(0 => "ea.email_address like '{0}%'")),'fields'=>"users.id, users.user_name, users.first_name, ea.email_address"),
1461 if(!empty($search_string) && isset($search_string)){
1462 foreach($modules as $module_name){
1463 $class_name = $beanList[$module_name];
1464 require_once($beanFiles[$class_name]);
1465 $seed = new $class_name();
1466 if(empty($beanList[$module_name])){
1469 if(!check_modules_access($current_user, $module_name, 'read')){
1472 if(! $seed->ACLAccess('ListView'))
1477 if(isset($query_array[$module_name])){
1480 //split here to do while loop
1481 foreach($query_array[$module_name]['where'] as $key => $value){
1482 foreach($value as $where_clause){
1485 $tmpQuery = ' UNION ';
1486 $tmpQuery .= "SELECT ".$query_array[$module_name]['fields']." FROM $seed->table_name ";
1487 // We need to confirm that the user is a member of the team of the item.
1490 if($module_name == 'ProjectTask'){
1491 $tmpQuery .= "INNER JOIN project ON $seed->table_name.project_id = project.id ";
1494 if(isset($seed->emailAddress) && $key == 'EmailAddresses'){
1495 $tmpQuery .= " INNER JOIN email_addr_bean_rel eabl ON eabl.bean_id = $seed->table_name.id and eabl.deleted=0";
1496 $tmpQuery .= " INNER JOIN email_addresses ea ON (ea.id = eabl.email_address_id) ";
1499 $search_terms = explode(", ", $search_string);
1500 $termCount = count($search_terms);
1502 if($key != 'EmailAddresses'){
1503 foreach($search_terms as $term){
1504 if(!strpos($where_clause, 'number')){
1505 $where .= string_format($where_clause,array($GLOBALS['db']->quote($term)));
1506 }elseif(is_numeric($term)){
1507 $where .= string_format($where_clause,array($GLOBALS['db']->quote($term)));
1511 if($count < $termCount){
1517 $where .= 'ea.email_address IN (';
1518 foreach($search_terms as $term){
1519 $where .= "'".$GLOBALS['db']->quote($term)."'";
1520 if($count < $termCount){
1527 $tmpQuery .= $where;
1528 $tmpQuery .= ") AND $seed->table_name.deleted = 0";
1530 $query .= $tmpQuery;
1533 //grab the items from the db
1534 $result = $seed->db->query($query, $offset, $max_results);
1536 while(($row = $seed->db->fetchByAssoc($result)) != null){
1538 $fields = explode(", ", $query_array[$module_name]['fields']);
1539 foreach($fields as $field){
1540 $field_names = explode(".", $field);
1541 $list[$field] = array('name'=>$field_names[1], 'value'=>$row[$field_names[1]]);
1544 $output_list[] = array('id'=>$row['id'],
1545 'module_name'=>$module_name,
1546 'name_value_list'=>$list);
1547 if(empty($field_list)){
1548 $field_list = get_field_list($row);
1555 $next_offset = $offset + sizeof($output_list);
1557 return array('result_count'=>sizeof($output_list), 'next_offset'=>$next_offset,'field_list'=>$field_list, 'entry_list'=>$output_list, 'error'=>$error->get_soap_array());
1563 'get_mailmerge_document',
1564 array('session'=>'xsd:string','file_name'=>'xsd:string', 'fields' => 'tns:select_fields'),
1565 array('return'=>'tns:get_sync_result_encoded'),
1569 * Get MailMerge document
1571 * @param String $session -- Session ID returned by a previous call to login.
1572 * @param unknown_type $file_name
1573 * @param unknown_type $fields
1576 function get_mailmerge_document($session, $file_name, $fields)
1578 global $beanList, $beanFiles, $app_list_strings;
1579 $error = new SoapError();
1580 if(!validate_authenticated($session))
1582 $error->set_error('invalid_login');
1583 return array('result'=>'', 'error'=>$error->get_soap_array());
1585 if(!preg_match('/^sugardata[\.\d\s]+\.php$/', $file_name)) {
1586 $error->set_error('no_records');
1587 return array('result'=>'', 'error'=>$error->get_soap_array());
1591 $file_name = sugar_cached('MergedDocuments/').pathinfo($file_name, PATHINFO_BASENAME);
1593 $master_fields = array();
1594 $related_fields = array();
1596 if(file_exists($file_name))
1598 include($file_name);
1600 $class1 = $merge_array['master_module'];
1601 $beanL = $beanList[$class1];
1602 $bean1 = $beanFiles[$beanL];
1603 require_once($bean1);
1604 $seed1 = new $beanL();
1606 if(!empty($merge_array['related_module']))
1608 $class2 = $merge_array['related_module'];
1609 $beanR = $beanList[$class2];
1610 $bean2 = $beanFiles[$beanR];
1611 require_once($bean2);
1612 $seed2 = new $beanR();
1616 //$token1 = strtolower($class1);
1617 if($class1 == 'Prospects'){
1618 $class1 = 'CampaignProspects';
1620 foreach($fields as $field)
1622 $pos = strpos(strtolower($field), strtolower($class1));
1623 $pos2 = strpos(strtolower($field), strtolower($class2));
1625 $fieldName = str_replace(strtolower($class1).'_', '', strtolower($field));
1626 array_push($master_fields, $fieldName);
1627 }else if($pos2 !== false){
1628 $fieldName = str_replace(strtolower($class2).'_', '', strtolower($field));
1629 array_push($related_fields, $fieldName);
1633 $html = '<html ' . get_language_header() .'><body><table border = 1><tr>';
1635 foreach($master_fields as $master_field){
1636 $html .= '<td>'.$class1.'_'.$master_field.'</td>';
1638 foreach($related_fields as $related_field){
1639 $html .= '<td>'.$class2.'_'.$related_field.'</td>';
1643 $ids = $merge_array['ids'];
1644 $is_prospect_merge = ($seed1->object_name == 'Prospect');
1645 foreach($ids as $key=>$value){
1646 if($is_prospect_merge){
1647 $seed1 = $seed1->retrieveTarget($key);
1649 $seed1->retrieve($key);
1652 foreach($master_fields as $master_field){
1653 if(isset($seed1->$master_field)){
1654 if($seed1->field_name_map[$master_field]['type'] == 'enum'){
1655 //pull in the translated dom
1656 $html .='<td>'.$app_list_strings[$seed1->field_name_map[$master_field]['options']][$seed1->$master_field].'</td>';
1658 $html .='<td>'.$seed1->$master_field.'</td>';
1662 $html .= '<td></td>';
1665 if(isset($value) && !empty($value)){
1666 $seed2->retrieve($value);
1667 foreach($related_fields as $related_field){
1668 if(isset($seed2->$related_field)){
1669 if($seed2->field_name_map[$related_field]['type'] == 'enum'){
1670 //pull in the translated dom
1671 $html .='<td>'.$app_list_strings[$seed2->field_name_map[$related_field]['options']][$seed2->$related_field].'</td>';
1673 $html .= '<td>'.$seed2->$related_field.'</td>';
1677 $html .= '<td></td>';
1683 $html .= "</table></body></html>";
1686 $result = base64_encode($html);
1687 return array('result' => $result, 'error' => $error);
1691 'get_mailmerge_document2',
1692 array('session'=>'xsd:string','file_name'=>'xsd:string', 'fields' => 'tns:select_fields'),
1693 array('return'=>'tns:get_mailmerge_document_result'),
1697 * Enter description here...
1699 * @param String $session -- Session ID returned by a previous call to login.
1700 * @param unknown_type $file_name
1701 * @param unknown_type $fields
1704 function get_mailmerge_document2($session, $file_name, $fields)
1706 global $beanList, $beanFiles, $app_list_strings, $app_strings;
1708 $error = new SoapError();
1709 if(!validate_authenticated($session))
1711 $GLOBALS['log']->error('invalid_login');
1712 $error->set_error('invalid_login');
1713 return array('result'=>'', 'error'=>$error->get_soap_array());
1715 if(!preg_match('/^sugardata[\.\d\s]+\.php$/', $file_name)) {
1716 $GLOBALS['log']->error($app_strings['ERR_NO_SUCH_FILE'] . " ({$file_name})");
1717 $error->set_error('no_records');
1718 return array('result'=>'', 'error'=>$error->get_soap_array());
1722 $file_name = sugar_cached('MergedDocuments/').pathinfo($file_name, PATHINFO_BASENAME);
1724 $master_fields = array();
1725 $related_fields = array();
1727 if(file_exists($file_name))
1729 include($file_name);
1731 $class1 = $merge_array['master_module'];
1732 $beanL = $beanList[$class1];
1733 $bean1 = $beanFiles[$beanL];
1734 require_once($bean1);
1735 $seed1 = new $beanL();
1737 if(!empty($merge_array['related_module']))
1739 $class2 = $merge_array['related_module'];
1740 $beanR = $beanList[$class2];
1741 $bean2 = $beanFiles[$beanR];
1742 require_once($bean2);
1743 $seed2 = new $beanR();
1747 //$token1 = strtolower($class1);
1748 if($class1 == 'Prospects'){
1749 $class1 = 'CampaignProspects';
1751 foreach($fields as $field)
1753 $pos = strpos(strtolower($field), strtolower($class1));
1754 $pos2 = strpos(strtolower($field), strtolower($class2));
1756 $fieldName = str_replace(strtolower($class1).'_', '', strtolower($field));
1757 array_push($master_fields, $fieldName);
1758 }else if($pos2 !== false){
1759 $fieldName = str_replace(strtolower($class2).'_', '', strtolower($field));
1760 array_push($related_fields, $fieldName);
1764 $html = '<html ' . get_language_header() . '><body><table border = 1><tr>';
1766 foreach($master_fields as $master_field){
1767 $html .= '<td>'.$class1.'_'.$master_field.'</td>';
1769 foreach($related_fields as $related_field){
1770 $html .= '<td>'.$class2.'_'.$related_field.'</td>';
1774 $ids = $merge_array['ids'];
1775 $resultIds = array();
1776 $is_prospect_merge = ($seed1->object_name == 'Prospect');
1777 if($is_prospect_merge){
1780 foreach($ids as $key=>$value){
1782 if($is_prospect_merge){
1783 $seed1 = $pSeed->retrieveTarget($key);
1785 $seed1->retrieve($key);
1787 $resultIds[] = array('name' => $seed1->module_name, 'value' => $key);
1789 foreach($master_fields as $master_field){
1790 if(isset($seed1->$master_field)){
1791 if($seed1->field_name_map[$master_field]['type'] == 'enum'){
1792 //pull in the translated dom
1793 $html .='<td>'.$app_list_strings[$seed1->field_name_map[$master_field]['options']][$seed1->$master_field].'</td>';
1795 $html .='<td>'.$seed1->$master_field.'</td>';
1799 $html .= '<td></td>';
1802 if(isset($value) && !empty($value)){
1803 $resultIds[] = array('name' => $seed2->module_name, 'value' => $value);
1804 $seed2->retrieve($value);
1805 foreach($related_fields as $related_field){
1806 if(isset($seed2->$related_field)){
1807 if($seed2->field_name_map[$related_field]['type'] == 'enum'){
1808 //pull in the translated dom
1809 $html .='<td>'.$app_list_strings[$seed2->field_name_map[$related_field]['options']][$seed2->$related_field].'</td>';
1811 $html .= '<td>'.$seed2->$related_field.'</td>';
1815 $html .= '<td></td>';
1821 $html .= "</table></body></html>";
1824 $result = base64_encode($html);
1825 return array('html' => $result, 'name_value_list' => $resultIds, 'error' => $error);
1829 'get_document_revision',
1830 array('session'=>'xsd:string','i'=>'xsd:string'),
1831 array('return'=>'tns:return_document_revision'),
1835 * This method is used as a result of the .htaccess lock down on the cache directory. It will allow a
1836 * properly authenticated user to download a document that they have proper rights to download.
1838 * @param String $session -- Session ID returned by a previous call to login.
1839 * @param String $id -- ID of the document revision to obtain
1840 * @return return_document_revision - this is a complex type as defined in SoapTypes.php
1842 function get_document_revision($session,$id)
1844 global $sugar_config;
1846 $error = new SoapError();
1847 if(!validate_authenticated($session)){
1848 $error->set_error('invalid_login');
1849 return array('id'=>-1, 'error'=>$error->get_soap_array());
1853 $dr = new DocumentRevision();
1855 if(!empty($dr->filename)){
1856 $filename = "upload://{$dr->id}";
1857 $contents = base64_encode(sugar_file_get_contents($filename));
1858 return array('document_revision'=>array('id' => $dr->id, 'document_name' => $dr->document_name, 'revision' => $dr->revision, 'filename' => $dr->filename, 'file' => $contents), 'error'=>$error->get_soap_array());
1860 $error->set_error('no_records');
1861 return array('id'=>-1, 'error'=>$error->get_soap_array());
1866 'set_campaign_merge',
1867 array('session'=>'xsd:string', 'targets'=>'tns:select_fields', 'campaign_id'=>'xsd:string'),
1868 array('return'=>'tns:error_value'),
1871 * Once we have successfuly done a mail merge on a campaign, we need to notify Sugar of the targets
1872 * and the campaign_id for tracking purposes
1874 * @param session the session id of the authenticated user
1875 * @param targets a string array of ids identifying the targets used in the merge
1876 * @param campaign_id the campaign_id used for the merge
1878 * @return error_value
1880 function set_campaign_merge($session,$targets, $campaign_id){
1881 $error = new SoapError();
1882 if(!validate_authenticated($session)){
1883 $error->set_error('invalid_login');
1884 return $error->get_soap_array();
1886 if (empty($campaign_id) or !is_array($targets) or count($targets) == 0) {
1887 $GLOBALS['log']->debug('set_campaign_merge: Merge action status will not be updated, because, campaign_id is null or no targets were selected.');
1889 require_once('modules/Campaigns/utils.php');
1890 campaign_log_mail_merge($campaign_id,$targets);
1893 return $error->get_soap_array();
1896 'get_entries_count',
1897 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'query'=>'xsd:string', 'deleted' => 'xsd:int'),
1898 array('return'=>'tns:get_entries_count_result'),
1902 * Retrieve number of records in a given module
1904 * @param session the session id of the authenticated user
1905 * @param module_name module to retrieve number of records from
1906 * @param query allows webservice user to provide a WHERE clause
1907 * @param deleted specify whether or not to include deleted records
1909 @return get_entries_count_result - this is a complex type as defined in SoapTypes.php
1911 function get_entries_count($session, $module_name, $query, $deleted) {
1912 global $beanList, $beanFiles, $current_user;
1914 $error = new SoapError();
1916 if (!validate_authenticated($session)) {
1917 $error->set_error('invalid_login');
1919 'result_count' => -1,
1920 'error' => $error->get_soap_array()
1924 if (empty($beanList[$module_name])) {
1925 $error->set_error('no_module');
1927 'result_count' => -1,
1928 'error' => $error->get_soap_array()
1932 if(!check_modules_access($current_user, $module_name, 'list')){
1933 $error->set_error('no_access');
1935 'result_count' => -1,
1936 'error' => $error->get_soap_array()
1940 $class_name = $beanList[$module_name];
1941 require_once($beanFiles[$class_name]);
1942 $seed = new $class_name();
1944 if (!$seed->ACLAccess('ListView')) {
1945 $error->set_error('no_access');
1947 'result_count' => -1,
1948 'error' => $error->get_soap_array()
1952 $sql = 'SELECT COUNT(*) result_count FROM ' . $seed->table_name . ' ';
1955 // build WHERE clauses, if any
1956 $where_clauses = array();
1957 if (!empty($query)) {
1958 require_once 'include/SugarSQLValidate.php';
1959 $valid = new SugarSQLValidate();
1960 if(!$valid->validateQueryClauses($query)) {
1961 $GLOBALS['log']->error("Bad query: $query");
1962 $error->set_error('no_access');
1964 'result_count' => -1,
1965 'error' => $error->get_soap_array()
1968 $where_clauses[] = $query;
1970 if ($deleted == 0) {
1971 $where_clauses[] = $seed->table_name . '.deleted = 0';
1974 // if WHERE clauses exist, add them to query
1975 if (!empty($where_clauses)) {
1976 $sql .= ' WHERE ' . implode(' AND ', $where_clauses);
1979 $res = $GLOBALS['db']->query($sql);
1980 $row = $GLOBALS['db']->fetchByAssoc($res);
1983 'result_count' => $row['result_count'],
1984 'error' => $error->get_soap_array()
1989 'set_entries_details',
1990 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'name_value_lists'=>'tns:name_value_lists', 'select_fields' => 'tns:select_fields'),
1991 array('return'=>'tns:set_entries_detail_result'),
1995 * Update or create a list of SugarBeans, returning details about the records created/updated
1997 * @param String $session -- Session ID returned by a previous call to login.
1998 * @param String $module_name -- The name of the module to return records from. This name should be the name the module was developed under (changing a tab name is studio does not affect the name that should be passed into this method)..
1999 * @param Array $name_value_lists -- Array of Bean specific Arrays where the keys of the array are the SugarBean attributes, the values of the array are the values the attributes should have.
2000 * @param Array $select_fields -- A list of the fields to be included in the results. This optional parameter allows for only needed fields to be retrieved.
2001 * @return Array 'name_value_lists' -- Array of Bean specific Arrays where the keys of the array are the SugarBean attributes, the values of the array are the values the attributes should have.
2002 * 'error' -- The SOAP error if any.
2004 function set_entries_details($session, $module_name, $name_value_lists, $select_fields) {
2005 $error = new SoapError();
2007 if(!validate_authenticated($session)){
2008 $error->set_error('invalid_login');
2012 'error' => $error->get_soap_array()
2016 return handle_set_entries($module_name, $name_value_lists, $select_fields);
2019 // INTERNAL FUNCTION NOT EXPOSED THROUGH API
2020 function handle_set_entries($module_name, $name_value_lists, $select_fields = FALSE) {
2021 global $beanList, $beanFiles, $app_list_strings, $current_user;
2023 $error = new SoapError();
2024 $ret_values = array();
2026 if(empty($beanList[$module_name])){
2027 $error->set_error('no_module');
2028 return array('ids'=>array(), 'error'=>$error->get_soap_array());
2031 if(!check_modules_access($current_user, $module_name, 'write')){
2032 $error->set_error('no_access');
2033 return array('ids'=>-1, 'error'=>$error->get_soap_array());
2036 $class_name = $beanList[$module_name];
2037 require_once($beanFiles[$class_name]);
2040 $total = sizeof($name_value_lists);
2042 foreach($name_value_lists as $name_value_list){
2043 $seed = new $class_name();
2045 $seed->update_vcal = false;
2047 //See if we can retrieve the seed by a given id value
2048 foreach($name_value_list as $value)
2050 if($value['name'] == 'id')
2052 $seed->retrieve($value['value']);
2058 $dataValues = array();
2060 foreach($name_value_list as $value)
2062 $val = $value['value'];
2064 if($seed->field_name_map[$value['name']]['type'] == 'enum' || $seed->field_name_map[$value['name']]['type'] == 'radioenum')
2066 $vardef = $seed->field_name_map[$value['name']];
2067 if(isset($app_list_strings[$vardef['options']]) && !isset($app_list_strings[$vardef['options']][$val]) )
2069 if ( in_array($val,$app_list_strings[$vardef['options']]) )
2071 $val = array_search($val,$app_list_strings[$vardef['options']]);
2075 } else if($seed->field_name_map[$value['name']]['type'] == 'multienum') {
2077 $vardef = $seed->field_name_map[$value['name']];
2079 if(isset($app_list_strings[$vardef['options']]) && !isset($app_list_strings[$vardef['options']][$value]) )
2081 $items = explode(",", $val);
2082 $parsedItems = array();
2083 foreach ($items as $item)
2085 if ( in_array($item, $app_list_strings[$vardef['options']]) )
2087 $keyVal = array_search($item,$app_list_strings[$vardef['options']]);
2088 array_push($parsedItems, $keyVal);
2092 if (!empty($parsedItems))
2094 $val = encodeMultienumValue($parsedItems);
2099 //Apply the non-empty values now since this will be used for duplicate checks
2102 $seed->$value['name'] = $val;
2104 //Store all the values in dataValues Array to apply later
2105 $dataValues[$value['name']] = $val;
2108 if($count == $total)
2110 $seed->update_vcal = false;
2114 //Add the account to a contact
2115 if($module_name == 'Contacts'){
2116 $GLOBALS['log']->debug('Creating Contact Account');
2117 add_create_account($seed);
2118 $duplicate_id = check_for_duplicate_contacts($seed);
2119 if($duplicate_id == null)
2121 if($seed->ACLAccess('Save') && ($seed->deleted != 1 || $seed->ACLAccess('Delete')))
2123 //Now apply the values, since this is not a duplicate we can just pass false for the $firstSync argument
2124 apply_values($seed, $dataValues, false);
2126 if($seed->deleted == 1){
2127 $seed->mark_deleted($seed->id);
2132 //since we found a duplicate we should set the sync flag
2133 if( $seed->ACLAccess('Save'))
2135 //Determine if this is a first time sync. We find out based on whether or not a contacts_users relationship exists
2136 $seed->id = $duplicate_id;
2137 $seed->load_relationship("user_sync");
2138 $beans = $seed->user_sync->getBeans();
2139 $first_sync = empty($beans);
2141 //Now apply the values and indicate whether or not this is a first time sync
2142 apply_values($seed, $dataValues, $first_sync);
2143 $seed->contacts_users_id = $current_user->id;
2145 $ids[] = $duplicate_id;//we have a conflict
2149 } else if($module_name == 'Meetings' || $module_name == 'Calls'){
2150 //we are going to check if we have a meeting in the system
2151 //with the same outlook_id. If we do find one then we will grab that
2153 if( $seed->ACLAccess('Save') && ($seed->deleted != 1 || $seed->ACLAccess('Delete'))){
2154 if(empty($seed->id) && !isset($seed->id)){
2155 if(!empty($seed->outlook_id) && isset($seed->outlook_id)){
2156 //at this point we have an object that does not have
2157 //the id set, but does have the outlook_id set
2158 //so we need to query the db to find if we already
2159 //have an object with this outlook_id, if we do
2160 //then we can set the id, otherwise this is a new object
2162 $query = $seed->table_name.".outlook_id = '".$seed->outlook_id."'";
2163 $response = $seed->get_list($order_by, $query, 0,-1,-1,0);
2164 $list = $response['list'];
2165 if(count($list) > 0){
2166 foreach($list as $value)
2168 $seed->id = $value->id;
2174 if (empty($seed->reminder_time)) {
2175 $seed->reminder_time = -1;
2177 if($seed->reminder_time == -1){
2178 $defaultRemindrTime = $current_user->getPreference('reminder_time');
2179 if ($defaultRemindrTime != -1){
2180 $seed->reminder_checked = '1';
2181 $seed->reminder_time = $defaultRemindrTime;
2190 if( $seed->ACLAccess('Save') && ($seed->deleted != 1 || $seed->ACLAccess('Delete'))){
2196 // if somebody is calling set_entries_detail() and wants fields returned...
2197 if ($select_fields !== FALSE) {
2198 $ret_values[$count] = array();
2200 foreach ($select_fields as $select_field) {
2201 if (isset($seed->$select_field)) {
2202 $ret_values[$count][] = get_name_value($select_field, $seed->$select_field);
2208 // handle returns for set_entries_detail() and set_entries()
2209 if ($select_fields !== FALSE) {
2211 'name_value_lists' => $ret_values,
2212 'error' => $error->get_soap_array()
2218 'error' => $error->get_soap_array()