run:R W Run
19.11 KB
2026-03-11 16:18:52
R W Run
15.53 KB
2026-03-11 16:18:52
R W Run
error_log
📄Curl.php
1<?php
2/**
3 * cURL HTTP transport
4 *
5 * @package Requests\Transport
6 */
7
8namespace WpOrg\Requests\Transport;
9
10use RecursiveArrayIterator;
11use RecursiveIteratorIterator;
12use WpOrg\Requests\Capability;
13use WpOrg\Requests\Exception;
14use WpOrg\Requests\Exception\InvalidArgument;
15use WpOrg\Requests\Exception\Transport\Curl as CurlException;
16use WpOrg\Requests\Requests;
17use WpOrg\Requests\Transport;
18use WpOrg\Requests\Utility\InputValidator;
19
20/**
21 * cURL HTTP transport
22 *
23 * @package Requests\Transport
24 */
25final class Curl implements Transport {
26 const CURL_7_10_5 = 0x070A05;
27 const CURL_7_16_2 = 0x071002;
28
29 /**
30 * Raw HTTP data
31 *
32 * @var string
33 */
34 public $headers = '';
35
36 /**
37 * Raw body data
38 *
39 * @var string
40 */
41 public $response_data = '';
42
43 /**
44 * Information on the current request
45 *
46 * @var array cURL information array, see {@link https://www.php.net/curl_getinfo}
47 */
48 public $info;
49
50 /**
51 * cURL version number
52 *
53 * @var int
54 */
55 public $version;
56
57 /**
58 * cURL handle
59 *
60 * @var resource|\CurlHandle Resource in PHP < 8.0, Instance of CurlHandle in PHP >= 8.0.
61 */
62 private $handle;
63
64 /**
65 * Hook dispatcher instance
66 *
67 * @var \WpOrg\Requests\Hooks
68 */
69 private $hooks;
70
71 /**
72 * Have we finished the headers yet?
73 *
74 * @var boolean
75 */
76 private $done_headers = false;
77
78 /**
79 * If streaming to a file, keep the file pointer
80 *
81 * @var resource
82 */
83 private $stream_handle;
84
85 /**
86 * How many bytes are in the response body?
87 *
88 * @var int
89 */
90 private $response_bytes;
91
92 /**
93 * What's the maximum number of bytes we should keep?
94 *
95 * @var int|bool Byte count, or false if no limit.
96 */
97 private $response_byte_limit;
98
99 /**
100 * Constructor
101 */
102 public function __construct() {
103 $curl = curl_version();
104 $this->version = $curl['version_number'];
105 $this->handle = curl_init();
106
107 curl_setopt($this->handle, CURLOPT_HEADER, false);
108 curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
109 if ($this->version >= self::CURL_7_10_5) {
110 curl_setopt($this->handle, CURLOPT_ENCODING, '');
111 }
112
113 if (defined('CURLOPT_PROTOCOLS')) {
114 // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound
115 curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
116 }
117
118 if (defined('CURLOPT_REDIR_PROTOCOLS')) {
119 // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound
120 curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
121 }
122 }
123
124 /**
125 * Destructor
126 */
127 public function __destruct() {
128 if (is_resource($this->handle)) {
129 curl_close($this->handle);
130 }
131 }
132
133 /**
134 * Perform a request
135 *
136 * @param string|Stringable $url URL to request
137 * @param array $headers Associative array of request headers
138 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
139 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
140 * @return string Raw HTTP result
141 *
142 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
143 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
144 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
145 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
146 * @throws \WpOrg\Requests\Exception On a cURL error (`curlerror`)
147 */
148 public function request($url, $headers = [], $data = [], $options = []) {
149 if (InputValidator::is_string_or_stringable($url) === false) {
150 throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url));
151 }
152
153 if (is_array($headers) === false) {
154 throw InvalidArgument::create(2, '$headers', 'array', gettype($headers));
155 }
156
157 if (!is_array($data) && !is_string($data)) {
158 if ($data === null) {
159 $data = '';
160 } else {
161 throw InvalidArgument::create(3, '$data', 'array|string', gettype($data));
162 }
163 }
164
165 if (is_array($options) === false) {
166 throw InvalidArgument::create(4, '$options', 'array', gettype($options));
167 }
168
169 $this->hooks = $options['hooks'];
170
171 $this->setup_handle($url, $headers, $data, $options);
172
173 $options['hooks']->dispatch('curl.before_send', [&$this->handle]);
174
175 if ($options['filename'] !== false) {
176 // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception.
177 $this->stream_handle = @fopen($options['filename'], 'wb');
178 if ($this->stream_handle === false) {
179 $error = error_get_last();
180 throw new Exception($error['message'], 'fopen');
181 }
182 }
183
184 $this->response_data = '';
185 $this->response_bytes = 0;
186 $this->response_byte_limit = false;
187 if ($options['max_bytes'] !== false) {
188 $this->response_byte_limit = $options['max_bytes'];
189 }
190
191 if (isset($options['verify'])) {
192 if ($options['verify'] === false) {
193 curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
194 curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
195 } elseif (is_string($options['verify'])) {
196 curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
197 }
198 }
199
200 if (isset($options['verifyname']) && $options['verifyname'] === false) {
201 curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
202 }
203
204 curl_exec($this->handle);
205 $response = $this->response_data;
206
207 $options['hooks']->dispatch('curl.after_send', []);
208
209 if (curl_errno($this->handle) === CURLE_WRITE_ERROR || curl_errno($this->handle) === CURLE_BAD_CONTENT_ENCODING) {
210 // Reset encoding and try again
211 curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
212
213 $this->response_data = '';
214 $this->response_bytes = 0;
215 curl_exec($this->handle);
216 $response = $this->response_data;
217 }
218
219 $this->process_response($response, $options);
220
221 // Need to remove the $this reference from the curl handle.
222 // Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called.
223 curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
224 curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
225
226 return $this->headers;
227 }
228
229 /**
230 * Send multiple requests simultaneously
231 *
232 * @param array $requests Request data
233 * @param array $options Global options
234 * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
235 *
236 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
237 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
238 */
239 public function request_multiple($requests, $options) {
240 // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
241 if (empty($requests)) {
242 return [];
243 }
244
245 if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
246 throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
247 }
248
249 if (is_array($options) === false) {
250 throw InvalidArgument::create(2, '$options', 'array', gettype($options));
251 }
252
253 $multihandle = curl_multi_init();
254 $subrequests = [];
255 $subhandles = [];
256
257 $class = get_class($this);
258 foreach ($requests as $id => $request) {
259 $subrequests[$id] = new $class();
260 $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
261 $request['options']['hooks']->dispatch('curl.before_multi_add', [&$subhandles[$id]]);
262 curl_multi_add_handle($multihandle, $subhandles[$id]);
263 }
264
265 $completed = 0;
266 $responses = [];
267 $subrequestcount = count($subrequests);
268
269 $request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]);
270
271 do {
272 $active = 0;
273
274 do {
275 $status = curl_multi_exec($multihandle, $active);
276 } while ($status === CURLM_CALL_MULTI_PERFORM);
277
278 $to_process = [];
279
280 // Read the information as needed
281 while ($done = curl_multi_info_read($multihandle)) {
282 $key = array_search($done['handle'], $subhandles, true);
283 if (!isset($to_process[$key])) {
284 $to_process[$key] = $done;
285 }
286 }
287
288 // Parse the finished requests before we start getting the new ones
289 foreach ($to_process as $key => $done) {
290 $options = $requests[$key]['options'];
291 if ($done['result'] !== CURLE_OK) {
292 //get error string for handle.
293 $reason = curl_error($done['handle']);
294 $exception = new CurlException(
295 $reason,
296 CurlException::EASY,
297 $done['handle'],
298 $done['result']
299 );
300 $responses[$key] = $exception;
301 $options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]);
302 } else {
303 $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
304
305 $options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]);
306 }
307
308 curl_multi_remove_handle($multihandle, $done['handle']);
309 curl_close($done['handle']);
310
311 if (!is_string($responses[$key])) {
312 $options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]);
313 }
314
315 $completed++;
316 }
317 } while ($active || $completed < $subrequestcount);
318
319 $request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]);
320
321 curl_multi_close($multihandle);
322
323 return $responses;
324 }
325
326 /**
327 * Get the cURL handle for use in a multi-request
328 *
329 * @param string $url URL to request
330 * @param array $headers Associative array of request headers
331 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
332 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
333 * @return resource|\CurlHandle Subrequest's cURL handle
334 */
335 public function &get_subrequest_handle($url, $headers, $data, $options) {
336 $this->setup_handle($url, $headers, $data, $options);
337
338 if ($options['filename'] !== false) {
339 $this->stream_handle = fopen($options['filename'], 'wb');
340 }
341
342 $this->response_data = '';
343 $this->response_bytes = 0;
344 $this->response_byte_limit = false;
345 if ($options['max_bytes'] !== false) {
346 $this->response_byte_limit = $options['max_bytes'];
347 }
348
349 $this->hooks = $options['hooks'];
350
351 return $this->handle;
352 }
353
354 /**
355 * Setup the cURL handle for the given data
356 *
357 * @param string $url URL to request
358 * @param array $headers Associative array of request headers
359 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
360 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
361 */
362 private function setup_handle($url, $headers, $data, $options) {
363 $options['hooks']->dispatch('curl.before_request', [&$this->handle]);
364
365 // Force closing the connection for old versions of cURL (<7.22).
366 if (!isset($headers['Connection'])) {
367 $headers['Connection'] = 'close';
368 }
369
370 /**
371 * Add "Expect" header.
372 *
373 * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can
374 * add as much as a second to the time it takes for cURL to perform a request. To
375 * prevent this, we need to set an empty "Expect" header. To match the behaviour of
376 * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use
377 * HTTP/1.1.
378 *
379 * https://curl.se/mail/lib-2017-07/0013.html
380 */
381 if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) {
382 $headers['Expect'] = $this->get_expect_header($data);
383 }
384
385 $headers = Requests::flatten($headers);
386
387 if (!empty($data)) {
388 $data_format = $options['data_format'];
389
390 if ($data_format === 'query') {
391 $url = self::format_get($url, $data);
392 $data = '';
393 } elseif (!is_string($data)) {
394 $data = http_build_query($data, '', '&');
395 }
396 }
397
398 switch ($options['type']) {
399 case Requests::POST:
400 curl_setopt($this->handle, CURLOPT_POST, true);
401 curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
402 break;
403 case Requests::HEAD:
404 curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
405 curl_setopt($this->handle, CURLOPT_NOBODY, true);
406 break;
407 case Requests::TRACE:
408 curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
409 break;
410 case Requests::PATCH:
411 case Requests::PUT:
412 case Requests::DELETE:
413 case Requests::OPTIONS:
414 default:
415 curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
416 if (!empty($data)) {
417 curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
418 }
419 }
420
421 // cURL requires a minimum timeout of 1 second when using the system
422 // DNS resolver, as it uses `alarm()`, which is second resolution only.
423 // There's no way to detect which DNS resolver is being used from our
424 // end, so we need to round up regardless of the supplied timeout.
425 //
426 // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
427 $timeout = max($options['timeout'], 1);
428
429 if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
430 curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
431 } else {
432 // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound
433 curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
434 }
435
436 if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
437 curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
438 } else {
439 // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound
440 curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
441 }
442
443 curl_setopt($this->handle, CURLOPT_URL, $url);
444 curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
445 if (!empty($headers)) {
446 curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
447 }
448
449 if ($options['protocol_version'] === 1.1) {
450 curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
451 } else {
452 curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
453 }
454
455 if ($options['blocking'] === true) {
456 curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']);
457 curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']);
458 curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
459 }
460 }
461
462 /**
463 * Process a response
464 *
465 * @param string $response Response data from the body
466 * @param array $options Request options
467 * @return string|false HTTP response data including headers. False if non-blocking.
468 * @throws \WpOrg\Requests\Exception If the request resulted in a cURL error.
469 */
470 public function process_response($response, $options) {
471 if ($options['blocking'] === false) {
472 $fake_headers = '';
473 $options['hooks']->dispatch('curl.after_request', [&$fake_headers]);
474 return false;
475 }
476
477 if ($options['filename'] !== false && $this->stream_handle) {
478 fclose($this->stream_handle);
479 $this->headers = trim($this->headers);
480 } else {
481 $this->headers .= $response;
482 }
483
484 if (curl_errno($this->handle)) {
485 $error = sprintf(
486 'cURL error %s: %s',
487 curl_errno($this->handle),
488 curl_error($this->handle)
489 );
490 throw new Exception($error, 'curlerror', $this->handle);
491 }
492
493 $this->info = curl_getinfo($this->handle);
494
495 $options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]);
496 return $this->headers;
497 }
498
499 /**
500 * Collect the headers as they are received
501 *
502 * @param resource|\CurlHandle $handle cURL handle
503 * @param string $headers Header string
504 * @return integer Length of provided header
505 */
506 public function stream_headers($handle, $headers) {
507 // Why do we do this? cURL will send both the final response and any
508 // interim responses, such as a 100 Continue. We don't need that.
509 // (We may want to keep this somewhere just in case)
510 if ($this->done_headers) {
511 $this->headers = '';
512 $this->done_headers = false;
513 }
514
515 $this->headers .= $headers;
516
517 if ($headers === "\r\n") {
518 $this->done_headers = true;
519 }
520
521 return strlen($headers);
522 }
523
524 /**
525 * Collect data as it's received
526 *
527 * @since 1.6.1
528 *
529 * @param resource|\CurlHandle $handle cURL handle
530 * @param string $data Body data
531 * @return integer Length of provided data
532 */
533 public function stream_body($handle, $data) {
534 $this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]);
535 $data_length = strlen($data);
536
537 // Are we limiting the response size?
538 if ($this->response_byte_limit) {
539 if ($this->response_bytes === $this->response_byte_limit) {
540 // Already at maximum, move on
541 return $data_length;
542 }
543
544 if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
545 // Limit the length
546 $limited_length = ($this->response_byte_limit - $this->response_bytes);
547 $data = substr($data, 0, $limited_length);
548 }
549 }
550
551 if ($this->stream_handle) {
552 fwrite($this->stream_handle, $data);
553 } else {
554 $this->response_data .= $data;
555 }
556
557 $this->response_bytes += strlen($data);
558 return $data_length;
559 }
560
561 /**
562 * Format a URL given GET data
563 *
564 * @param string $url Original URL.
565 * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
566 * @return string URL with data
567 */
568 private static function format_get($url, $data) {
569 if (!empty($data)) {
570 $query = '';
571 $url_parts = parse_url($url);
572 if (empty($url_parts['query'])) {
573 $url_parts['query'] = '';
574 } else {
575 $query = $url_parts['query'];
576 }
577
578 $query .= '&' . http_build_query($data, '', '&');
579 $query = trim($query, '&');
580
581 if (empty($url_parts['query'])) {
582 $url .= '?' . $query;
583 } else {
584 $url = str_replace($url_parts['query'], $query, $url);
585 }
586 }
587
588 return $url;
589 }
590
591 /**
592 * Self-test whether the transport can be used.
593 *
594 * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
595 *
596 * @codeCoverageIgnore
597 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
598 * @return bool Whether the transport can be used.
599 */
600 public static function test($capabilities = []) {
601 if (!function_exists('curl_init') || !function_exists('curl_exec')) {
602 return false;
603 }
604
605 // If needed, check that our installed curl version supports SSL
606 if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) {
607 $curl_version = curl_version();
608 if (!(CURL_VERSION_SSL & $curl_version['features'])) {
609 return false;
610 }
611 }
612
613 return true;
614 }
615
616 /**
617 * Get the correct "Expect" header for the given request data.
618 *
619 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
620 * @return string The "Expect" header.
621 */
622 private function get_expect_header($data) {
623 if (!is_array($data)) {
624 return strlen((string) $data) >= 1048576 ? '100-Continue' : '';
625 }
626
627 $bytesize = 0;
628 $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));
629
630 foreach ($iterator as $datum) {
631 $bytesize += strlen((string) $datum);
632
633 if ($bytesize >= 1048576) {
634 return '100-Continue';
635 }
636 }
637
638 return '';
639 }
640}
641
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1