Test Failed
Push — CI ( 0f01dd...c95a04 )
by Adam
55:13
created

SugarWebServiceImplv4::search_by_module()   F

Complexity

Conditions 46
Paths > 20000

Size

Total Lines 196
Code Lines 129

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 196
rs 2
cc 46
eloc 129
nc 429496.7295
nop 9

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
if(!defined('sugarEntry'))define('sugarEntry', true);
3
/*********************************************************************************
4
 * SugarCRM Community Edition is a customer relationship management program developed by
5
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
42
/**
43
 * This class is an implemenatation class for all the rest services
44
 */
45
require_once('service/v3_1/SugarWebServiceImplv3_1.php');
46
require_once('SugarWebServiceUtilv4.php');
47
48
49
class SugarWebServiceImplv4 extends SugarWebServiceImplv3_1 {
50
51
    public function __construct()
52
    {
53
        self::$helperObject = new SugarWebServiceUtilv4();
54
    }
55
56
        /**
57
     * Log the user into the application
58
     *
59
     * @param UserAuth array $user_auth -- Set user_name and password (password needs to be
60
     *      in the right encoding for the type of authentication the user is setup for.  For Base
61
     *      sugar validation, password is the MD5 sum of the plain text password.
62
     * @param String $application -- The name of the application you are logging in from.  (Currently unused).
63
     * @param array $name_value_list -- Array of name value pair of extra parameters. As of today only 'language' and 'notifyonsave' is supported
64
     * @return Array - id - String id is the session_id of the session that was created.
65
     * 				 - module_name - String - module name of user
66
     * 				 - name_value_list - Array - The name value pair of user_id, user_name, user_language, user_currency_id, user_currency_name,
67
     *                                         - user_default_team_id, user_is_admin, user_default_dateformat, user_default_timeformat
68
     * @exception 'SoapFault' -- The SOAP error, if any
69
     */
70
    public function login($user_auth, $application, $name_value_list = array()){
71
        $GLOBALS['log']->info("Begin: SugarWebServiceImpl->login({$user_auth['user_name']}, $application, ". print_r($name_value_list, true) .")");
72
        global $sugar_config, $system_config;
73
        $error = new SoapError();
74
        $user = new User();
75
        $success = false;
76
        //rrs
77
        $system_config = new Administration();
78
        $system_config->retrieveSettings('system');
79
        $authController = new AuthenticationController();
80
        //rrs
81
        if(!empty($user_auth['encryption']) && $user_auth['encryption'] === 'PLAIN' && $authController->authController->userAuthenticateClass != "LDAPAuthenticateUser")
82
        {
83
            $user_auth['password'] = md5($user_auth['password']);
84
        }
85
        $isLoginSuccess = $authController->login($user_auth['user_name'], $user_auth['password'], array('passwordEncrypted' => true));
86
        $usr_id=$user->retrieve_user_id($user_auth['user_name']);
87
        if($usr_id)
88
            $user->retrieve($usr_id);
89
90
        if ($isLoginSuccess)
91
        {
92
            if ($_SESSION['hasExpiredPassword'] =='1')
93
            {
94
                $error->set_error('password_expired');
95
                $GLOBALS['log']->fatal('password expired for user ' . $user_auth['user_name']);
96
                LogicHook::initialize();
97
                $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
98
                self::$helperObject->setFaultObject($error);
99
                return;
100
            }
101
            if(!empty($user) && !empty($user->id) && !$user->is_group)
102
            {
103
                $success = true;
104
                global $current_user;
105
                $current_user = $user;
106
            }
107
        }
108
        else if($usr_id && isset($user->user_name) && ($user->getPreference('lockout') == '1'))
109
        {
110
            $error->set_error('lockout_reached');
111
            $GLOBALS['log']->fatal('Lockout reached for user ' . $user_auth['user_name']);
112
            LogicHook::initialize();
113
            $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
114
            self::$helperObject->setFaultObject($error);
115
            return;
116
        }
117
		else if(function_exists('mcrypt_cbc') && $authController->authController->userAuthenticateClass == "LDAPAuthenticateUser"
118
        		&& (empty($user_auth['encryption']) || $user_auth['encryption'] !== 'PLAIN' ) )
119
        {
120
            $password = self::$helperObject->decrypt_string($user_auth['password']);
121
            $authController->loggedIn = false; // reset login attempt to try again with decrypted password
122
            if($authController->login($user_auth['user_name'], $password) && isset($_SESSION['authenticated_user_id']))
123
                $success = true;
124
        }
125
        else if( $authController->authController->userAuthenticateClass == "LDAPAuthenticateUser"
126
                 && (empty($user_auth['encryption']) || $user_auth['encryption'] == 'PLAIN' ) )
127
        {
128
129
        	$authController->loggedIn = false; // reset login attempt to try again with md5 password
130
        	if($authController->login($user_auth['user_name'], md5($user_auth['password']), array('passwordEncrypted' => true))
131
        		&& isset($_SESSION['authenticated_user_id']))
132
        	{
133
        		$success = true;
134
        	}
135
        	else
136
        	{
137
138
	            $error->set_error('ldap_error');
139
	            LogicHook::initialize();
140
	            $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
141
	            self::$helperObject->setFaultObject($error);
142
	            return;
143
        	}
144
        }
145
146
147
        if($success)
148
        {
149
            session_start();
150
            global $current_user;
151
            //$current_user = $user;
152
            self::$helperObject->login_success($name_value_list);
153
            $current_user->loadPreferences();
154
            $_SESSION['is_valid_session']= true;
155
            $_SESSION['ip_address'] = query_client_ip();
156
            $_SESSION['user_id'] = $current_user->id;
157
            $_SESSION['type'] = 'user';
158
            $_SESSION['avail_modules']= self::$helperObject->get_user_module_list($current_user);
159
            $_SESSION['authenticated_user_id'] = $current_user->id;
160
            $_SESSION['unique_key'] = $sugar_config['unique_key'];
161
            $GLOBALS['log']->info('End: SugarWebServiceImpl->login - successful login');
162
            $current_user->call_custom_logic('after_login');
163
            $nameValueArray = array();
164
            global $current_language;
165
            $nameValueArray['user_id'] = self::$helperObject->get_name_value('user_id', $current_user->id);
166
            $nameValueArray['user_name'] = self::$helperObject->get_name_value('user_name', $current_user->user_name);
167
            $nameValueArray['user_language'] = self::$helperObject->get_name_value('user_language', $current_language);
168
            $cur_id = $current_user->getPreference('currency');
169
            $nameValueArray['user_currency_id'] = self::$helperObject->get_name_value('user_currency_id', $cur_id);
170
            $nameValueArray['user_is_admin'] = self::$helperObject->get_name_value('user_is_admin', is_admin($current_user));
171
            $nameValueArray['user_default_team_id'] = self::$helperObject->get_name_value('user_default_team_id', $current_user->default_team );
172
            $nameValueArray['user_default_dateformat'] = self::$helperObject->get_name_value('user_default_dateformat', $current_user->getPreference('datef') );
173
            $nameValueArray['user_default_timeformat'] = self::$helperObject->get_name_value('user_default_timeformat', $current_user->getPreference('timef') );
174
175
            $num_grp_sep = $current_user->getPreference('num_grp_sep');
176
            $dec_sep = $current_user->getPreference('dec_sep');
177
            $nameValueArray['user_number_seperator'] = self::$helperObject->get_name_value('user_number_seperator', empty($num_grp_sep) ? $sugar_config['default_number_grouping_seperator'] : $num_grp_sep);
178
            $nameValueArray['user_decimal_seperator'] = self::$helperObject->get_name_value('user_decimal_seperator', empty($dec_sep) ? $sugar_config['default_decimal_seperator'] : $dec_sep);
179
180
            $nameValueArray['mobile_max_list_entries'] = self::$helperObject->get_name_value('mobile_max_list_entries', $sugar_config['wl_list_max_entries_per_page'] );
181
            $nameValueArray['mobile_max_subpanel_entries'] = self::$helperObject->get_name_value('mobile_max_subpanel_entries', $sugar_config['wl_list_max_entries_per_subpanel'] );
182
183
184
            $currencyObject = new Currency();
185
            $currencyObject->retrieve($cur_id);
186
            $nameValueArray['user_currency_name'] = self::$helperObject->get_name_value('user_currency_name', $currencyObject->name);
187
            $_SESSION['user_language'] = $current_language;
188
            return array('id'=>session_id(), 'module_name'=>'Users', 'name_value_list'=>$nameValueArray);
189
        }
190
        LogicHook::initialize();
191
        $GLOBALS['logic_hook']->call_custom_logic('Users', 'login_failed');
192
        $error->set_error('invalid_login');
193
        self::$helperObject->setFaultObject($error);
194
        $GLOBALS['log']->error('End: SugarWebServiceImpl->login - failed login');
195
    }
196
197
198
	/**
199
	 * Retrieve a list of SugarBean's based on provided IDs. This API will not wotk with report module
200
	 *
201
	 * @param String $session -- Session ID returned by a previous call to login.
202
	 * @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)..
203
	 * @param Array $ids -- An array of SugarBean IDs.
204
	 * @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.
205
	 * @param Array $link_name_to_fields_array -- A list of link_names and for each link_name, what fields value to be returned. For ex.'link_name_to_fields_array' => array(array('name' =>  'email_addresses', 'value' => array('id', 'email_address', 'opt_out', 'primary_address')))
206
	 * @return Array
207
	 *        'entry_list' -- Array - The records name value pair for the simple data types excluding link field data.
208
	 *	     'relationship_list' -- Array - The records link field data. The example is if asked about accounts email address then return data would look like Array ( [0] => Array ( [name] => email_addresses [records] => Array ( [0] => Array ( [0] => Array ( [name] => id [value] => 3fb16797-8d90-0a94-ac12-490b63a6be67 ) [1] => Array ( [name] => email_address [value] => [email protected] ) [2] => Array ( [name] => opt_out [value] => 0 ) [3] => Array ( [name] => primary_address [value] => 1 ) ) [1] => Array ( [0] => Array ( [name] => id [value] => 403f8da1-214b-6a88-9cef-490b63d43566 ) [1] => Array ( [name] => email_address [value] => [email protected] ) [2] => Array ( [name] => opt_out [value] => 0 ) [3] => Array ( [name] => primary_address [value] => 0 ) ) ) ) )
209
	 * @exception 'SoapFault' -- The SOAP error, if any
210
	 */
211
	public function get_entries($session, $module_name, $ids, $select_fields, $link_name_to_fields_array)
212
	{
213
	    $result = parent::get_entries($session, $module_name, $ids, $select_fields, $link_name_to_fields_array);
214
		$relationshipList = $result['relationship_list'];
215
		$returnRelationshipList = array();
216
		foreach($relationshipList as $rel){
217
			$link_output = array();
218
			foreach($rel as $row){
219
				$rowArray = array();
220
				foreach($row['records'] as $record){
221
					$rowArray[]['link_value'] = $record;
222
				}
223
				$link_output[] = array('name' => $row['name'], 'records' => $rowArray);
224
			}
225
			$returnRelationshipList[]['link_list'] = $link_output;
226
		}
227
228
		$result['relationship_list'] = $returnRelationshipList;
229
		return $result;
230
	}
231
232
	    /**
233
     * Retrieve a list of beans.  This is the primary method for getting list of SugarBeans from Sugar using the SOAP API.
234
     *
235
     * @param String $session -- Session ID returned by a previous call to login.
236
     * @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)..
237
     * @param String $query -- SQL where clause without the word 'where'
238
     * @param String $order_by -- SQL order by clause without the phrase 'order by'
239
     * @param integer $offset -- The record offset to start from.
240
     * @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.
241
     * @param Array $link_name_to_fields_array -- A list of link_names and for each link_name, what fields value to be returned. For ex.'link_name_to_fields_array' => array(array('name' =>  'email_addresses', 'value' => array('id', 'email_address', 'opt_out', 'primary_address')))
242
    * @param integer $max_results -- The maximum number of records to return.  The default is the sugar configuration value for 'list_max_entries_per_page'
243
     * @param integer $deleted -- false if deleted records should not be include, true if deleted records should be included.
244
     * @return Array 'result_count' -- integer - The number of records returned
245
     *               'next_offset' -- integer - 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.
246
     *               'entry_list' -- Array - The records that were retrieved
247
     *	     		 'relationship_list' -- Array - The records link field data. The example is if asked about accounts email address then return data would look like Array ( [0] => Array ( [name] => email_addresses [records] => Array ( [0] => Array ( [0] => Array ( [name] => id [value] => 3fb16797-8d90-0a94-ac12-490b63a6be67 ) [1] => Array ( [name] => email_address [value] => [email protected] ) [2] => Array ( [name] => opt_out [value] => 0 ) [3] => Array ( [name] => primary_address [value] => 1 ) ) [1] => Array ( [0] => Array ( [name] => id [value] => 403f8da1-214b-6a88-9cef-490b63d43566 ) [1] => Array ( [name] => email_address [value] => [email protected] ) [2] => Array ( [name] => opt_out [value] => 0 ) [3] => Array ( [name] => primary_address [value] => 0 ) ) ) ) )
248
    * @exception 'SoapFault' -- The SOAP error, if any
249
    */
250
    function get_entry_list($session, $module_name, $query, $order_by,$offset, $select_fields, $link_name_to_fields_array, $max_results, $deleted, $favorites = false ){
251
252
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->get_entry_list');
253
        global  $beanList, $beanFiles;
254
        $error = new SoapError();
255
        $using_cp = false;
256
        if($module_name == 'CampaignProspects'){
257
            $module_name = 'Prospects';
258
            $using_cp = true;
259
        }
260
        if (!self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', $module_name, 'read', 'no_access', $error)) {
261
            $GLOBALS['log']->error('End: SugarWebServiceImpl->get_entry_list - FAILED on checkSessionAndModuleAccess');
262
            return;
263
        } // if
264
265
        if (!self::$helperObject->checkQuery($error, $query, $order_by)) {
266
    		$GLOBALS['log']->info('End: SugarWebServiceImpl->get_entry_list');
267
        	return;
268
        } // if
269
270
        // If the maximum number of entries per page was specified, override the configuration value.
271
        if($max_results > 0){
272
            global $sugar_config;
273
            $sugar_config['list_max_entries_per_page'] = $max_results;
274
        } // if
275
276
        $class_name = $beanList[$module_name];
277
        require_once($beanFiles[$class_name]);
278
        $seed = new $class_name();
279
280
        if (!self::$helperObject->checkACLAccess($seed, 'list', $error, 'no_access')) {
281
            $GLOBALS['log']->error('End: SugarWebServiceImpl->get_entry_list - FAILED on checkACLAccess');
282
            return;
283
        } // if
284
285
        if($query == ''){
286
            $where = '';
287
        } // if
288
        if($offset == '' || $offset == -1){
289
            $offset = 0;
290
        } // if
291
        if($deleted){
292
            $deleted = -1;
293
        }
294
        if($using_cp){
295
            $response = $seed->retrieveTargetList($query, $select_fields, $offset,-1,-1,$deleted);
296
        }else
297
        {
298
            $response = self::$helperObject->get_data_list($seed,$order_by, $query, $offset,-1,-1,$deleted,$favorites);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SoapHelperWebServices as the method get_data_list() does only exist in the following sub-classes of SoapHelperWebServices: SugarWebServiceUtilv3_1, SugarWebServiceUtilv4, SugarWebServiceUtilv4_1. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
299
        } // else
300
        $list = $response['list'];
301
302
        $output_list = array();
303
        $linkoutput_list = array();
304
305
        foreach($list as $value) {
306
            if(isset($value->emailAddress)){
307
                $value->emailAddress->handleLegacyRetrieve($value);
308
            } // if
309
            $value->fill_in_additional_detail_fields();
310
311
            $output_list[] = self::$helperObject->get_return_value_for_fields($value, $module_name, $select_fields);
312
            if(!empty($link_name_to_fields_array)){
313
                $linkoutput_list[] = self::$helperObject->get_return_value_for_link_fields($value, $module_name, $link_name_to_fields_array);
314
            }
315
        } // foreach
316
317
        // Calculate the offset for the start of the next page
318
        $next_offset = $offset + sizeof($output_list);
319
320
		$returnRelationshipList = array();
321
		foreach($linkoutput_list as $rel){
322
			$link_output = array();
323
			foreach($rel as $row){
324
				$rowArray = array();
325
				foreach($row['records'] as $record){
326
					$rowArray[]['link_value'] = $record;
327
				}
328
				$link_output[] = array('name' => $row['name'], 'records' => $rowArray);
329
			}
330
			$returnRelationshipList[]['link_list'] = $link_output;
331
		}
332
333
		$totalRecordCount = $response['row_count'];
334
        if( !empty($sugar_config['disable_count_query']) )
335
            $totalRecordCount = -1;
336
337
        $GLOBALS['log']->info('End: SugarWebServiceImpl->get_entry_list - SUCCESS');
338
        return array('result_count'=>sizeof($output_list), 'total_count' => $totalRecordCount, 'next_offset'=>$next_offset, 'entry_list'=>$output_list, 'relationship_list' => $returnRelationshipList);
339
    } // fn
340
341
	/**
342
     * Retrieve the layout metadata for a given module given a specific type and view.
343
     *
344
     * @param String $session -- Session ID returned by a previous call to login.
345
     * @param array $module_name(s) -- The name of the module(s) 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)..
0 ignored issues
show
Bug introduced by
There is no parameter named $module_name(s). Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
346
     * @return array $type The type(s) of views requested.  Current supported types are 'default' (for application) and 'wireless'
347
     * @return array $view The view(s) requested.  Current supported types are edit, detail, list, and subpanel.
348
     * @exception 'SoapFault' -- The SOAP error, if any
349
     */
350
    function get_module_layout($session, $a_module_names, $a_type, $a_view,$acl_check = TRUE, $md5 = FALSE){
351
    	$GLOBALS['log']->info('Begin: SugarWebServiceImpl->get_module_layout');
352
353
    	global  $beanList, $beanFiles;
354
    	$error = new SoapError();
355
        $results = array();
356
        foreach ($a_module_names as $module_name)
357
        {
358
            if (!self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', $module_name, 'read', 'no_access', $error))
359
            {
360
                $GLOBALS['log']->error("End: SugarWebServiceImpl->get_module_layout for $module_name - FAILED on checkSessionAndModuleAccess");
361
                continue;
362
            }
363
364
            if( empty($module_name) )
365
                continue;
366
367
            $class_name = $beanList[$module_name];
368
            require_once($beanFiles[$class_name]);
369
            $seed = new $class_name();
370
371
            foreach ($a_view as $view)
372
            {
373
                $aclViewCheck = (strtolower($view) == 'subpanel') ? 'DetailView' : ucfirst(strtolower($view)) . 'View';
374
                if(!$acl_check || $seed->ACLAccess($aclViewCheck, true) )
375
                {
376
                    foreach ($a_type as $type)
377
                    {
378
                        $a_vardefs = self::$helperObject->get_module_view_defs($module_name, $type, $view);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SoapHelperWebServices as the method get_module_view_defs() does only exist in the following sub-classes of SoapHelperWebServices: SugarWebServiceUtilv3, SugarWebServiceUtilv3_1, SugarWebServiceUtilv4, SugarWebServiceUtilv4_1. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
379
                        if($md5)
380
                            $results[$module_name][$type][$view] = md5(serialize($a_vardefs));
381
                        else
382
                            $results[$module_name][$type][$view] = $a_vardefs;
383
                    }
384
                }
385
            }
386
        }
387
388
        $GLOBALS['log']->info('End: SugarWebServiceImpl->get_module_layout ->> '.print_r($results,true) );
389
390
        return $results;
391
    }
392
393
394
	/**
395
     * Given a list of modules to search and a search string, return the id, module_name, along with the fields
396
     * We will support Accounts, Bugs, Cases, Contacts, Leads, Opportunities, Project, ProjectTask, Quotes
397
     *
398
     * @param string $session			- Session ID returned by a previous call to login.
399
     * @param string $search_string 	- string to search
400
     * @param string[] $modules			- array of modules to query
401
     * @param int $offset				- a specified offset in the query
402
     * @param int $max_results			- max number of records to return
403
     * @param string $assigned_user_id	- a user id to filter all records by, leave empty to exclude the filter
404
     * @param string[] $select_fields   - An array of fields to return.  If empty the default return fields will be from the active list view defs.
405
     * @param bool $unified_search_only - A boolean indicating if we should only search against those modules participating in the unified search.
406
     * @param bool $favorites           - A boolean indicating if we should only search against records marked as favorites.
407
     * @return Array return_search_result 	- Array('Accounts' => array(array('name' => 'first_name', 'value' => 'John', 'name' => 'last_name', 'value' => 'Do')))
408
     * @exception 'SoapFault' -- The SOAP error, if any
409
     */
410
    function search_by_module($session, $search_string, $modules, $offset, $max_results,$assigned_user_id = '', $select_fields = array(), $unified_search_only = TRUE, $favorites = FALSE){
411
    	$GLOBALS['log']->info('Begin: SugarWebServiceImpl->search_by_module');
412
    	global  $beanList, $beanFiles;
413
    	global $sugar_config,$current_language;
414
415
    	$error = new SoapError();
416
    	$output_list = array();
417
    	if (!self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', '', '', '', $error)) {
418
    		$error->set_error('invalid_login');
419
    		$GLOBALS['log']->error('End: SugarWebServiceImpl->search_by_module - FAILED on checkSessionAndModuleAccess');
420
    		return;
421
    	}
422
    	global $current_user;
423
    	if($max_results > 0){
424
    		$sugar_config['list_max_entries_per_page'] = $max_results;
425
    	}
426
427
    	require_once('modules/Home/UnifiedSearchAdvanced.php');
428
    	require_once 'include/utils.php';
429
    	$usa = new UnifiedSearchAdvanced();
430
        if(!file_exists($cachefile = sugar_cached('modules/unified_search_modules.php'))) {
431
            $usa->buildCache();
432
        }
433
434
    	include $cachefile;
435
    	$modules_to_search = array();
436
    	$unified_search_modules['Users'] =   array('fields' => array());
437
438
    	$unified_search_modules['ProjectTask'] =   array('fields' => array());
439
440
        //If we are ignoring the unified search flag within the vardef we need to re-create the search fields.  This allows us to search
441
        //against a specific module even though it is not enabled for the unified search within the application.
442
        if( !$unified_search_only )
443
        {
444
            foreach ($modules as $singleModule)
445
            {
446
                if( !isset($unified_search_modules[$singleModule]) )
447
                {
448
                    $newSearchFields = array('fields' => self::$helperObject->generateUnifiedSearchFields($singleModule) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SoapHelperWebServices as the method generateUnifiedSearchFields() does only exist in the following sub-classes of SoapHelperWebServices: SugarWebServiceUtilv3_1, SugarWebServiceUtilv4, SugarWebServiceUtilv4_1. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
449
                    $unified_search_modules[$singleModule] = $newSearchFields;
450
                }
451
            }
452
        }
453
454
455
        foreach($unified_search_modules as $module=>$data) {
456
        	if (in_array($module, $modules)) {
457
            	$modules_to_search[$module] = $beanList[$module];
458
        	} // if
459
        } // foreach
460
461
        $GLOBALS['log']->info('SugarWebServiceImpl->search_by_module - search string = ' . $search_string);
462
463
    	if(!empty($search_string) && isset($search_string)) {
464
    		$search_string = trim($GLOBALS['db']->quote(securexss(from_html(clean_string($search_string, 'UNIFIED_SEARCH')))));
0 ignored issues
show
Bug introduced by
It seems like clean_string($search_string, 'UNIFIED_SEARCH') targeting clean_string() can also be of type false or null; however, from_html() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
465
        	foreach($modules_to_search as $name => $beanName) {
466
        		$where_clauses_array = array();
467
    			$unifiedSearchFields = array () ;
468
    			foreach ($unified_search_modules[$name]['fields'] as $field=>$def ) {
469
    				$unifiedSearchFields[$name] [ $field ] = $def ;
470
    				$unifiedSearchFields[$name] [ $field ]['value'] = $search_string;
471
    			}
472
473
    			require_once $beanFiles[$beanName] ;
474
    			$seed = new $beanName();
475
    			require_once 'include/SearchForm/SearchForm2.php' ;
476
    			if ($beanName == "User"
477
    			    || $beanName == "ProjectTask"
478
    			    ) {
479
    				if(!self::$helperObject->check_modules_access($current_user, $seed->module_dir, 'read')){
480
    					continue;
481
    				} // if
482
    				if(!$seed->ACLAccess('ListView')) {
483
    					continue;
484
    				} // if
485
    			}
486
487
    			if ($beanName != "User"
488
    			    && $beanName != "ProjectTask"
489
    			    ) {
490
    				$searchForm = new SearchForm ($seed, $name ) ;
491
492
    				$searchForm->setup(array ($name => array()) ,$unifiedSearchFields , '' , 'saved_views' /* hack to avoid setup doing further unwanted processing */ ) ;
0 ignored issues
show
Unused Code introduced by
The call to SearchForm::setup() has too many arguments starting with array($name => array()).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
493
    				$where_clauses = $searchForm->generateSearchWhere() ;
494
    				require_once 'include/SearchForm/SearchForm2.php' ;
495
    				$searchForm = new SearchForm ($seed, $name ) ;
496
497
    				$searchForm->setup(array ($name => array()) ,$unifiedSearchFields , '' , 'saved_views' /* hack to avoid setup doing further unwanted processing */ ) ;
0 ignored issues
show
Unused Code introduced by
The call to SearchForm::setup() has too many arguments starting with array($name => array()).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
498
    				$where_clauses = $searchForm->generateSearchWhere() ;
499
    				$emailQuery = false;
500
501
    				$where = '';
502
    				if (count($where_clauses) > 0 ) {
503
    					$where = '('. implode(' ) OR ( ', $where_clauses) . ')';
504
    				}
505
506
    				$mod_strings = return_module_language($current_language, $seed->module_dir);
507
508
    				if(count($select_fields) > 0)
509
    				    $filterFields = $select_fields;
510
    				else {
511
    				    if(file_exists('custom/modules/'.$seed->module_dir.'/metadata/listviewdefs.php'))
512
    					   require_once('custom/modules/'.$seed->module_dir.'/metadata/listviewdefs.php');
513
        				else
514
        					require_once('modules/'.$seed->module_dir.'/metadata/listviewdefs.php');
515
516
        				$filterFields = array();
517
        				foreach($listViewDefs[$seed->module_dir] as $colName => $param) {
518
        	                if(!empty($param['default']) && $param['default'] == true)
519
        	                    $filterFields[] = strtolower($colName);
520
        	            }
521
        	            if (!in_array('id', $filterFields))
522
        	            	$filterFields[] = 'id';
523
    				}
524
525
    				//Pull in any db fields used for the unified search query so the correct joins will be added
526
    				$selectOnlyQueryFields = array();
527
    				foreach ($unifiedSearchFields[$name] as $field => $def){
528
    				    if( isset($def['db_field']) && !in_array($field,$filterFields) ){
529
    				        $filterFields[] = $field;
530
    				        $selectOnlyQueryFields[] = $field;
531
    				    }
532
    				}
533
534
    	            //Add the assigned user filter if applicable
535
    	            if (!empty($assigned_user_id) && isset( $seed->field_defs['assigned_user_id']) ) {
536
    	               $ownerWhere = $seed->getOwnerWhere($assigned_user_id);
537
    	               $where = "($where) AND $ownerWhere";
538
    	            }
539
540
    	            if( $beanName == "Employee" )
541
    	            {
542
    	                $where = "($where) AND users.deleted = 0 AND users.is_group = 0 AND users.employee_status = 'Active'";
543
    	            }
544
545
    	            $list_params = array();
546
547
    				$ret_array = $seed->create_new_list_query('', $where, $filterFields, $list_params, 0, '', true, $seed, true);
548
    		        if(empty($params) or !is_array($params)) $params = array();
549
    		        if(!isset($params['custom_select'])) $params['custom_select'] = '';
550
    		        if(!isset($params['custom_from'])) $params['custom_from'] = '';
551
    		        if(!isset($params['custom_where'])) $params['custom_where'] = '';
552
    		        if(!isset($params['custom_order_by'])) $params['custom_order_by'] = '';
553
    				$main_query = $ret_array['select'] . $params['custom_select'] . $ret_array['from'] . $params['custom_from'] . $ret_array['where'] . $params['custom_where'] . $ret_array['order_by'] . $params['custom_order_by'];
554
    			} else {
555
    				if ($beanName == "User") {
556
    					$filterFields = array('id', 'user_name', 'first_name', 'last_name', 'email_address');
557
    					$main_query = "select users.id, ea.email_address, users.user_name, first_name, last_name from users ";
558
    					$main_query = $main_query . " LEFT JOIN email_addr_bean_rel eabl ON eabl.bean_module = '{$seed->module_dir}'
559
    LEFT JOIN email_addresses ea ON (ea.id = eabl.email_address_id) ";
560
    					$main_query = $main_query . "where ((users.first_name like '{$search_string}') or (users.last_name like '{$search_string}') or (users.user_name like '{$search_string}') or (ea.email_address like '{$search_string}')) and users.deleted = 0 and users.is_group = 0 and users.employee_status = 'Active'";
561
    				} // if
562
    				if ($beanName == "ProjectTask") {
563
    					$filterFields = array('id', 'name', 'project_id', 'project_name');
564
    					$main_query = "select {$seed->table_name}.project_task_id id,{$seed->table_name}.project_id, {$seed->table_name}.name, project.name project_name from {$seed->table_name} ";
565
    					$seed->add_team_security_where_clause($main_query);
566
    					$main_query .= "LEFT JOIN teams ON $seed->table_name.team_id=teams.id AND (teams.deleted=0) ";
567
    		            $main_query .= "LEFT JOIN project ON $seed->table_name.project_id = project.id ";
568
    		            $main_query .= "where {$seed->table_name}.name like '{$search_string}%'";
569
    				} // if
570
    			} // else
571
572
    			$GLOBALS['log']->info('SugarWebServiceImpl->search_by_module - query = ' . $main_query);
573
    	   		if($max_results < -1) {
574
    				$result = $seed->db->query($main_query);
575
    			}
576
    			else {
577
    				if($max_results == -1) {
578
    					$limit = $sugar_config['list_max_entries_per_page'];
579
    	            } else {
580
    	            	$limit = $max_results;
581
    	            }
582
    	            $result = $seed->db->limitQuery($main_query, $offset, $limit + 1);
583
    			}
584
585
    			$rowArray = array();
586
    			while($row = $seed->db->fetchByAssoc($result)) {
587
    				$nameValueArray = array();
588
    				foreach ($filterFields as $field) {
589
    				    if(in_array($field, $selectOnlyQueryFields))
590
    				        continue;
591
    					$nameValue = array();
592
    					if (isset($row[$field])) {
593
    						$nameValueArray[$field] = self::$helperObject->get_name_value($field, $row[$field]);
594
    					} // if
595
    				} // foreach
596
    				$rowArray[] = $nameValueArray;
597
    			} // while
598
    			$output_list[] = array('name' => $name, 'records' => $rowArray);
599
        	} // foreach
600
601
    	$GLOBALS['log']->info('End: SugarWebServiceImpl->search_by_module');
602
    	return array('entry_list'=>$output_list);
603
    	} // if
604
    	return array('entry_list'=>$output_list);
605
    } // fn
606
607
608
609
    /**
610
     * Get OAuth reqtest token
611
     */
612
    public function oauth_request_token()
613
    {
614
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->oauth_request_token');
615
        require_once "include/SugarOAuthServer.php";
616
        try {
617
	        $oauth = new SugarOAuthServer(rtrim($GLOBALS['sugar_config']['site_url'],'/').'/service/v4/rest.php');
618
	        $result = $oauth->requestToken()."&oauth_callback_confirmed=true&authorize_url=".$oauth->authURL();
619
        } catch(OAuthException $e) {
620
            $GLOBALS['log']->debug("OAUTH Exception: $e");
621
            $errorObject = new SoapError();
622
            $errorObject->set_error('invalid_login');
623
			self::$helperObject->setFaultObject($errorObject);
624
            $result = null;
625
        }
626
        $GLOBALS['log']->info('End: SugarWebServiceImpl->oauth_request_token');
627
        return $result;
628
    }
629
630
    /**
631
     * Get OAuth access token
632
     */
633
    public function oauth_access_token()
634
    {
635
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->oauth_access_token');
636
        require_once "include/SugarOAuthServer.php";
637
        try {
638
	        $oauth = new SugarOAuthServer();
639
	        $result = $oauth->accessToken();
640
        } catch(OAuthException $e) {
641
            $GLOBALS['log']->debug("OAUTH Exception: $e");
642
            $errorObject = new SoapError();
643
            $errorObject->set_error('invalid_login');
644
			self::$helperObject->setFaultObject($errorObject);
645
            $result = null;
646
        }
647
        $GLOBALS['log']->info('End: SugarWebServiceImpl->oauth_access_token');
648
        return $result;
649
    }
650
651
    public function oauth_access($session='')
652
    {
653
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->oauth_access');
654
        $error = new SoapError();
655
    	$output_list = array();
656
    	if (!self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', '', '', '', $error)) {
657
    		$error->set_error('invalid_login');
658
    		$GLOBALS['log']->info('End: SugarWebServiceImpl->oauth_access');
659
    		$result = $error;
660
    	} else {
661
            $result = array('id'=>session_id());
662
    	}
663
        $GLOBALS['log']->info('End: SugarWebServiceImpl->oauth_access');
664
        return $result;
665
    }
666
667
668
    /**
669
     * Get next job from the queue
670
     * @param string $session
671
     * @param string $clientid
672
     */
673
    public function job_queue_next($session, $clientid)
674
    {
675
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->job_queue_next');
676
        $error = new SoapError();
677
        if (! self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', '', 'read', 'no_access',  $error)) {
678
            $GLOBALS['log']->info('End: SugarWebServiceImpl->job_queue_next denied.');
679
            return;
680
        }
681
        require_once 'include/SugarQueue/SugarJobQueue.php';
682
        $queue = new SugarJobQueue();
683
        $job = $queue->nextJob($clientid);
684
        if(!empty($job)) {
685
            $jobid = $job->id;
686
        } else {
687
            $jobid = null;
688
        }
689
        $GLOBALS['log']->info('End: SugarWebServiceImpl->job_queue_next');
690
        return array("results" => $jobid);
691
    }
692
693
    /**
694
     * Run cleanup and schedule
695
     * @param string $session
696
     * @param string $clientid
697
     */
698
    public function job_queue_cycle($session, $clientid)
699
    {
700
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->job_queue_cycle');
701
        $error = new SoapError();
702
        if (! self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', '', 'read', 'no_access',  $error)) {
703
            $GLOBALS['log']->info('End: SugarWebServiceImpl->job_queue_cycle denied.');
704
            return;
705
        }
706
        require_once 'include/SugarQueue/SugarJobQueue.php';
707
        $queue = new SugarJobQueue();
708
        $queue->cleanup();
709
        $queue->runSchedulers();
710
        $GLOBALS['log']->info('End: SugarWebServiceImpl->job_queue_cycle');
711
        return array("results" => "ok");
712
    }
713
714
    /**
715
     * Run job from queue
716
     * @param string $session
717
     * @param string $jobid
718
     * @param string $clientid
719
     */
720
    public function job_queue_run($session, $jobid, $clientid)
721
    {
722
        $GLOBALS['log']->info('Begin: SugarWebServiceImpl->job_queue_run');
723
        $error = new SoapError();
724
        if (! self::$helperObject->checkSessionAndModuleAccess($session, 'invalid_session', '', 'read', 'no_access',  $error)) {
725
            $GLOBALS['log']->info('End: SugarWebServiceImpl->job_queue_run denied.');
726
            return;
727
        }
728
        $GLOBALS['log']->debug('Starting job $jobid execution as $clientid');
729
        require_once 'modules/SchedulersJobs/SchedulersJob.php';
730
        $result = SchedulersJob::runJobId($jobid, $clientid);
731
        $GLOBALS['log']->info('End: SugarWebServiceImpl->job_queue_run');
732
        if($result === true) {
733
            return array("results" => true);
734
        } else {
735
            return array("results" => false, "message" => $result);
736
        }
737
    }
738
}
739
740
SugarWebServiceImplv4::$helperObject = new SugarWebServiceUtilv4();
741