Completed
Push — master ( 4c707a...1e14b3 )
by Zhmayev
01:23
created

Session::sendHttpRequest()   D

Complexity

Conditions 9
Paths 23

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 27
nc 23
nop 2
dl 0
loc 40
rs 4.909
c 0
b 0
f 0
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 Session class
39
*
40
* Class Session
41
* @package Salaros\Vtiger\VTWSCLib
42
* @internal
43
*/
44
class Session
45
{
46
    // HTTP Client instance
47
    protected $httpClient = null;
48
49
    // Service URL to which client connects to
50
    protected $vtigerUrl = null;
51
    protected $wsBaseURL = null;
52
53
    // Webservice login validity
54
    private $serviceServerTime = null;
55
    private $serviceExpireTime = null;
56
    private $serviceToken = null;
57
58
    // Vtiger CRM and WebServices API version
59
    private $apiVersion = false;
60
    private $vtigerVersion = false;
61
62
    // Webservice user credentials
63
    private $userName = null;
64
    private $accessKey = null;
65
66
    // Webservice login credentials
67
    private $userID = null;
68
    private $sessionName = null;
69
70
    /**
71
     * Class constructor
72
     * @param string $vtigerUrl  The URL of the remote WebServices server
73
     * @param string [$wsBaseURL = 'webservice.php']  WebServices base URL appended to vTiger root URL
74
     */
75
    public function __construct($vtigerUrl, $wsBaseURL = 'webservice.php')
76
    {
77
        $this->vtigerUrl = self::fixVtigerBaseUrl($vtigerUrl);
78
        $this->serviceBaseURL = $wsBaseURL;
0 ignored issues
show
Bug introduced by
The property serviceBaseURL does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
79
80
        // Gets target URL for WebServices API requests
81
        $this->httpClient = new Client([
82
            'base_uri' => $this->vtigerUrl
83
        ]);
84
    }
85
86
    /**
87
     * Login to the server using username and VTiger access key token
88
     * @access public
89
     * @param  string $username VTiger user name
90
     * @param  string $accessKey VTiger access key token (visible on user profile/settings page)
91
     * @return boolean Returns true if login operation has been successful
92
     */
93
    public function login($username, $accessKey)
94
    {
95
        // Do the challenge before loggin in
96
        if ($this->passChallenge($username) === false) {
97
            return false;
98
        }
99
100
        $postdata = [
101
            'operation' => 'login',
102
            'username'  => $username,
103
            'accessKey' => md5($this->serviceToken . $accessKey)
104
        ];
105
106
        $result = $this->sendHttpRequest($postdata);
107
        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...
108
            return false;
109
        }
110
111
        // Backuping logged in user credentials
112
        $this->userName = $username;
113
        $this->accessKey = $accessKey;
114
115
        // Session data
116
        $this->sessionName = $result['sessionName'];
117
        $this->userID = $result['userId'];
118
119
        // Vtiger CRM and WebServices API version
120
        $this->apiVersion = $result['version'];
121
        $this->vtigerVersion = $result['vtigerVersion'];
122
123
        return true;
124
    }
125
126
    /**
127
     * Allows you to login using username and password instead of access key (works on some VTige forks)
128
     * @access public
129
     * @param  string $username VTiger user name
130
     * @param  string $password VTiger password (used to access CRM using the standard login page)
131
     * @param  string $accessKey This parameter will be filled with user's VTiger access key
132
     * @return boolean  Returns true if login operation has been successful
133
     */
134
    public function loginPassword($username, $password, &$accessKey = null)
135
    {
136
        // Do the challenge before loggin in
137
        if ($this->passChallenge($username) === false) {
138
            return false;
139
        }
140
141
        $postdata = [
142
            'operation' => 'login_pwd',
143
            'username' => $username,
144
            'password' => $password
145
        ];
146
147
        $result = $this->sendHttpRequest($postdata);
148
        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...
149
            return false;
150
        }
151
152
        $this->accessKey = array_key_exists('accesskey', $result)
153
            ? $result['accesskey']
154
            : $result[0];
155
156
        return $this->login($username, $accessKey);
157
    }
158
159
    /**
160
     * Checks and performs a login operation if requried and repeats login if needed
161
     * @access private
162
     */
163
    public function checkLogin()
164
    {
165
        if (time() <= $this->serviceExpireTime) {
166
            return;
167
        }
168
        $this->login($this->userName, $this->accessKey);
169
    }
170
171
    /**
172
     * Gets a challenge token from the server and stores for future requests
173
     * @access private
174
     * @param  string $username VTiger user name
175
     * @return booleanReturns false in case of failure
176
     */
177
    private function passChallenge($username)
178
    {
179
        $getdata = [
180
            'operation' => 'getchallenge',
181
            'username'  => $username
182
        ];
183
        $result = $this->sendHttpRequest($getdata, 'GET');
184
        
185
        if (!is_array($result) || !isset($result['token'])) {
186
            return false;
187
        }
188
189
        $this->serviceServerTime = $result['serverTime'];
190
        $this->serviceExpireTime = $result['expireTime'];
191
        $this->serviceToken = $result['token'];
192
193
        return true;
194
    }
195
196
    /**
197
     * Sends HTTP request to VTiger web service API endpoint
198
     * @access private
199
     * @param  array $requestData HTTP request data
200
     * @param  string $method HTTP request method (GET, POST etc)
201
     * @return array Returns request result object (null in case of failure)
202
     */
203
    public function sendHttpRequest(array $requestData, $method = 'POST')
204
    {
205
        if (!isset($requestData['operation'])) {
206
            throw new WSException('Request data must contain the name of the operation!');
207
        }
208
209
        $requestData['sessionName'] = $this->sessionName;
210
211
        // Perform re-login if required.
212
        if ('getchallenge' !== $requestData['operation'] && time() > $this->serviceExpireTime) {
213
            $this->login($this->userName, $this->accessKey);
214
        }
215
        
216
        try {
217
            switch ($method) {
218
                case 'GET':
219
                    $response = $this->httpClient->get($this->serviceBaseURL, ['query' => $requestData]);
220
                    break;
221
                case 'POST':
222
                    $response = $this->httpClient->post($this->serviceBaseURL, ['form_params' => $requestData]);
223
                    break;
224
                default:
225
                    throw new WSException("Unsupported request type {$method}");
226
            }
227
        } catch (RequestException $ex) {
228
            $urlFailed = $this->httpClient->getConfig('base_uri') . $this->serviceBaseURL;
229
            throw new WSException(
230
                sprintf('Failed to execute %s call on "%s" URL', $method, $urlFailed),
231
                'FAILED_SENDING_REQUEST',
232
                $ex
233
            );
234
        }
235
236
        $jsonRaw = $response->getBody();
237
        $jsonObj = json_decode($jsonRaw, true);
238
239
        return (!is_array($jsonObj) || self::checkForError($jsonObj))
240
            ? null
241
            : $jsonObj['result'];
242
    }
243
244
    /**
245
     *  Cleans and fixes vTiger URL
246
     * @access private
247
     * @static
248
     * @param  string  Base URL of vTiger CRM
249
     * @return boolean Returns cleaned and fixed vTiger URL
250
     */
251
    private static function fixVtigerBaseUrl($baseUrl)
252
    {
253
        if (!preg_match('/^https?:\/\//i', $baseUrl)) {
254
            $baseUrl = sprintf('http://%s', $baseUrl);
255
        }
256
        if (strripos($baseUrl, '/') !== strlen($baseUrl)-1) {
257
            $baseUrl .= '/';
258
        }
259
        return $baseUrl;
260
    }
261
262
    /**
263
     * Check if server response contains an error, therefore the requested operation has failed
264
     * @access private
265
     * @static
266
     * @param  array $jsonResult Server response object to check for errors
267
     * @return boolean  True if response object contains an error
268
     */
269
    private static function checkForError(array $jsonResult)
270
    {
271
        if (isset($jsonResult['success']) && (bool)$jsonResult['success'] === true) {
272
            return false;
273
        }
274
275
        if (isset($jsonResult['error'])) {
276
            $error = $jsonResult['error'];
277
            throw new WSException(
278
                $error['message'],
279
                $error['code']
280
            );
281
        }
282
283
        // This should never happen
284
        throw new WSException('Unknown error');
285
    }
286
}
287