Completed
Push — master ( c75980...87f827 )
by Zhmayev
01:21
created

WSClient::entitiesRetrieve()   D

Complexity

Conditions 10
Paths 17

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 18
nc 17
nop 4

How to fix   Complexity   

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:

1
<?php
2
/**
3
 * Vtiger Web Services PHP Client Library
4
 *
5
 * The MIT License (MIT)
6
 *
7
 * Copyright (c) 2015, Zhmayev Yaroslav <[email protected]>
8
 *
9
 * Permission is hereby granted, free of charge, to any person obtaining a copy
10
 * of this software and associated documentation files (the "Software"), to deal
11
 * in the Software without restriction, including without limitation the rights
12
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
 * copies of the Software, and to permit persons to whom the Software is
14
 * furnished to do so, subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be included in
17
 * all copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
 * THE SOFTWARE.
26
 *
27
 * @author    Zhmayev Yaroslav <[email protected]>
28
 * @copyright 2015-2016 Zhmayev Yaroslav
29
 * @license   The MIT License (MIT)
30
 */
31
32
namespace Salaros\Vtiger\VTWSCLib;
33
34
use GuzzleHttp\Client;
35
use GuzzleHttp\Exception\RequestException;
36
37
/**
38
 * Vtiger Web Services PHP Client
39
 *
40
 * Class WSClient
41
 * @package Salaros\Vtiger\VTWSCLib
42
 */
43
class WSClient
44
{
45
    // HTTP Client instance
46
    protected $httpClient = null;
47
48
    // Service URL to which client connects to
49
    protected $serviceBaseURL = 'webservice.php';
50
51
    // Webservice login validity
52
    private $serviceServerTime = false;
53
    private $serviceExpireTime = false;
54
    private $serviceToken = false;
55
56
    // Webservice user credentials
57
    private $userName = false;
58
    private $accessKey = false;
59
60
    // Webservice login credentials
61
    private $userID = false;
62
    private $sessionName = false;
63
64
    // Vtiger CRM and WebServices API version
65
    private $apiVersion = false;
66
    private $vtigerVersion = false;
67
68
    // Last operation error information
69
    protected $lastErrorMessage = false;
70
71
    /**
72
     * Class constructor
73
     * @param string $url The URL of the remote WebServices server
74
     */
75
    public function __construct($url)
76
    {
77
        if (!preg_match('/^https?:\/\//i', $url)) {
78
            $url = sprintf('http://%s', $url);
79
        }
80
        if (strripos($url, '/') != (strlen($url)-1)) {
81
            $url .= '/';
82
        }
83
84
        // Gets target URL for WebServices API requests
85
        $this->httpClient = new Client([
86
            'base_uri' => $url
87
        ]);
88
    }
89
90
    /**
91
     * Check if server response contains an error, therefore the requested operation has failed
92
     * @access private
93
     * @param  array $jsonResult Server response object to check for errors
94
     * @return boolean  True if response object contains an error
95
     */
96
    private function checkForError(array $jsonResult) // TODO move checkForError body to sendHttpRequest method's body
97
    {
98
        if (isset($jsonResult['success']) && (bool)$jsonResult['success'] === true) {
99
            $this->lastErrorMessage = null;
100
            return false;
101
        }
102
103
        $this->lastErrorMessage = new WSClientError(
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Salaros\Vtiger\VTWS...esult['error']['code']) of type object<Salaros\Vtiger\VTWSCLib\WSClientError> is incompatible with the declared type boolean of property $lastErrorMessage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
104
            $jsonResult['error']['message'],
105
            $jsonResult['error']['code']
106
        );
107
108
        return true;
109
    }
110
111
    /**
112
     * Checks and performs a login operation if requried and repeats login if needed
113
     * @access private
114
     */
115
    private function checkLogin()
116
    {
117
        if (time() <= $this->serviceExpireTime) {
118
            return;
119
        }
120
        $this->login($this->userName, $this->accessKey);
0 ignored issues
show
Documentation introduced by
$this->userName is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->accessKey is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
121
    }
122
123
    /**
124
     * Sends HTTP request to VTiger web service API endpoint
125
     * @access private
126
     * @param  array $requestData HTTP request data
127
     * @param  string $method HTTP request method (GET, POST etc)
128
     * @return array Returns request result object (null in case of failure)
129
     */
130
    private function sendHttpRequest(array $requestData, $method = 'POST')
131
    {
132
        try {
133
            switch ($method) {
134
                case 'GET':
135
                    $response = $this->httpClient->get($this->serviceBaseURL, ['query' => $requestData]);
136
                    break;
137
                case 'POST':
138
                    $response = $this->httpClient->post($this->serviceBaseURL, ['form_params' => $requestData]);
139
                    break;
140
                default:
141
                    $this->lastErrorMessage = new WSClientError("Unknown request type {$method}");
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Salaros\Vtiger\VTWS...equest type {$method}") of type object<Salaros\Vtiger\VTWSCLib\WSClientError> is incompatible with the declared type boolean of property $lastErrorMessage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
142
                    return null;
143
            }
144
        } catch (RequestException $ex) {
145
            $this->lastError = new WSClientError(
0 ignored issues
show
Bug introduced by
The property lastError does not seem to exist. Did you mean lastErrorMessage?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
146
                $ex->getMessage(),
147
                $ex->getCode()
148
            );
149
            return null;
150
        }
151
152
        $jsonRaw = $response->getBody();
153
        $jsonObj = json_decode($jsonRaw, true);
154
155
        return (!is_array($jsonObj) || $this->checkForError($jsonObj))
156
            ? null
157
            : $jsonObj['result'];
158
    }
159
160
    /**
161
     * Gets a challenge token from the server and stores for future requests
162
     * @access private
163
     * @param  string $username VTiger user name
164
     * @return bool Returns false in case of failure
165
     */
166
    private function passChallenge($username)
167
    {
168
        $getdata = [
169
            'operation' => 'getchallenge',
170
            'username'  => $username
171
        ];
172
        $result = $this->sendHttpRequest($getdata, 'GET');
173
        
174
        if (!is_array($result) || !isset($result['token'])) {
175
            return false;
176
        }
177
178
        $this->serviceServerTime = $result['serverTime'];
179
        $this->serviceExpireTime = $result['expireTime'];
180
        $this->serviceToken = $result['token'];
181
182
        return true;
183
    }
184
185
    /**
186
     * Login to the server using username and VTiger access key token
187
     * @access public
188
     * @param  string $username VTiger user name
189
     * @param  string $accessKey VTiger access key token (visible on user profile/settings page)
190
     * @return boolean Returns true if login operation has been successful
191
     */
192
    public function login($username, $accessKey)
193
    {
194
        // Do the challenge before loggin in
195
        if ($this->passChallenge($username) === false) {
196
            return false;
197
        }
198
199
        $postdata = [
200
            'operation' => 'login',
201
            'username'  => $username,
202
            'accessKey' => md5($this->serviceToken.$accessKey)
203
        ];
204
205
        $result = $this->sendHttpRequest($postdata);
206
        if (!$result || !is_array($result)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
207
            return false;
208
        }
209
210
        // Backuping logged in user credentials
211
        $this->userName = $username;
0 ignored issues
show
Documentation Bug introduced by
The property $userName was declared of type boolean, but $username is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
212
        $this->accessKey = $accessKey;
0 ignored issues
show
Documentation Bug introduced by
The property $accessKey was declared of type boolean, but $accessKey is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
213
214
        // Session data
215
        $this->sessionName = $result['sessionName'];
216
        $this->userID = $result['userId'];
217
218
        // Vtiger CRM and WebServices API version
219
        $this->apiVersion = $result['version'];
220
        $this->vtigerVersion = $result['vtigerVersion'];
221
222
        return true;
223
    }
224
225
    /**
226
     * Allows you to login using username and password instead of access key (works on some VTige forks)
227
     * @access public
228
     * @param  string $username VTiger user name
229
     * @param  string $password VTiger password (used to access CRM using the standard login page)
230
     * @param  string $accessKey This parameter will be filled with user's VTiger access key
231
     * @return boolean  Returns true if login operation has been successful
232
     */
233
    public function loginPassword($username, $password, &$accessKey = null)
234
    {
235
        // Do the challenge before loggin in
236
        if ($this->passChallenge($username) === false) {
237
            return false;
238
        }
239
240
        $postdata = [
241
            'operation' => 'login_pwd',
242
            'username' => $username,
243
            'password' => $password
244
        ];
245
246
        $result = $this->sendHttpRequest($postdata);
247
        if (!$result || !is_array($result) || count($result) !== 1) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
248
            return false;
249
        }
250
251
        $accessKey = array_key_exists('accesskey', $result)
252
            ? $result['accesskey']
253
            : $result[0];
254
255
        return $this->login($username, $accessKey);
256
    }
257
258
    /**
259
     * Gets last operation error, if any
260
     * @access public
261
     * @return WSClientError The error object
262
     */
263
    public function getLastError()
264
    {
265
        return $this->lastErrorMessage;
266
    }
267
268
    /**
269
     * Returns the client library version.
270
     * @access public
271
     * @return string Client library version
272
     */
273
    public function getVersion()
274
    {
275
        global $wsclient_version;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
276
        return $wsclient_version;
277
    }
278
279
    /**
280
     * Lists all the Vtiger entity types available through the API
281
     * @access public
282
     * @return array List of entity types
283
     */
284
    public function getTypes()
285
    {
286
        // Perform re-login if required.
287
        $this->checkLogin();
288
289
        $getdata = [
290
            'operation' => 'listtypes',
291
            'sessionName'  => $this->sessionName
292
        ];
293
294
        $result = $this->sendHttpRequest($getdata, 'GET');
295
        $modules = $result['types'];
296
297
        $result = array();
298
        foreach ($modules as $moduleName) {
299
            $result[$moduleName] = ['name' => $moduleName];
300
        }
301
        return $result;
302
    }
303
304
    /**
305
     * Get the type information about a given VTiger entity type.
306
     * @access public
307
     * @param  string $moduleName Name of the module / entity type
308
     * @return array  Result object
309
     */
310
    public function getType($moduleName)
311
    {
312
        // Perform re-login if required.
313
        $this->checkLogin();
314
315
        $getdata = [
316
            'operation' => 'describe',
317
            'sessionName'  => $this->sessionName,
318
            'elementType' => $moduleName
319
        ];
320
321
        return $this->sendHttpRequest($getdata, 'GET');
322
    }
323
324
    /**
325
     * Gets the entity ID prepended with module / entity type ID
326
     * @access private
327
     * @param  string       $moduleName   Name of the module / entity type
328
     * @param  string       $entityID     Numeric entity ID
329
     * @return boolean|string Returns false if it is not possible to retrieve module / entity type ID
330
     */
331
    private function getTypedID($moduleName, $entityID)
332
    {
333
        if (stripos((string)$entityID, 'x') !== false) {
334
            return $entityID;
335
        }
336
337
        $type = $this->getType($moduleName);
338
        if (!$type || !array_key_exists('idPrefix', $type)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
339
            $errorMessage = sprintf("The following module is not installed: %s", $moduleName);
340
            $this->lastErrorMessage = new WSClientError($errorMessage);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Salaros\Vtiger\VTWS...entError($errorMessage) of type object<Salaros\Vtiger\VTWSCLib\WSClientError> is incompatible with the declared type boolean of property $lastErrorMessage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
341
            return false;
342
        }
343
344
        return "{$type['idPrefix']}x{$entityID}";
345
    }
346
347
    /**
348
     * Invokes custom operation (defined in vtiger_ws_operation table)
349
     * @access public
350
     * @param  string  $operation  Name of the webservice to invoke
351
     * @param  array   [$params = null] Parameter values to operation
352
     * @param  string  [$method   = 'POST'] HTTP request method (GET, POST etc)
353
     * @return array Result object
354
     */
355
    public function invokeOperation($operation, array $params = null, $method = 'POST') // TODO check if params is an assoc array
356
    {
357
        // Perform re-login if required
358
        $this->checkLogin();
359
360
        $requestData = [
361
            'operation' => $operation,
362
            'sessionName' => $this->sessionName
363
        ];
364
365
        if (!empty($params) && is_array($params)) {
366
            $requestData = array_merge($requestData, $params);
367
        }
368
369
        return $this->sendHttpRequest($requestData, $method);
370
    }
371
372
    /**
373
     * VTiger provides a simple query language for fetching data.
374
     * This language is quite similar to select queries in SQL.
375
     * There are limitations, the queries work on a single Module,
376
     * embedded queries are not supported, and does not support joins.
377
     * But this is still a powerful way of getting data from Vtiger.
378
     * Query always limits its output to 100 records,
379
     * Client application can use limit operator to get different records.
380
     * @access public
381
     * @param  string $query SQL-like expression
382
     * @return array  Query results
383
     */
384
    public function query($query)
385
    {
386
        // Perform re-login if required.
387
        $this->checkLogin();
388
389
        // Make sure the query ends with ;
390
        $query = (strripos($query, ';') != strlen($query)-1)
391
            ? trim($query .= ';')
392
            : trim($query);
393
394
        $getdata = [
395
            'operation' => 'query',
396
            'sessionName' => $this->sessionName,
397
            'query' => $query
398
        ];
399
400
        return $this->sendHttpRequest($getdata, 'GET');
401
    }
402
403
    /**
404
     * Retrieves an entity by ID
405
     * @param  string $moduleName The name of the module / entity type
406
     * @param  string $entityID The ID of the entity to retrieve
407
     * @return boolean  Entity data
408
     */
409 View Code Duplication
    public function entityRetrieveByID($moduleName, $entityID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
410
    {
411
        // Perform re-login if required.
412
        $this->checkLogin();
413
414
        // Preprend so-called moduleid if needed
415
        $entityID = $this->getTypedID($moduleName, $entityID);
416
417
        $getdata = [
418
            'operation' => 'retrieve',
419
            'sessionName' => $this->sessionName,
420
            'id' => $entityID
421
        ];
422
423
        return $this->sendHttpRequest($getdata, 'GET');
424
    }
425
426
    /**
427
     * Uses VTiger queries to retrieve the entity matching a list of constraints
428
     * @param  string  $moduleName   The name of the module / entity type
429
     * @param  array   $params  Data used to find a matching entry
430
     * @return array   $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
431
     * @return int  The matching record
432
     */
433
    public function entityRetrieve($moduleName, array $params, array $select = [])
434
    {
435
        $records = $this->entitiesRetrieve($moduleName, $params, $select, 1);
436
        if (false === $records || !isset($records[0])) {
437
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Salaros\Vtiger\VTWSCLib\WSClient::entityRetrieve of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
438
        }
439
        return $records[0];
440
    }
441
442
    /**
443
     * Uses VTiger queries to retrieve the ID of the entity matching a list of constraints
444
     * @param  string $moduleName   The name of the module / entity type
445
     * @param  array   $params  Data used to find a matching entry
446
     * @return int  Numeric ID
447
     */
448
    public function entityRetrieveID($moduleName, array $params) // TODO check if params is an assoc array
449
    {
450
        $record = $this->entityRetrieve($moduleName, $params, ['id']);
451
        if (false === $record || !isset($record['id'])) {
452
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Salaros\Vtiger\VTWSCLib\WSClient::entityRetrieveID of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
453
        }
454
455
        $entityID = $record['id'];
456
        $entityIDParts = explode('x', $entityID, 2);
457
        return (is_array($entityIDParts) && count($entityIDParts) === 2)
458
            ? $entityIDParts[1]
459
            : -1;
460
    }
461
462
    /**
463
     * Creates an entity for the giving module
464
     * @param  string $moduleName   Name of the module / entity type for which the entry has to be created
465
     * @param  array $params Entity data
466
     * @return array  Entity creation results
467
     */
468
    public function entityCreate($moduleName, array $params) // TODO check if params is an assoc array
469
    {
470
        // Perform re-login if required.
471
        $this->checkLogin();
472
473
        // Assign record to logged in user if not specified
474
        if (!isset($params['assigned_user_id'])) {
475
            $params['assigned_user_id'] = $this->userID;
476
        }
477
478
        $postdata = [
479
            'operation'   => 'create',
480
            'sessionName' => $this->sessionName,
481
            'elementType' => $moduleName,
482
            'element'     => json_encode($params)
483
        ];
484
485
        return $this->sendHttpRequest($postdata);
486
    }
487
488
    /**
489
     * Updates an entity
490
     * @param  string $moduleName   The name of the module / entity type
491
     * @param  array $params Entity data
492
     * @return array  Entity update result
493
     */
494
    public function entityUpdate($moduleName, array $params) // TODO check if params is an assoc array
495
    {
496
        // Perform re-login if required.
497
        $this->checkLogin();
498
499
        // Assign record to logged in user if not specified
500
        if (!isset($params['assigned_user_id'])) {
501
            $params['assigned_user_id'] = $this->userID;
502
        }
503
504
        // TODO implement the case when no ID is given
505
        if (array_key_exists('id', $params)) {
506
            $data = $this->entityRetrieveByID($moduleName, $params['id']);
507
            if ($data !== false && is_array($data)) {
508
                $entityID = $data['id'];
509
                $params = array_merge(
510
                    $data,      // needed to provide mandatory field values
511
                    $params,    // updated data override
512
                    ['id'=>$entityID] // fixing id, might be useful when non <moduleid>x<id> one was specified
513
                );
514
            }
515
        }
516
517
        $postdata = [
518
                'operation'   => 'update',
519
                'sessionName' => $this->sessionName,
520
                'elementType' => $moduleName,
521
                'element'     => json_encode($params)
522
        ];
523
524
        return $this->sendHttpRequest($postdata);
525
    }
526
527
    /**
528
     * Provides entity removal functionality
529
     * @param  string $moduleName   The name of the module / entity type
530
     * @param  string $entityID The ID of the entity to delete
531
     * @return array  Removal status object
532
     */
533 View Code Duplication
    public function entityDelete($moduleName, $entityID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
534
    {
535
        // Perform re-login if required.
536
        $this->checkLogin();
537
538
        // Preprend so-called moduleid if needed
539
        $entityID = $this->getTypedID($moduleName, $entityID);
540
541
        $postdata = [
542
            'operation' => 'delete',
543
            'sessionName' => $this->sessionName,
544
            'id' => $entityID
545
        ];
546
547
        return $this->sendHttpRequest($postdata);
548
    }
549
550
    /**
551
     * Retrieves multiple records using module name and a set of constraints
552
     * @param  string   $moduleName  The name of the module / entity type
553
     * @param  array    $params  Data used to find the matching entries
554
     * @return array    $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
555
     * @return int      $limit  limit the list of entries to N records (acts like LIMIT in SQL)
556
     * @return bool|array  The array containing the matching entries or false if nothing was found
557
     */
558
    public function entitiesRetrieve($moduleName, array $params, array $select = [], $limit = 0)
559
    {
560
        // Perform re-login if required.
561
        $this->checkLogin();
562
563
        if (empty($params) || !is_array($params) || !$this->isAssocArray($params)) {
564
            $errorMessage = "You have to specify at least on parameter (prop => value) in order to retrieve entity ID";
565
            $this->lastErrorMessage = new WSClientError($errorMessage);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Salaros\Vtiger\VTWS...entError($errorMessage) of type object<Salaros\Vtiger\VTWSCLib\WSClientError> is incompatible with the declared type boolean of property $lastErrorMessage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
566
            return false;
567
        }
568
569
        // Build the query
570
        $criteria = array();
571
        $select=(empty($select)) ? '*' : implode(',', $select);
572
        $query=sprintf("SELECT %s FROM $moduleName WHERE ", $select);
573
        foreach ($params as $param => $value) {
574
            $criteria[] = "{$param} LIKE '{$value}'";
575
        }
576
577
        $query.=implode(" AND ", $criteria);
578
        if (intval($limit) > 0) {
579
            $query.=sprintf(" LIMIT %s", intval($limit));
580
        }
581
582
        $records = $this->query($query);
583
        if (false === $records || !is_array($records) || (count($records) <= 0)) {
584
            return false;
585
        }
586
587
        return $records;
588
    }
589
590
    /**
591
     * Sync will return a sync result object containing details of changes after modifiedTime
592
     * @param  int [$modifiedTime = null]    The date of the first change
593
     * @param  string [$moduleName = null]   The name of the module / entity type
594
     * @return array  Sync result object
595
     */
596
    public function entitiesSync($modifiedTime = null, $moduleName = null)
597
    {
598
        // Perform re-login if required.
599
        $this->checkLogin();
600
601
        $modifiedTime = (empty($modifiedTime))
602
            ? strtotime('today midnight')
603
            : intval($modifiedTime);
604
605
        $requestData = [
606
            'operation' => 'sync',
607
            'sessionName' => $this->sessionName,
608
            'modifiedTime' => $modifiedTime
609
        ];
610
611
        if (!empty($moduleName)) {
612
            $requestData['elementType'] = $moduleName;
613
        }
614
615
        return $this->sendHttpRequest($requestData, true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
616
    }
617
618
    /**
619
     * A helper method, used to check in an array is associative or not
620
     * @param  string  Array to test
621
     * @return bool  Returns true in a given array is associative and false if it's not
622
     */
623
    private function isAssocArray(array $array)
624
    {
625
        foreach (array_keys($array) as $key) {
626
            if (!is_int($key)) {
627
                return true;
628
            }
629
        }
630
        return false;
631
    }
632
}
633