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.
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){
1518 foreach ($search_terms as $term)
1520 $where .= "ea.email_address LIKE '".$GLOBALS['db']->quote($term)."'";
1521 if ($count < $termCount)
1529 $tmpQuery .= $where;
1530 $tmpQuery .= ") AND $seed->table_name.deleted = 0";
1532 $query .= $tmpQuery;
1535 //grab the items from the db
1536 $result = $seed->db->query($query, $offset, $max_results);
1538 while(($row = $seed->db->fetchByAssoc($result)) != null){
1540 $fields = explode(", ", $query_array[$module_name]['fields']);
1541 foreach($fields as $field){
1542 $field_names = explode(".", $field);
1543 $list[$field] = array('name'=>$field_names[1], 'value'=>$row[$field_names[1]]);
1546 $output_list[] = array('id'=>$row['id'],
1547 'module_name'=>$module_name,
1548 'name_value_list'=>$list);
1549 if(empty($field_list)){
1550 $field_list = get_field_list($row);
1557 $next_offset = $offset + sizeof($output_list);
1559 return array('result_count'=>sizeof($output_list), 'next_offset'=>$next_offset,'field_list'=>$field_list, 'entry_list'=>$output_list, 'error'=>$error->get_soap_array());
1565 'get_mailmerge_document',
1566 array('session'=>'xsd:string','file_name'=>'xsd:string', 'fields' => 'tns:select_fields'),
1567 array('return'=>'tns:get_sync_result_encoded'),
1571 * Get MailMerge document
1573 * @param String $session -- Session ID returned by a previous call to login.
1574 * @param unknown_type $file_name
1575 * @param unknown_type $fields
1578 function get_mailmerge_document($session, $file_name, $fields)
1580 global $beanList, $beanFiles, $app_list_strings;
1581 $error = new SoapError();
1582 if(!validate_authenticated($session))
1584 $error->set_error('invalid_login');
1585 return array('result'=>'', 'error'=>$error->get_soap_array());
1587 if(!preg_match('/^sugardata[\.\d\s]+\.php$/', $file_name)) {
1588 $error->set_error('no_records');
1589 return array('result'=>'', 'error'=>$error->get_soap_array());
1593 $file_name = sugar_cached('MergedDocuments/').pathinfo($file_name, PATHINFO_BASENAME);
1595 $master_fields = array();
1596 $related_fields = array();
1598 if(file_exists($file_name))
1600 include($file_name);
1602 $class1 = $merge_array['master_module'];
1603 $beanL = $beanList[$class1];
1604 $bean1 = $beanFiles[$beanL];
1605 require_once($bean1);
1606 $seed1 = new $beanL();
1608 if(!empty($merge_array['related_module']))
1610 $class2 = $merge_array['related_module'];
1611 $beanR = $beanList[$class2];
1612 $bean2 = $beanFiles[$beanR];
1613 require_once($bean2);
1614 $seed2 = new $beanR();
1618 //$token1 = strtolower($class1);
1619 if($class1 == 'Prospects'){
1620 $class1 = 'CampaignProspects';
1622 foreach($fields as $field)
1624 $pos = strpos(strtolower($field), strtolower($class1));
1625 $pos2 = strpos(strtolower($field), strtolower($class2));
1627 $fieldName = str_replace(strtolower($class1).'_', '', strtolower($field));
1628 array_push($master_fields, $fieldName);
1629 }else if($pos2 !== false){
1630 $fieldName = str_replace(strtolower($class2).'_', '', strtolower($field));
1631 array_push($related_fields, $fieldName);
1635 $html = '<html ' . get_language_header() .'><body><table border = 1><tr>';
1637 foreach($master_fields as $master_field){
1638 $html .= '<td>'.$class1.'_'.$master_field.'</td>';
1640 foreach($related_fields as $related_field){
1641 $html .= '<td>'.$class2.'_'.$related_field.'</td>';
1645 $ids = $merge_array['ids'];
1646 $is_prospect_merge = ($seed1->object_name == 'Prospect');
1647 foreach($ids as $key=>$value){
1648 if($is_prospect_merge){
1649 $seed1 = $seed1->retrieveTarget($key);
1651 $seed1->retrieve($key);
1654 foreach($master_fields as $master_field){
1655 if(isset($seed1->$master_field)){
1656 if($seed1->field_name_map[$master_field]['type'] == 'enum'){
1657 //pull in the translated dom
1658 $html .='<td>'.$app_list_strings[$seed1->field_name_map[$master_field]['options']][$seed1->$master_field].'</td>';
1660 $html .='<td>'.$seed1->$master_field.'</td>';
1664 $html .= '<td></td>';
1667 if(isset($value) && !empty($value)){
1668 $seed2->retrieve($value);
1669 foreach($related_fields as $related_field){
1670 if(isset($seed2->$related_field)){
1671 if($seed2->field_name_map[$related_field]['type'] == 'enum'){
1672 //pull in the translated dom
1673 $html .='<td>'.$app_list_strings[$seed2->field_name_map[$related_field]['options']][$seed2->$related_field].'</td>';
1675 $html .= '<td>'.$seed2->$related_field.'</td>';
1679 $html .= '<td></td>';
1685 $html .= "</table></body></html>";
1688 $result = base64_encode($html);
1689 return array('result' => $result, 'error' => $error);
1693 'get_mailmerge_document2',
1694 array('session'=>'xsd:string','file_name'=>'xsd:string', 'fields' => 'tns:select_fields'),
1695 array('return'=>'tns:get_mailmerge_document_result'),
1699 * Enter description here...
1701 * @param String $session -- Session ID returned by a previous call to login.
1702 * @param unknown_type $file_name
1703 * @param unknown_type $fields
1706 function get_mailmerge_document2($session, $file_name, $fields)
1708 global $beanList, $beanFiles, $app_list_strings, $app_strings;
1710 $error = new SoapError();
1711 if(!validate_authenticated($session))
1713 $GLOBALS['log']->error('invalid_login');
1714 $error->set_error('invalid_login');
1715 return array('result'=>'', 'error'=>$error->get_soap_array());
1717 if(!preg_match('/^sugardata[\.\d\s]+\.php$/', $file_name)) {
1718 $GLOBALS['log']->error($app_strings['ERR_NO_SUCH_FILE'] . " ({$file_name})");
1719 $error->set_error('no_records');
1720 return array('result'=>'', 'error'=>$error->get_soap_array());
1724 $file_name = sugar_cached('MergedDocuments/').pathinfo($file_name, PATHINFO_BASENAME);
1726 $master_fields = array();
1727 $related_fields = array();
1729 if(file_exists($file_name))
1731 include($file_name);
1733 $class1 = $merge_array['master_module'];
1734 $beanL = $beanList[$class1];
1735 $bean1 = $beanFiles[$beanL];
1736 require_once($bean1);
1737 $seed1 = new $beanL();
1739 if(!empty($merge_array['related_module']))
1741 $class2 = $merge_array['related_module'];
1742 $beanR = $beanList[$class2];
1743 $bean2 = $beanFiles[$beanR];
1744 require_once($bean2);
1745 $seed2 = new $beanR();
1749 //$token1 = strtolower($class1);
1750 if($class1 == 'Prospects'){
1751 $class1 = 'CampaignProspects';
1753 foreach($fields as $field)
1755 $pos = strpos(strtolower($field), strtolower($class1));
1756 $pos2 = strpos(strtolower($field), strtolower($class2));
1758 $fieldName = str_replace(strtolower($class1).'_', '', strtolower($field));
1759 array_push($master_fields, $fieldName);
1760 }else if($pos2 !== false){
1761 $fieldName = str_replace(strtolower($class2).'_', '', strtolower($field));
1762 array_push($related_fields, $fieldName);
1766 $html = '<html ' . get_language_header() . '><body><table border = 1><tr>';
1768 foreach($master_fields as $master_field){
1769 $html .= '<td>'.$class1.'_'.$master_field.'</td>';
1771 foreach($related_fields as $related_field){
1772 $html .= '<td>'.$class2.'_'.$related_field.'</td>';
1776 $ids = $merge_array['ids'];
1777 $resultIds = array();
1778 $is_prospect_merge = ($seed1->object_name == 'Prospect');
1779 if($is_prospect_merge){
1782 foreach($ids as $key=>$value){
1784 if($is_prospect_merge){
1785 $seed1 = $pSeed->retrieveTarget($key);
1787 $seed1->retrieve($key);
1789 $resultIds[] = array('name' => $seed1->module_name, 'value' => $key);
1791 foreach($master_fields as $master_field){
1792 if(isset($seed1->$master_field)){
1793 if($seed1->field_name_map[$master_field]['type'] == 'enum'){
1794 //pull in the translated dom
1795 $html .='<td>'.$app_list_strings[$seed1->field_name_map[$master_field]['options']][$seed1->$master_field].'</td>';
1797 $html .='<td>'.$seed1->$master_field.'</td>';
1801 $html .= '<td></td>';
1804 if(isset($value) && !empty($value)){
1805 $resultIds[] = array('name' => $seed2->module_name, 'value' => $value);
1806 $seed2->retrieve($value);
1807 foreach($related_fields as $related_field){
1808 if(isset($seed2->$related_field)){
1809 if($seed2->field_name_map[$related_field]['type'] == 'enum'){
1810 //pull in the translated dom
1811 $html .='<td>'.$app_list_strings[$seed2->field_name_map[$related_field]['options']][$seed2->$related_field].'</td>';
1813 $html .= '<td>'.$seed2->$related_field.'</td>';
1817 $html .= '<td></td>';
1823 $html .= "</table></body></html>";
1826 $result = base64_encode($html);
1827 return array('html' => $result, 'name_value_list' => $resultIds, 'error' => $error);
1831 'get_document_revision',
1832 array('session'=>'xsd:string','i'=>'xsd:string'),
1833 array('return'=>'tns:return_document_revision'),
1837 * This method is used as a result of the .htaccess lock down on the cache directory. It will allow a
1838 * properly authenticated user to download a document that they have proper rights to download.
1840 * @param String $session -- Session ID returned by a previous call to login.
1841 * @param String $id -- ID of the document revision to obtain
1842 * @return return_document_revision - this is a complex type as defined in SoapTypes.php
1844 function get_document_revision($session,$id)
1846 global $sugar_config;
1848 $error = new SoapError();
1849 if(!validate_authenticated($session)){
1850 $error->set_error('invalid_login');
1851 return array('id'=>-1, 'error'=>$error->get_soap_array());
1855 $dr = new DocumentRevision();
1857 if(!empty($dr->filename)){
1858 $filename = "upload://{$dr->id}";
1859 $contents = base64_encode(sugar_file_get_contents($filename));
1860 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());
1862 $error->set_error('no_records');
1863 return array('id'=>-1, 'error'=>$error->get_soap_array());
1868 'set_campaign_merge',
1869 array('session'=>'xsd:string', 'targets'=>'tns:select_fields', 'campaign_id'=>'xsd:string'),
1870 array('return'=>'tns:error_value'),
1873 * Once we have successfuly done a mail merge on a campaign, we need to notify Sugar of the targets
1874 * and the campaign_id for tracking purposes
1876 * @param session the session id of the authenticated user
1877 * @param targets a string array of ids identifying the targets used in the merge
1878 * @param campaign_id the campaign_id used for the merge
1880 * @return error_value
1882 function set_campaign_merge($session,$targets, $campaign_id){
1883 $error = new SoapError();
1884 if(!validate_authenticated($session)){
1885 $error->set_error('invalid_login');
1886 return $error->get_soap_array();
1888 if (empty($campaign_id) or !is_array($targets) or count($targets) == 0) {
1889 $GLOBALS['log']->debug('set_campaign_merge: Merge action status will not be updated, because, campaign_id is null or no targets were selected.');
1891 require_once('modules/Campaigns/utils.php');
1892 campaign_log_mail_merge($campaign_id,$targets);
1895 return $error->get_soap_array();
1898 'get_entries_count',
1899 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'query'=>'xsd:string', 'deleted' => 'xsd:int'),
1900 array('return'=>'tns:get_entries_count_result'),
1904 * Retrieve number of records in a given module
1906 * @param session the session id of the authenticated user
1907 * @param module_name module to retrieve number of records from
1908 * @param query allows webservice user to provide a WHERE clause
1909 * @param deleted specify whether or not to include deleted records
1911 @return get_entries_count_result - this is a complex type as defined in SoapTypes.php
1913 function get_entries_count($session, $module_name, $query, $deleted) {
1914 global $beanList, $beanFiles, $current_user;
1916 $error = new SoapError();
1918 if (!validate_authenticated($session)) {
1919 $error->set_error('invalid_login');
1921 'result_count' => -1,
1922 'error' => $error->get_soap_array()
1926 if (empty($beanList[$module_name])) {
1927 $error->set_error('no_module');
1929 'result_count' => -1,
1930 'error' => $error->get_soap_array()
1934 if(!check_modules_access($current_user, $module_name, 'list')){
1935 $error->set_error('no_access');
1937 'result_count' => -1,
1938 'error' => $error->get_soap_array()
1942 $class_name = $beanList[$module_name];
1943 require_once($beanFiles[$class_name]);
1944 $seed = new $class_name();
1946 if (!$seed->ACLAccess('ListView')) {
1947 $error->set_error('no_access');
1949 'result_count' => -1,
1950 'error' => $error->get_soap_array()
1954 $sql = 'SELECT COUNT(*) result_count FROM ' . $seed->table_name . ' ';
1957 // build WHERE clauses, if any
1958 $where_clauses = array();
1959 if (!empty($query)) {
1960 require_once 'include/SugarSQLValidate.php';
1961 $valid = new SugarSQLValidate();
1962 if(!$valid->validateQueryClauses($query)) {
1963 $GLOBALS['log']->error("Bad query: $query");
1964 $error->set_error('no_access');
1966 'result_count' => -1,
1967 'error' => $error->get_soap_array()
1970 $where_clauses[] = $query;
1972 if ($deleted == 0) {
1973 $where_clauses[] = $seed->table_name . '.deleted = 0';
1976 // if WHERE clauses exist, add them to query
1977 if (!empty($where_clauses)) {
1978 $sql .= ' WHERE ' . implode(' AND ', $where_clauses);
1981 $res = $GLOBALS['db']->query($sql);
1982 $row = $GLOBALS['db']->fetchByAssoc($res);
1985 'result_count' => $row['result_count'],
1986 'error' => $error->get_soap_array()
1991 'set_entries_details',
1992 array('session'=>'xsd:string', 'module_name'=>'xsd:string', 'name_value_lists'=>'tns:name_value_lists', 'select_fields' => 'tns:select_fields'),
1993 array('return'=>'tns:set_entries_detail_result'),
1997 * Update or create a list of SugarBeans, returning details about the records created/updated
1999 * @param String $session -- Session ID returned by a previous call to login.
2000 * @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)..
2001 * @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.
2002 * @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.
2003 * @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.
2004 * 'error' -- The SOAP error if any.
2006 function set_entries_details($session, $module_name, $name_value_lists, $select_fields) {
2007 $error = new SoapError();
2009 if(!validate_authenticated($session)){
2010 $error->set_error('invalid_login');
2014 'error' => $error->get_soap_array()
2018 return handle_set_entries($module_name, $name_value_lists, $select_fields);
2021 // INTERNAL FUNCTION NOT EXPOSED THROUGH API
2022 function handle_set_entries($module_name, $name_value_lists, $select_fields = FALSE) {
2023 global $beanList, $beanFiles, $app_list_strings, $current_user;
2025 $error = new SoapError();
2026 $ret_values = array();
2028 if(empty($beanList[$module_name])){
2029 $error->set_error('no_module');
2030 return array('ids'=>array(), 'error'=>$error->get_soap_array());
2033 if(!check_modules_access($current_user, $module_name, 'write')){
2034 $error->set_error('no_access');
2035 return array('ids'=>-1, 'error'=>$error->get_soap_array());
2038 $class_name = $beanList[$module_name];
2039 require_once($beanFiles[$class_name]);
2042 $total = sizeof($name_value_lists);
2044 foreach($name_value_lists as $name_value_list){
2045 $seed = new $class_name();
2047 $seed->update_vcal = false;
2049 //See if we can retrieve the seed by a given id value
2050 foreach($name_value_list as $value)
2052 if($value['name'] == 'id')
2054 $seed->retrieve($value['value']);
2060 $dataValues = array();
2062 foreach($name_value_list as $value)
2064 $val = $value['value'];
2066 if($seed->field_name_map[$value['name']]['type'] == 'enum' || $seed->field_name_map[$value['name']]['type'] == 'radioenum')
2068 $vardef = $seed->field_name_map[$value['name']];
2069 if(isset($app_list_strings[$vardef['options']]) && !isset($app_list_strings[$vardef['options']][$val]) )
2071 if ( in_array($val,$app_list_strings[$vardef['options']]) )
2073 $val = array_search($val,$app_list_strings[$vardef['options']]);
2077 } else if($seed->field_name_map[$value['name']]['type'] == 'multienum') {
2079 $vardef = $seed->field_name_map[$value['name']];
2081 if(isset($app_list_strings[$vardef['options']]) && !isset($app_list_strings[$vardef['options']][$value]) )
2083 $items = explode(",", $val);
2084 $parsedItems = array();
2085 foreach ($items as $item)
2087 if ( in_array($item, $app_list_strings[$vardef['options']]) )
2089 $keyVal = array_search($item,$app_list_strings[$vardef['options']]);
2090 array_push($parsedItems, $keyVal);
2094 if (!empty($parsedItems))
2096 $val = encodeMultienumValue($parsedItems);
2101 //Apply the non-empty values now since this will be used for duplicate checks
2104 $seed->$value['name'] = $val;
2106 //Store all the values in dataValues Array to apply later
2107 $dataValues[$value['name']] = $val;
2110 if($count == $total)
2112 $seed->update_vcal = false;
2116 //Add the account to a contact
2117 if($module_name == 'Contacts'){
2118 $GLOBALS['log']->debug('Creating Contact Account');
2119 add_create_account($seed);
2120 $duplicate_id = check_for_duplicate_contacts($seed);
2121 if($duplicate_id == null)
2123 if($seed->ACLAccess('Save') && ($seed->deleted != 1 || $seed->ACLAccess('Delete')))
2125 //Now apply the values, since this is not a duplicate we can just pass false for the $firstSync argument
2126 apply_values($seed, $dataValues, false);
2128 if($seed->deleted == 1){
2129 $seed->mark_deleted($seed->id);
2134 //since we found a duplicate we should set the sync flag
2135 if( $seed->ACLAccess('Save'))
2137 //Determine if this is a first time sync. We find out based on whether or not a contacts_users relationship exists
2138 $seed->id = $duplicate_id;
2139 $seed->load_relationship("user_sync");
2140 $beans = $seed->user_sync->getBeans();
2141 $first_sync = empty($beans);
2143 //Now apply the values and indicate whether or not this is a first time sync
2144 apply_values($seed, $dataValues, $first_sync);
2145 $seed->contacts_users_id = $current_user->id;
2147 $ids[] = $duplicate_id;//we have a conflict
2151 } else if($module_name == 'Meetings' || $module_name == 'Calls'){
2152 //we are going to check if we have a meeting in the system
2153 //with the same outlook_id. If we do find one then we will grab that
2155 if( $seed->ACLAccess('Save') && ($seed->deleted != 1 || $seed->ACLAccess('Delete'))){
2156 if(empty($seed->id) && !isset($seed->id)){
2157 if(!empty($seed->outlook_id) && isset($seed->outlook_id)){
2158 //at this point we have an object that does not have
2159 //the id set, but does have the outlook_id set
2160 //so we need to query the db to find if we already
2161 //have an object with this outlook_id, if we do
2162 //then we can set the id, otherwise this is a new object
2164 $query = $seed->table_name.".outlook_id = '".$seed->outlook_id."'";
2165 $response = $seed->get_list($order_by, $query, 0,-1,-1,0);
2166 $list = $response['list'];
2167 if(count($list) > 0){
2168 foreach($list as $value)
2170 $seed->id = $value->id;
2176 if (empty($seed->reminder_time)) {
2177 $seed->reminder_time = -1;
2179 if($seed->reminder_time == -1){
2180 $defaultRemindrTime = $current_user->getPreference('reminder_time');
2181 if ($defaultRemindrTime != -1){
2182 $seed->reminder_checked = '1';
2183 $seed->reminder_time = $defaultRemindrTime;
2192 if( $seed->ACLAccess('Save') && ($seed->deleted != 1 || $seed->ACLAccess('Delete'))){
2198 // if somebody is calling set_entries_detail() and wants fields returned...
2199 if ($select_fields !== FALSE) {
2200 $ret_values[$count] = array();
2202 foreach ($select_fields as $select_field) {
2203 if (isset($seed->$select_field)) {
2204 $ret_values[$count][] = get_name_value($select_field, $seed->$select_field);
2210 // handle returns for set_entries_detail() and set_entries()
2211 if ($select_fields !== FALSE) {
2213 'name_value_lists' => $ret_values,
2214 'error' => $error->get_soap_array()
2220 'error' => $error->get_soap_array()