Completed
Push — master ( a67f37...fac198 )
by Zhmayev
01:42
created

WSClient::getTypes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 0
1
<?php
2
3
/**
4
 * Vtiger Web Services PHP Client Library
5
 *
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015, Zhmayev Yaroslav <[email protected]>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in
18
 * all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
 * THE SOFTWARE.
27
 *
28
 * @author    Zhmayev Yaroslav <[email protected]>
29
 * @copyright 2015-2016 Zhmayev Yaroslav
30
 * @license   The MIT License (MIT)
31
 */
32
33
namespace Salaros\Vtiger\VTWSCLib;
34
35
use GuzzleHttp\Client;
36
use GuzzleHttp\Exception\RequestException;
37
38
/**
39
 * Vtiger Web Services PHP Client
40
 *
41
 * Class WSClient
42
 * @package Salaros\Vtiger\VTWSCLib
43
 */
44
class WSClient
45
{
46
    // HTTP Client instance
47
    protected $httpClient = null;
48
49
    // Service URL to which client connects to
50
    protected $serviceBaseURL = 'webservice.php';
51
52
    // Webservice login validity
53
    private $serviceServerTime = false;
54
    private $serviceExpireTime = false;
55
    private $serviceToken = false;
56
57
    // Webservice user credentials
58
    private $userName = false;
59
    private $accessKey = false;
60
61
    // Webservice login credentials
62
    private $userID = false;
63
    private $sessionName = false;
64
65
    // Vtiger CRM and WebServices API version
66
    private $apiVersion = false;
67
    private $vtigerVersion = false;
68
69
    // Last operation error information
70
    protected $lastErrorMessage = null;
71
72
    /**
73
     * Class constructor
74
     * @param string $url The URL of the remote WebServices server
75
     */
76
    public function __construct($url)
77
    {
78
        if (!preg_match('/^https?:\/\//i', $url)) {
79
            $url = sprintf('http://%s', $url);
80
        }
81
        if (strripos($url, '/') != (strlen($url)-1)) {
82
            $url .= '/';
83
        }
84
85
        // Gets target URL for WebServices API requests
86
        $this->httpClient = new Client([
87
            'base_uri' => $url
88
        ]);
89
    }
90
91
    /**
92
     * Check if server response contains an error, therefore the requested operation has failed
93
     * @access private
94
     * @param  array $jsonResult Server response object to check for errors
95
     * @return boolean  True if response object contains an error
96
     */
97
    private function checkForError(array $jsonResult)
98
    {
99
        if (isset($jsonResult['success']) && (bool)$jsonResult['success'] === true) {
100
            $this->lastErrorMessage = null;
101
            return false;
102
        }
103
104
        $this->lastErrorMessage = new WSClientError(
105
            $jsonResult['error']['message'],
106
            $jsonResult['error']['code']
107
        );
108
109
        return true;
110
    }
111
112
    /**
113
     * Checks and performs a login operation if requried and repeats login if needed
114
     * @access private
115
     */
116
    private function checkLogin()
117
    {
118
        if (time() <= $this->serviceExpireTime) {
119
            return;
120
        }
121
        $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...
122
    }
123
124
    /**
125
     * Sends HTTP request to VTiger web service API endpoint
126
     * @access private
127
     * @param  array $requestData HTTP request data
128
     * @param  string $method HTTP request method (GET, POST etc)
129
     * @return array Returns request result object (null in case of failure)
130
     */
131
    private function sendHttpRequest(array $requestData, $method = 'POST')
132
    {
133
        try {
134
            switch ($method) {
135
                case 'GET':
136
                    $response = $this->httpClient->get($this->serviceBaseURL, ['query' => $requestData]);
137
                    break;
138
                case 'POST':
139
                    $response = $this->httpClient->post($this->serviceBaseURL, ['form_params' => $requestData]);
140
                    break;
141
                default:
142
                    $this->lastErrorMessage = new WSClientError("Unknown request type {$method}");
143
                    return null;
144
            }
145
        } catch (RequestException $ex) {
146
            $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...
147
                $ex->getMessage(),
148
                $ex->getCode()
149
            );
150
            return null;
151
        }
152
153
        $jsonRaw = $response->getBody();
154
        $jsonObj = json_decode($jsonRaw, true);
155
156
        return (!is_array($jsonObj) || $this->checkForError($jsonObj))
157
            ? null
158
            : $jsonObj['result'];
159
    }
160
161
    /**
162
     * Gets a challenge token from the server and stores for future requests
163
     * @access private
164
     * @param  string $username VTiger user name
165
     * @return booleanReturns false in case of failure
166
     */
167
    private function passChallenge($username)
168
    {
169
        $getdata = [
170
            'operation' => 'getchallenge',
171
            'username'  => $username
172
        ];
173
        $result = $this->sendHttpRequest($getdata, 'GET');
174
        
175
        if (!is_array($result) || !isset($result['token'])) {
176
            return false;
177
        }
178
179
        $this->serviceServerTime = $result['serverTime'];
180
        $this->serviceExpireTime = $result['expireTime'];
181
        $this->serviceToken = $result['token'];
182
183
        return true;
184
    }
185
186
    /**
187
     * Login to the server using username and VTiger access key token
188
     * @access public
189
     * @param  string $username VTiger user name
190
     * @param  string $accessKey VTiger access key token (visible on user profile/settings page)
191
     * @return boolean Returns true if login operation has been successful
192
     */
193
    public function login($username, $accessKey)
194
    {
195
        // Do the challenge before loggin in
196
        if ($this->passChallenge($username) === false) {
197
            return false;
198
        }
199
200
        $postdata = [
201
            'operation' => 'login',
202
            'username'  => $username,
203
            'accessKey' => md5($this->serviceToken.$accessKey)
204
        ];
205
206
        $result = $this->sendHttpRequest($postdata);
207
        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...
208
            return false;
209
        }
210
211
        // Backuping logged in user credentials
212
        $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...
213
        $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...
214
215
        // Session data
216
        $this->sessionName = $result['sessionName'];
217
        $this->userID = $result['userId'];
218
219
        // Vtiger CRM and WebServices API version
220
        $this->apiVersion = $result['version'];
221
        $this->vtigerVersion = $result['vtigerVersion'];
222
223
        return true;
224
    }
225
226
    /**
227
     * Allows you to login using username and password instead of access key (works on some VTige forks)
228
     * @access public
229
     * @param  string $username VTiger user name
230
     * @param  string $password VTiger password (used to access CRM using the standard login page)
231
     * @param  string $accessKey This parameter will be filled with user's VTiger access key
232
     * @return boolean  Returns true if login operation has been successful
233
     */
234
    public function loginPassword($username, $password, &$accessKey = null)
235
    {
236
        // Do the challenge before loggin in
237
        if ($this->passChallenge($username) === false) {
238
            return false;
239
        }
240
241
        $postdata = [
242
            'operation' => 'login_pwd',
243
            'username' => $username,
244
            'password' => $password
245
        ];
246
247
        $result = $this->sendHttpRequest($postdata);
248
        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...
249
            return false;
250
        }
251
252
        $accessKey = array_key_exists('accesskey', $result)
253
            ? $result['accesskey']
254
            : $result[0];
255
256
        return $this->login($username, $accessKey);
257
    }
258
259
    /**
260
     * Gets last operation error, if any
261
     * @access public
262
     * @return WSClientError The error object
263
     */
264
    public function getLastError()
265
    {
266
        return $this->lastErrorMessage;
267
    }
268
269
    /**
270
     * Returns the client library version.
271
     * @access public
272
     * @return string Client library version
273
     */
274
    public function getVersion()
275
    {
276
        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...
277
        return $wsclient_version;
278
    }
279
280
    /**
281
     * Lists all the Vtiger entity types available through the API
282
     * @access public
283
     * @return array List of entity types
284
     */
285
    public function getTypes()
286
    {
287
        // Perform re-login if required.
288
        $this->checkLogin();
289
290
        $getdata = [
291
            'operation' => 'listtypes',
292
            'sessionName'  => $this->sessionName
293
        ];
294
295
        $result = $this->sendHttpRequest($getdata, 'GET');
296
        $modules = $result['types'];
297
298
        $result = array();
299
        foreach ($modules as $moduleName) {
300
            $result[$moduleName] = ['name' => $moduleName];
301
        }
302
        return $result;
303
    }
304
305
    /**
306
     * Get the type information about a given VTiger entity type.
307
     * @access public
308
     * @param  string $moduleName Name of the module / entity type
309
     * @return array  Result object
310
     */
311
    public function getType($moduleName)
312
    {
313
        // Perform re-login if required.
314
        $this->checkLogin();
315
316
        $getdata = [
317
            'operation' => 'describe',
318
            'sessionName'  => $this->sessionName,
319
            'elementType' => $moduleName
320
        ];
321
322
        return $this->sendHttpRequest($getdata, 'GET');
323
    }
324
325
    /**
326
     * Gets the entity ID prepended with module / entity type ID
327
     * @access private
328
     * @param  string       $moduleName   Name of the module / entity type
329
     * @param  string       $entityID     Numeric entity ID
330
     * @return boolean|string Returns false if it is not possible to retrieve module / entity type ID
331
     */
332
    private function getTypedID($moduleName, $entityID)
333
    {
334
        if (stripos((string)$entityID, 'x') !== false) {
335
            return $entityID;
336
        }
337
338
        $type = $this->getType($moduleName);
339
        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...
340
            $errorMessage = sprintf("The following module is not installed: %s", $moduleName);
341
            $this->lastErrorMessage = new WSClientError($errorMessage);
342
            return false;
343
        }
344
345
        return "{$type['idPrefix']}x{$entityID}";
346
    }
347
348
    /**
349
     * Invokes custom operation (defined in vtiger_ws_operation table)
350
     * @access public
351
     * @param  string  $operation  Name of the webservice to invoke
352
     * @param  array   [$params = null] Parameter values to operation
353
     * @param  string  [$method   = 'POST'] HTTP request method (GET, POST etc)
354
     * @return array Result object
355
     */
356
    public function invokeOperation($operation, array $params = null, $method = 'POST')
357
    {
358 View Code Duplication
        if (!empty($params) || !is_array($params) || !$this->isAssocArray($params)) {
359
            $this->lastErrorMessage = new WSClientError(
360
                "You have to specified a list of operation parameters, 
361
                but apparently it's not an associative array ('prop' => value), you must fix it!"
362
            );
363
            return false;
364
        }
365
366
        // Perform re-login if required
367
        $this->checkLogin();
368
369
        $requestData = [
370
            'operation' => $operation,
371
            'sessionName' => $this->sessionName
372
        ];
373
374
        if (!empty($params) && is_array($params)) {
375
            $requestData = array_merge($requestData, $params);
376
        }
377
378
        return $this->sendHttpRequest($requestData, $method);
379
    }
380
381
    /**
382
     * VTiger provides a simple query language for fetching data.
383
     * This language is quite similar to select queries in SQL.
384
     * There are limitations, the queries work on a single Module,
385
     * embedded queries are not supported, and does not support joins.
386
     * But this is still a powerful way of getting data from Vtiger.
387
     * Query always limits its output to 100 records,
388
     * Client application can use limit operator to get different records.
389
     * @access public
390
     * @param  string $query SQL-like expression
391
     * @return array  Query results
392
     */
393
    public function query($query)
394
    {
395
        // Perform re-login if required.
396
        $this->checkLogin();
397
398
        // Make sure the query ends with ;
399
        $query = (strripos($query, ';') != strlen($query)-1)
400
            ? trim($query .= ';')
401
            : trim($query);
402
403
        $getdata = [
404
            'operation' => 'query',
405
            'sessionName' => $this->sessionName,
406
            'query' => $query
407
        ];
408
409
        return $this->sendHttpRequest($getdata, 'GET');
410
    }
411
412
    /**
413
     * Retrieves an entity by ID
414
     * @param  string $moduleName The name of the module / entity type
415
     * @param  string $entityID The ID of the entity to retrieve
416
     * @return boolean  Entity data
417
     */
418 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...
419
    {
420
        // Perform re-login if required.
421
        $this->checkLogin();
422
423
        // Preprend so-called moduleid if needed
424
        $entityID = $this->getTypedID($moduleName, $entityID);
425
426
        $getdata = [
427
            'operation' => 'retrieve',
428
            'sessionName' => $this->sessionName,
429
            'id' => $entityID
430
        ];
431
432
        return $this->sendHttpRequest($getdata, 'GET');
433
    }
434
435
    /**
436
     * Uses VTiger queries to retrieve the entity matching a list of constraints
437
     * @param  string  $moduleName   The name of the module / entity type
438
     * @param  array   $params  Data used to find a matching entry
439
     * @return array   $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
440
     * @return int  The matching record
441
     */
442
    public function entityRetrieve($moduleName, array $params, array $select = [])
443
    {
444
        $records = $this->entitiesRetrieve($moduleName, $params, $select, 1);
445
        if (false === $records || !isset($records[0])) {
446
            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...
447
        }
448
        return $records[0];
449
    }
450
451
    /**
452
     * Uses VTiger queries to retrieve the ID of the entity matching a list of constraints
453
     * @param  string $moduleName   The name of the module / entity type
454
     * @param  array   $params  Data used to find a matching entry
455
     * @return int  Numeric ID
456
     */
457
    public function entityRetrieveID($moduleName, array $params)
458
    {
459
        $record = $this->entityRetrieve($moduleName, $params, ['id']);
460
        if (false === $record || !isset($record['id'])) {
461
            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...
462
        }
463
464
        $entityID = $record['id'];
465
        $entityIDParts = explode('x', $entityID, 2);
466
        return (is_array($entityIDParts) && count($entityIDParts) === 2)
467
            ? $entityIDParts[1]
468
            : -1;
469
    }
470
471
    /**
472
     * Creates an entity for the giving module
473
     * @param  string $moduleName   Name of the module / entity type for which the entry has to be created
474
     * @param  array $params Entity data
475
     * @return array  Entity creation results
476
     */
477
    public function entityCreate($moduleName, array $params)
478
    {
479
        if (!$this->checkParams($params, 'be able to create an entity')) {
480
            return false;
481
        }
482
483
        // Perform re-login if required.
484
        $this->checkLogin();
485
486
        // Assign record to logged in user if not specified
487
        if (!isset($params['assigned_user_id'])) {
488
            $params['assigned_user_id'] = $this->userID;
489
        }
490
491
        $postdata = [
492
            'operation'   => 'create',
493
            'sessionName' => $this->sessionName,
494
            'elementType' => $moduleName,
495
            'element'     => json_encode($params)
496
        ];
497
498
        return $this->sendHttpRequest($postdata);
499
    }
500
501
    /**
502
     * Updates an entity
503
     * @param  string $moduleName   The name of the module / entity type
504
     * @param  array $params Entity data
505
     * @return array  Entity update result
506
     */
507
    public function entityUpdate($moduleName, array $params)
508
    {
509
        if (!$this->checkParams($params, 'be able to update the entity(ies)')) {
510
            return false;
511
        }
512
513
        // Perform re-login if required.
514
        $this->checkLogin();
515
516
        // Assign record to logged in user if not specified
517
        if (!isset($params['assigned_user_id'])) {
518
            $params['assigned_user_id'] = $this->userID;
519
        }
520
521
        // TODO implement the case when no ID is given
522
        if (array_key_exists('id', $params)) {
523
            $data = $this->entityRetrieveByID($moduleName, $params['id']);
524
            if ($data !== false && is_array($data)) {
525
                $entityID = $data['id'];
526
                $params = array_merge(
527
                    $data,      // needed to provide mandatory field values
528
                    $params,    // updated data override
529
                    ['id'=>$entityID] // fixing id, might be useful when non <moduleid>x<id> one was specified
530
                );
531
            }
532
        }
533
534
        $postdata = [
535
                'operation'   => 'update',
536
                'sessionName' => $this->sessionName,
537
                'elementType' => $moduleName,
538
                'element'     => json_encode($params)
539
        ];
540
541
        return $this->sendHttpRequest($postdata);
542
    }
543
544
    /**
545
     * Provides entity removal functionality
546
     * @param  string $moduleName   The name of the module / entity type
547
     * @param  string $entityID The ID of the entity to delete
548
     * @return array  Removal status object
549
     */
550 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...
551
    {
552
        // Perform re-login if required.
553
        $this->checkLogin();
554
555
        // Preprend so-called moduleid if needed
556
        $entityID = $this->getTypedID($moduleName, $entityID);
557
558
        $postdata = [
559
            'operation' => 'delete',
560
            'sessionName' => $this->sessionName,
561
            'id' => $entityID
562
        ];
563
564
        return $this->sendHttpRequest($postdata);
565
    }
566
567
    /**
568
     * Retrieves multiple records using module name and a set of constraints
569
     * @param  string   $moduleName  The name of the module / entity type
570
     * @param  array    $params  Data used to find matching entries
571
     * @return array    $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
572
     * @return int      $limit  limit the list of entries to N records (acts like LIMIT in SQL)
573
     * @return bool|array  The array containing matching entries or false if nothing was found
574
     */
575
    public function entitiesRetrieve($moduleName, array $params, array $select = [], $limit = 0)
576
    {
577
        if (!$this->checkParams($params, 'be able to retrieve entity(ies)')) {
578
            return false;
579
        }
580
581
        // Perform re-login if required.
582
        $this->checkLogin();
583
584
        // Builds the query
585
        $query = $this->buildQuery($moduleName, $params, $select, $limit);
586
587
        // Run the query
588
        $records = $this->query($query);
589
        if (false === $records || !is_array($records) || (count($records) <= 0)) {
590
            return false;
591
        }
592
593
        return $records;
594
    }
595
596
    /**
597
     * Sync will return a sync result object containing details of changes after modifiedTime
598
     * @param  int [$modifiedTime = null]    The date of the first change
599
     * @param  string [$moduleName = null]   The name of the module / entity type
600
     * @return array  Sync result object
601
     */
602
    public function entitiesSync($modifiedTime = null, $moduleName = null)
603
    {
604
        // Perform re-login if required.
605
        $this->checkLogin();
606
607
        $modifiedTime = (empty($modifiedTime))
608
            ? strtotime('today midnight')
609
            : intval($modifiedTime);
610
611
        $requestData = [
612
            'operation' => 'sync',
613
            'sessionName' => $this->sessionName,
614
            'modifiedTime' => $modifiedTime
615
        ];
616
617
        if (!empty($moduleName)) {
618
            $requestData['elementType'] = $moduleName;
619
        }
620
621
        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...
622
    }
623
624
    /**
625
     * Builds the query using the supplied parameters
626
     * @param  string   $moduleName  The name of the module / entity type
627
     * @param  array    $params  Data used to find matching entries
628
     * @return array    $select  The list of fields to select (defaults to SQL-like '*' - all the fields)
629
     * @return int      $limit  limit the list of entries to N records (acts like LIMIT in SQL)
630
     * @return string   The query build out of the supplied parameters
631
     */
632
    private function buildQuery($moduleName, array $params, array $select = [], $limit = 0)
633
    {
634
        $criteria = array();
635
        $select=(empty($select)) ? '*' : implode(',', $select);
636
        $query=sprintf("SELECT %s FROM $moduleName WHERE ", $select);
637
        foreach ($params as $param => $value) {
638
            $criteria[] = "{$param} LIKE '{$value}'";
639
        }
640
641
        $query.=implode(" AND ", $criteria);
642
        if (intval($limit) > 0) {
643
            $query.=sprintf(" LIMIT %s", intval($limit));
644
        }
645
        return $query;
646
    }
647
648
    /**
649
     * Checks if if params holds valid entity data/search constraints, otherwise returns false
650
     * @param  array    $params  Array holding entity data/search constraints
651
     * @return boolean  Returns true if params holds valid entity data/search constraints, otherwise returns false
652
     */
653
    private function checkParams(array $params, $paramsPurpose)
654
    {
655 View Code Duplication
        if (empty($params) || !is_array($params) || !$this->isAssocArray($params)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
656
            $this->lastErrorMessage = new WSClientError(sprintf(
657
                "You have to specify at least one search parameter (prop => value) in order to %s",
658
                $paramsPurpose
659
            ));
660
            return false;
661
        }
662
        return true;
663
    }
664
665
    /**
666
     * A helper method, used to check in an array is associative or not
667
     * @param  string  Array to test
668
     * @return boolean Returns true in a given array is associative and false if it's not
669
     */
670
    private function isAssocArray(array $array)
671
    {
672
        foreach (array_keys($array) as $key) {
673
            if (!is_int($key)) {
674
                return true;
675
            }
676
        }
677
        return false;
678
    }
679
}
680