Passed
Push — develop-3.3.x ( 457e64...75e26c )
by Mario
02:23
created

ipn_paypal::get_u_paypal()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 *
4
 * PayPal Donation extension for the phpBB Forum Software package.
5
 *
6
 * @copyright (c) 2015-2020 Skouat
7
 * @license GNU General Public License, version 2 (GPL-2.0)
8
 *
9
 * Special Thanks to the following individuals for their inspiration:
10
 *    David Lewis (Highway of Life) http://startrekguide.com
11
 *    Micah Carrick ([email protected]) http://www.micahcarrick.com
12
 */
13
14
namespace skouat\ppde\controller;
15
16
use phpbb\config\config;
17
use phpbb\language\language;
18
use phpbb\request\request;
19
20
class ipn_paypal
21
{
22
	/**
23
	 * Args from PayPal notify return URL
24
	 *
25
	 * @var string
26
	 */
27
	private $args_return_uri = [];
28
	/** Production and Sandbox Postback URL
29
	 *
30
	 * @var array
31
	 */
32
	private static $remote_uri = [
33
		['hostname' => 'www.paypal.com', 'uri' => 'https://www.paypal.com/cgi-bin/webscr', 'type' => 'live'],
34
		['hostname' => 'www.sandbox.paypal.com', 'uri' => 'https://www.sandbox.paypal.com/cgi-bin/webscr', 'type' => 'sandbox'],
35
		['hostname' => 'ipnpb.paypal.com', 'uri' => 'https://ipnpb.paypal.com/cgi-bin/webscr', 'type' => 'live'],
36
		['hostname' => 'ipnpb.sandbox.paypal.com', 'uri' => 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr', 'type' => 'sandbox'],
37
	];
38
39
	protected $config;
40
	protected $language;
41
	protected $ppde_ext_manager;
42
	protected $ppde_ipn_log;
43
	protected $request;
44
	/**
45
	 * @var array
46
	 */
47
	private $curl_fsock = ['curl' => false, 'none' => true];
48
	/**
49
	 * Full PayPal response for include in text report
50
	 *
51
	 * @var string
52
	 */
53
	private $report_response = '';
54
	/**
55
	 * PayPal response (VERIFIED or INVALID)
56
	 *
57
	 * @var string
58
	 */
59
	private $response = '';
60
	/**
61
	 * PayPal response status (code 200 or other)
62
	 *
63
	 * @var string
64
	 */
65
	private $response_status = '';
66
	/**
67
	 * PayPal URL
68
	 * Could be Sandbox URL ou normal PayPal URL.
69
	 *
70
	 * @var string
71
	 */
72
	private $u_paypal = '';
73
74
	/**
75
	 * Constructor
76
	 *
77
	 * @param config            $config           Config object
78
	 * @param language          $language         Language user object
79
	 * @param extension_manager $ppde_ext_manager Extension manager object
80
	 * @param ipn_log           $ppde_ipn_log     IPN log
81
	 * @param request           $request          Request object
82
	 *
83
	 * @access public
84
	 */
85
	public function __construct(
86
		config $config,
87
		language $language,
88
		extension_manager $ppde_ext_manager,
89
		ipn_log $ppde_ipn_log,
90
		request $request
91
	)
92
	{
93
		$this->config = $config;
94
		$this->language = $language;
95
		$this->ppde_ext_manager = $ppde_ext_manager;
96
		$this->ppde_ipn_log = $ppde_ipn_log;
97
		$this->request = $request;
98
	}
99
100
	/**
101
	 * @return array
102
	 */
103
	public static function get_remote_uri()
104
	{
105
		return self::$remote_uri;
106
	}
107
108
	/**
109
	 * Initiate communication with PayPal.
110
	 * We use cURL. If it is not available we log an error.
111
	 *
112
	 * @param array $data
113
	 *
114
	 * @return void
115
	 * @access public
116
	 */
117
	public function initiate_paypal_connection(array $data)
118
	{
119
		if ($this->curl_fsock['curl'])
120
		{
121
			$this->curl_post($this->args_return_uri);
122
		}
123
		else
124
		{
125
			$this->ppde_ipn_log->log_error($this->language->lang('NO_CONNECTION_DETECTED'), $this->ppde_ipn_log->is_use_log_error(), true, E_USER_ERROR, $data);
126
		}
127
	}
128
129
	/**
130
	 * Post Back Using cURL
131
	 *
132
	 * Sends the post back to PayPal using the cURL library. Called by
133
	 * the validate_transaction() method if the curl_fsock['curl'] property is true.
134
	 * Throws an exception if the post fails. Populates the response and response_status properties on success.
135
	 *
136
	 * @param string $encoded_data The post data as a URL encoded string
137
	 *
138
	 * @return void
139
	 * @access private
140
	 */
141
	private function curl_post(string $encoded_data)
142
	{
143
		$ch = curl_init($this->u_paypal);
144
		curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
		curl_setopt(/** @scrutinizer ignore-type */ $ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
Loading history...
145
		curl_setopt($ch, CURLOPT_POST, true);
146
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
147
		curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data);
148
		curl_setopt($ch, CURLOPT_SSLVERSION, 6);
149
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
150
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
151
		curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
152
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
153
		curl_setopt($ch, CURLOPT_HTTPHEADER, [
154
			'User-Agent: PHP-IPN-Verification-Script',
155
			'Connection: Close',
156
		]);
157
158
		if ($this->ppde_ipn_log->is_use_log_error())
159
		{
160
			curl_setopt($ch, CURLOPT_HEADER, true);
161
			curl_setopt($ch, CURLINFO_HEADER_OUT, true);
162
		}
163
164
		$this->report_response = $this->response = curl_exec($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

164
		$this->report_response = $this->response = curl_exec(/** @scrutinizer ignore-type */ $ch);
Loading history...
Documentation Bug introduced by
It seems like $this->response = curl_exec($ch) can also be of type boolean. However, the property $report_response is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
Documentation Bug introduced by
It seems like curl_exec($ch) can also be of type boolean. However, the property $response is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
165
		if (curl_errno($ch) != 0)
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_errno() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
		if (curl_errno(/** @scrutinizer ignore-type */ $ch) != 0)
Loading history...
166
		{
167
			// cURL error
168
			$this->ppde_ipn_log->log_error($this->language->lang('CURL_ERROR', curl_errno($ch) . ' (' . curl_error($ch) . ')'), $this->ppde_ipn_log->is_use_log_error());
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_error() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

168
			$this->ppde_ipn_log->log_error($this->language->lang('CURL_ERROR', curl_errno($ch) . ' (' . curl_error(/** @scrutinizer ignore-type */ $ch) . ')'), $this->ppde_ipn_log->is_use_log_error());
Loading history...
169
		}
170
		else
171
		{
172
			$info = curl_getinfo($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_getinfo() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
			$info = curl_getinfo(/** @scrutinizer ignore-type */ $ch);
Loading history...
173
			$this->response_status = $info['http_code'];
174
		}
175
		curl_close($ch);
176
177
		// Split response headers and payload, a better way for strcmp
178
		$tokens = explode("\r\n\r\n", trim($this->response));
179
		$this->response = trim(end($tokens));
180
	}
181
182
	/**
183
	 * Set property 'curl_fsock' to use cURL based on config settings.
184
	 * If cURL is not available we use default value of the property 'curl_fsock'.
185
	 *
186
	 * @return bool
187
	 * @access public
188
	 */
189
	public function is_remote_detected()
190
	{
191
		if ($this->config['ppde_curl_detected'])
192
		{
193
			$this->curl_fsock = ['curl' => true, 'none' => false];
194
		}
195
196
		return array_search(true, $this->curl_fsock);
197
	}
198
199
	/**
200
	 * Set the property '$u_paypal'
201
	 *
202
	 * @param string $u_paypal
203
	 *
204
	 * @return void
205
	 * @access public
206
	 */
207
	public function set_u_paypal(string $u_paypal)
208
	{
209
		$this->u_paypal = $u_paypal;
210
	}
211
212
	/**
213
	 * Get the property '$u_paypal'
214
	 *
215
	 * @return string
216
	 * @access public
217
	 */
218
	public function get_u_paypal()
219
	{
220
		return $this->u_paypal;
221
	}
222
223
	/**
224
	 * Get the service that will be used to contact PayPal
225
	 * Returns the name of the key that is set to true.
226
	 *
227
	 * @return string
228
	 * @access public
229
	 */
230
	public function get_remote_used()
231
	{
232
		return array_search(true, $this->curl_fsock);
233
	}
234
235
	/**
236
	 * Full PayPal response for include in text report
237
	 *
238
	 * @return string
239
	 * @access public
240
	 */
241
	public function get_report_response()
242
	{
243
		return $this->report_response;
244
	}
245
246
	/**
247
	 * PayPal response status
248
	 *
249
	 * @return string
250
	 * @access public
251
	 */
252
	public function get_response_status()
253
	{
254
		return $this->response_status;
255
	}
256
257
	/**
258
	 * Check if the response status is equal to "200".
259
	 *
260
	 * @return bool
261
	 * @access public
262
	 */
263
	public function check_response_status()
264
	{
265
		return $this->response_status != 200;
266
	}
267
268
	/**
269
	 * If cURL is available we use strcmp() to get the Pay
270
	 *
271
	 * @param string $arg
272
	 *
273
	 * @return bool
274
	 * @access public
275
	 */
276
	public function is_curl_strcmp(string $arg)
277
	{
278
		return $this->curl_fsock['curl'] && (strcmp($this->response, $arg) === 0);
279
	}
280
281
	/**
282
	 * Check if website use TLS 1.2
283
	 *
284
	 * @return void
285
	 * @access public
286
	 */
287
	public function check_tls()
288
	{
289
		$ext_meta = $this->ppde_ext_manager->get_ext_meta();
290
291
		// Reset settings to false
292
		$this->config->set('ppde_tls_detected', false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $value of phpbb\config\config::set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

292
		$this->config->set('ppde_tls_detected', /** @scrutinizer ignore-type */ false);
Loading history...
293
		$this->response = '';
294
295
		$this->check_curl($ext_meta['extra']['security-check']['tls']['tls-host']);
296
297
		// Analyse response
298
		$json = json_decode($this->response);
299
300
		if (in_array($json->tls_version, $ext_meta['extra']['security-check']['tls']['tls-version']))
301
		{
302
			$this->config->set('ppde_tls_detected', true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of phpbb\config\config::set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

302
			$this->config->set('ppde_tls_detected', /** @scrutinizer ignore-type */ true);
Loading history...
303
		}
304
	}
305
306
	/**
307
	 * Set config value for cURL
308
	 *
309
	 * @return void
310
	 * @access public
311
	 */
312
	public function set_remote_detected()
313
	{
314
		$ext_meta = $this->ppde_ext_manager->get_ext_meta();
315
316
		$this->config->set('ppde_curl_detected', $this->check_curl($ext_meta['extra']['version-check']['host']));
317
	}
318
319
	/**
320
	 * Check if cURL is available
321
	 *
322
	 * @param string $host
323
	 *
324
	 * @return bool
325
	 * @access public
326
	 */
327
	public function check_curl(string $host)
328
	{
329
		if (function_exists('curl_init') && function_exists('curl_exec'))
330
		{
331
			$ch = curl_init($host);
332
333
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

333
			curl_setopt(/** @scrutinizer ignore-type */ $ch, CURLOPT_RETURNTRANSFER, true);
Loading history...
334
335
			$this->response = curl_exec($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

335
			$this->response = curl_exec(/** @scrutinizer ignore-type */ $ch);
Loading history...
Documentation Bug introduced by
It seems like curl_exec($ch) can also be of type boolean. However, the property $response is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
336
			$this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_getinfo() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
			$this->response_status = strval(curl_getinfo(/** @scrutinizer ignore-type */ $ch, CURLINFO_HTTP_CODE));
Loading history...
337
338
			curl_close($ch);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

338
			curl_close(/** @scrutinizer ignore-type */ $ch);
Loading history...
339
340
			return $this->response !== false || $this->response_status !== '0';
341
		}
342
343
		return false;
344
	}
345
346
	/**
347
	 * Set config value for cURL version
348
	 *
349
	 * @return void
350
	 * @access public
351
	 */
352
	public function set_curl_info()
353
	{
354
		// Get cURL version informations
355
		if ($curl_info = $this->check_curl_info())
356
		{
357
			$this->config->set('ppde_curl_version', $curl_info['version']);
358
			$this->config->set('ppde_curl_ssl_version', $curl_info['ssl_version']);
359
		}
360
	}
361
362
	/**
363
	 * Get cURL version if available
364
	 *
365
	 * @return array|bool
366
	 * @access public
367
	 */
368
	public function check_curl_info()
369
	{
370
		if (function_exists('curl_version'))
371
		{
372
			return curl_version();
373
		}
374
375
		return false;
376
	}
377
378
	/**
379
	 * Get all args and build the return URI
380
	 *
381
	 * @return void
382
	 * @access public
383
	 */
384
	public function set_args_return_uri()
385
	{
386
		$values = [];
387
		// Add the cmd=_notify-validate for PayPal
388
		$this->args_return_uri = 'cmd=_notify-validate';
389
390
		// Grab the post data form and set in an array to be used in the URI to PayPal
391
		foreach ($this->get_postback_args() as $key => $value)
392
		{
393
			$values[] = $key . '=' . urlencode($value);
394
		}
395
396
		// Implode the array into a string URI
397
		$this->args_return_uri .= '&' . implode('&', $values);
398
	}
399
400
	/**
401
	 * Get $_POST content as is. This is used to Postback args to PayPal or for tracking errors.
402
	 * based on official PayPal IPN class (https://github.com/paypal/ipn-code-samples/blob/master/php/PaypalIPN.php)
403
	 *
404
	 * @return array
405
	 * @access public
406
	 */
407
	public function get_postback_args()
408
	{
409
		$data_ary = [];
410
		$raw_post_data = file_get_contents('php://input');
411
		$raw_post_array = explode('&', $raw_post_data);
412
413
		foreach ($raw_post_array as $keyval)
414
		{
415
			$keyval = explode('=', $keyval);
416
			if (count($keyval) == 2)
417
			{
418
				// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
419
				if ($keyval[0] === 'payment_date')
420
				{
421
					if (substr_count($keyval[1], '+') === 1)
422
					{
423
						$keyval[1] = str_replace('+', '%2B', $keyval[1]);
424
					}
425
				}
426
				$data_ary[$keyval[0]] = urldecode($keyval[1]);
427
			}
428
		}
429
430
		return $data_ary;
431
	}
432
}
433