PHP-script based on CURL to get data from schools timetable management software "WebUntis"
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

1061 líneas
27 KiB

  1. <?php
  2. namespace Curl;
  3. abstract class CurlCookieConst
  4. {
  5. private static $RFC2616 = array();
  6. private static $RFC6265 = array();
  7. public static function Init() {
  8. self::$RFC2616 = array_fill_keys(array(
  9. // RFC2616: "any CHAR except CTLs or separators".
  10. '!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A',
  11. 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
  12. 'W', 'X', 'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
  13. 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~',
  14. ), true);
  15. self::$RFC6265 = array_fill_keys(array(
  16. // RFC6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash".
  17. // %x21
  18. '!',
  19. // %x23-2B
  20. '#', '$', '%', '&', "'", '(', ')', '*', '+',
  21. // %x2D-3A
  22. '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',
  23. // %x3C-5B
  24. '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  25. 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[',
  26. // %x5D-7E
  27. ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
  28. 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
  29. ), true);
  30. }
  31. public static function RFC2616() {
  32. return self::$RFC2616;
  33. }
  34. public static function RFC6265() {
  35. return self::$RFC6265;
  36. }
  37. }
  38. CurlCookieConst::Init();
  39. class Curl
  40. {
  41. const VERSION = '4.8.2';
  42. const DEFAULT_TIMEOUT = 30;
  43. public $curl;
  44. public $id = null;
  45. public $error = false;
  46. public $errorCode = 0;
  47. public $errorMessage = null;
  48. public $curlError = false;
  49. public $curlErrorCode = 0;
  50. public $curlErrorMessage = null;
  51. public $httpError = false;
  52. public $httpStatusCode = 0;
  53. public $httpErrorMessage = null;
  54. public $baseUrl = null;
  55. public $url = null;
  56. public $requestHeaders = null;
  57. public $responseHeaders = null;
  58. public $rawResponseHeaders = '';
  59. public $response = null;
  60. public $rawResponse = null;
  61. public $beforeSendFunction = null;
  62. public $downloadCompleteFunction = null;
  63. private $successFunction = null;
  64. private $errorFunction = null;
  65. private $completeFunction = null;
  66. private $cookies = array();
  67. private $responseCookies = array();
  68. private $headers = array();
  69. private $options = array();
  70. private $jsonDecoder = null;
  71. private $jsonPattern = '/^(?:application|text)\/(?:[a-z]+(?:[\.-][0-9a-z]+){0,}[\+\.]|x-)?json(?:-[a-z]+)?/i';
  72. private $xmlPattern = '~^(?:text/|application/(?:atom\+|rss\+)?)xml~i';
  73. /**
  74. * Construct
  75. *
  76. * @access public
  77. * @param $base_url
  78. * @throws \ErrorException
  79. */
  80. public function __construct($base_url = null)
  81. {
  82. if (!extension_loaded('curl')) {
  83. throw new \ErrorException('cURL library is not loaded');
  84. }
  85. $this->curl = curl_init();
  86. $this->id = 1;
  87. $this->setDefaultUserAgent();
  88. $this->setDefaultJsonDecoder();
  89. $this->setDefaultTimeout();
  90. $this->setOpt(CURLINFO_HEADER_OUT, true);
  91. $this->setOpt(CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
  92. $this->setOpt(CURLOPT_RETURNTRANSFER, true);
  93. $this->headers = new CaseInsensitiveArray();
  94. $this->setURL($base_url);
  95. }
  96. /**
  97. * Before Send
  98. *
  99. * @access public
  100. * @param $callback
  101. */
  102. public function beforeSend($callback)
  103. {
  104. $this->beforeSendFunction = $callback;
  105. }
  106. /**
  107. * Build Post Data
  108. *
  109. * @access public
  110. * @param $data
  111. *
  112. * @return array|string
  113. */
  114. public function buildPostData($data)
  115. {
  116. if (is_array($data)) {
  117. if (self::is_array_multidim($data)) {
  118. if (isset($this->headers['Content-Type']) &&
  119. preg_match($this->jsonPattern, $this->headers['Content-Type'])) {
  120. $json_str = json_encode($data);
  121. if (!($json_str === false)) {
  122. $data = $json_str;
  123. }
  124. } else {
  125. $data = self::http_build_multi_query($data);
  126. }
  127. } else {
  128. $binary_data = false;
  129. foreach ($data as $key => $value) {
  130. // Fix "Notice: Array to string conversion" when $value in curl_setopt($ch, CURLOPT_POSTFIELDS,
  131. // $value) is an array that contains an empty array.
  132. if (is_array($value) && empty($value)) {
  133. $data[$key] = '';
  134. // Fix "curl_setopt(): The usage of the @filename API for file uploading is deprecated. Please use
  135. // the CURLFile class instead". Ignore non-file values prefixed with the @ character.
  136. } elseif (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) {
  137. $binary_data = true;
  138. if (class_exists('CURLFile')) {
  139. $data[$key] = new \CURLFile(substr($value, 1));
  140. }
  141. } elseif ($value instanceof \CURLFile) {
  142. $binary_data = true;
  143. }
  144. }
  145. if (!$binary_data) {
  146. if (isset($this->headers['Content-Type']) &&
  147. preg_match($this->jsonPattern, $this->headers['Content-Type'])) {
  148. $json_str = json_encode($data);
  149. if (!($json_str === false)) {
  150. $data = $json_str;
  151. }
  152. } else {
  153. $data = http_build_query($data, '', '&');
  154. }
  155. }
  156. }
  157. }
  158. return $data;
  159. }
  160. /**
  161. * Call
  162. *
  163. * @access public
  164. */
  165. public function call()
  166. {
  167. $args = func_get_args();
  168. $function = array_shift($args);
  169. if (is_callable($function)) {
  170. array_unshift($args, $this);
  171. call_user_func_array($function, $args);
  172. }
  173. }
  174. /**
  175. * Close
  176. *
  177. * @access public
  178. */
  179. public function close()
  180. {
  181. if (is_resource($this->curl)) {
  182. curl_close($this->curl);
  183. }
  184. $this->options = null;
  185. $this->jsonDecoder = null;
  186. }
  187. /**
  188. * Complete
  189. *
  190. * @access public
  191. * @param $callback
  192. */
  193. public function complete($callback)
  194. {
  195. $this->completeFunction = $callback;
  196. }
  197. /**
  198. * Progress
  199. *
  200. * @access public
  201. * @param $callback
  202. */
  203. public function progress($callback)
  204. {
  205. $this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback);
  206. $this->setOpt(CURLOPT_NOPROGRESS, false);
  207. }
  208. /**
  209. * Delete
  210. *
  211. * @access public
  212. * @param $url
  213. * @param $query_parameters
  214. * @param $data
  215. *
  216. * @return string
  217. */
  218. public function delete($url, $query_parameters = array(), $data = array())
  219. {
  220. if (is_array($url)) {
  221. $data = $query_parameters;
  222. $query_parameters = $url;
  223. $url = $this->baseUrl;
  224. }
  225. $this->setURL($url, $query_parameters);
  226. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE');
  227. $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
  228. return $this->exec();
  229. }
  230. /**
  231. * Download Complete
  232. *
  233. * @access public
  234. * @param $fh
  235. */
  236. public function downloadComplete($fh)
  237. {
  238. if (!$this->error && $this->downloadCompleteFunction) {
  239. rewind($fh);
  240. $this->call($this->downloadCompleteFunction, $fh);
  241. $this->downloadCompleteFunction = null;
  242. }
  243. if (is_resource($fh)) {
  244. fclose($fh);
  245. }
  246. // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the
  247. // PHP script from stdin. Using null causes "Warning: curl_setopt():
  248. // supplied argument is not a valid File-Handle resource".
  249. if (!defined('STDOUT')) {
  250. define('STDOUT', fopen('php://stdout', 'w'));
  251. }
  252. // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE
  253. // resource has gone away, resetting to default".
  254. $this->setOpt(CURLOPT_FILE, STDOUT);
  255. // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent
  256. // responses as the return value of curl_exec(). Without this,
  257. // curl_exec() will revert to returning boolean values.
  258. $this->setOpt(CURLOPT_RETURNTRANSFER, true);
  259. }
  260. /**
  261. * Download
  262. *
  263. * @access public
  264. * @param $url
  265. * @param $mixed_filename
  266. *
  267. * @return boolean
  268. */
  269. public function download($url, $mixed_filename)
  270. {
  271. if (is_callable($mixed_filename)) {
  272. $this->downloadCompleteFunction = $mixed_filename;
  273. $fh = tmpfile();
  274. } else {
  275. $filename = $mixed_filename;
  276. $fh = fopen($filename, 'wb');
  277. }
  278. $this->setOpt(CURLOPT_FILE, $fh);
  279. $this->get($url);
  280. $this->downloadComplete($fh);
  281. return ! $this->error;
  282. }
  283. /**
  284. * Error
  285. *
  286. * @access public
  287. * @param $callback
  288. */
  289. public function error($callback)
  290. {
  291. $this->errorFunction = $callback;
  292. }
  293. /**
  294. * Exec
  295. *
  296. * @access public
  297. * @param $ch
  298. *
  299. * @return string
  300. */
  301. public function exec($ch = null)
  302. {
  303. $this->responseCookies = array();
  304. if (!($ch === null)) {
  305. $this->rawResponse = curl_multi_getcontent($ch);
  306. } else {
  307. $this->call($this->beforeSendFunction);
  308. $this->rawResponse = curl_exec($this->curl);
  309. $this->curlErrorCode = curl_errno($this->curl);
  310. }
  311. $this->curlErrorMessage = curl_error($this->curl);
  312. $this->curlError = !($this->curlErrorCode === 0);
  313. $this->httpStatusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
  314. $this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5));
  315. $this->error = $this->curlError || $this->httpError;
  316. $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0;
  317. // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders
  318. // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);).
  319. if ($this->getOpt(CURLINFO_HEADER_OUT) === true) {
  320. $this->requestHeaders = $this->parseRequestHeaders(curl_getinfo($this->curl, CURLINFO_HEADER_OUT));
  321. }
  322. $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders);
  323. list($this->response, $this->rawResponse) = $this->parseResponse($this->responseHeaders, $this->rawResponse);
  324. $this->httpErrorMessage = '';
  325. if ($this->error) {
  326. if (isset($this->responseHeaders['Status-Line'])) {
  327. $this->httpErrorMessage = $this->responseHeaders['Status-Line'];
  328. }
  329. }
  330. $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage;
  331. if (!$this->error) {
  332. $this->call($this->successFunction);
  333. } else {
  334. $this->call($this->errorFunction);
  335. }
  336. $this->call($this->completeFunction);
  337. return $this->response;
  338. }
  339. /**
  340. * Get
  341. *
  342. * @access public
  343. * @param $url
  344. * @param $data
  345. *
  346. * @return string
  347. */
  348. public function get($url, $data = array())
  349. {
  350. if (is_array($url)) {
  351. $data = $url;
  352. $url = $this->baseUrl;
  353. }
  354. $this->setURL($url, $data);
  355. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET');
  356. $this->setOpt(CURLOPT_HTTPGET, true);
  357. return $this->exec();
  358. }
  359. /**
  360. * Get Opt
  361. *
  362. * @access public
  363. * @param $option
  364. *
  365. * @return mixed
  366. */
  367. public function getOpt($option)
  368. {
  369. return $this->options[$option];
  370. }
  371. /**
  372. * Head
  373. *
  374. * @access public
  375. * @param $url
  376. * @param $data
  377. *
  378. * @return string
  379. */
  380. public function head($url, $data = array())
  381. {
  382. if (is_array($url)) {
  383. $data = $url;
  384. $url = $this->baseUrl;
  385. }
  386. $this->setURL($url, $data);
  387. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD');
  388. $this->setOpt(CURLOPT_NOBODY, true);
  389. return $this->exec();
  390. }
  391. /**
  392. * Header Callback
  393. *
  394. * @access public
  395. * @param $ch
  396. * @param $header
  397. *
  398. * @return integer
  399. */
  400. public function headerCallback($ch, $header)
  401. {
  402. if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) == 1) {
  403. $this->responseCookies[$cookie[1]] = $cookie[2];
  404. }
  405. $this->rawResponseHeaders .= $header;
  406. return strlen($header);
  407. }
  408. /**
  409. * Options
  410. *
  411. * @access public
  412. * @param $url
  413. * @param $data
  414. *
  415. * @return string
  416. */
  417. public function options($url, $data = array())
  418. {
  419. if (is_array($url)) {
  420. $data = $url;
  421. $url = $this->baseUrl;
  422. }
  423. $this->setURL($url, $data);
  424. $this->unsetHeader('Content-Length');
  425. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS');
  426. return $this->exec();
  427. }
  428. /**
  429. * Patch
  430. *
  431. * @access public
  432. * @param $url
  433. * @param $data
  434. *
  435. * @return string
  436. */
  437. public function patch($url, $data = array())
  438. {
  439. if (is_array($url)) {
  440. $data = $url;
  441. $url = $this->baseUrl;
  442. }
  443. if (is_array($data) && empty($data)) {
  444. $this->unsetHeader('Content-Length');
  445. }
  446. $this->setURL($url);
  447. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH');
  448. $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
  449. return $this->exec();
  450. }
  451. /**
  452. * Post
  453. *
  454. * @access public
  455. * @param $url
  456. * @param $data
  457. *
  458. * @return string
  459. */
  460. public function post($url, $data = array())
  461. {
  462. if (is_array($url)) {
  463. $data = $url;
  464. $url = $this->baseUrl;
  465. }
  466. $this->setURL($url);
  467. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
  468. $this->setOpt(CURLOPT_POST, true);
  469. $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
  470. return $this->exec();
  471. }
  472. /**
  473. * Put
  474. *
  475. * @access public
  476. * @param $url
  477. * @param $data
  478. *
  479. * @return string
  480. */
  481. public function put($url, $data = array())
  482. {
  483. if (is_array($url)) {
  484. $data = $url;
  485. $url = $this->baseUrl;
  486. }
  487. $this->setURL($url);
  488. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT');
  489. $put_data = $this->buildPostData($data);
  490. if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
  491. $this->setHeader('Content-Length', strlen($put_data));
  492. }
  493. $this->setOpt(CURLOPT_POSTFIELDS, $put_data);
  494. return $this->exec();
  495. }
  496. /**
  497. * Set Basic Authentication
  498. *
  499. * @access public
  500. * @param $username
  501. * @param $password
  502. */
  503. public function setBasicAuthentication($username, $password = '')
  504. {
  505. $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  506. $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
  507. }
  508. /**
  509. * Set Digest Authentication
  510. *
  511. * @access public
  512. * @param $username
  513. * @param $password
  514. */
  515. public function setDigestAuthentication($username, $password = '')
  516. {
  517. $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
  518. $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
  519. }
  520. /**
  521. * Set Cookie
  522. *
  523. * @access public
  524. * @param $key
  525. * @param $value
  526. */
  527. public function setCookie($key, $value)
  528. {
  529. $name_chars = array();
  530. foreach (str_split($key) as $name_char) {
  531. if (!array_key_exists($name_char, CurlCookieConst::RFC2616())) {
  532. $name_chars[] = rawurlencode($name_char);
  533. } else {
  534. $name_chars[] = $name_char;
  535. }
  536. }
  537. $value_chars = array();
  538. foreach (str_split($value) as $value_char) {
  539. if (!array_key_exists($value_char, CurlCookieConst::RFC6265())) {
  540. $value_chars[] = rawurlencode($value_char);
  541. } else {
  542. $value_chars[] = $value_char;
  543. }
  544. }
  545. $this->cookies[implode('', $name_chars)] = implode('', $value_chars);
  546. $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function($k, $v) {
  547. return $k . '=' . $v;
  548. }, array_keys($this->cookies), array_values($this->cookies))));
  549. }
  550. /**
  551. * Get cookie.
  552. *
  553. * @access public
  554. * @param $key
  555. */
  556. public function getCookie($key)
  557. {
  558. return $this->getResponseCookie($key);
  559. }
  560. /**
  561. * Get response cookie.
  562. *
  563. * @access public
  564. * @param $key
  565. */
  566. public function getResponseCookie($key)
  567. {
  568. return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null;
  569. }
  570. /**
  571. * Set Port
  572. *
  573. * @access public
  574. * @param $port
  575. */
  576. public function setPort($port)
  577. {
  578. $this->setOpt(CURLOPT_PORT, intval($port));
  579. }
  580. /**
  581. * Set Connect Timeout
  582. *
  583. * @access public
  584. * @param $seconds
  585. */
  586. public function setConnectTimeout($seconds)
  587. {
  588. $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds);
  589. }
  590. /**
  591. * Set Cookie File
  592. *
  593. * @access public
  594. * @param $cookie_file
  595. */
  596. public function setCookieFile($cookie_file)
  597. {
  598. $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file);
  599. }
  600. /**
  601. * Set Cookie Jar
  602. *
  603. * @access public
  604. * @param $cookie_jar
  605. */
  606. public function setCookieJar($cookie_jar)
  607. {
  608. $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar);
  609. }
  610. /**
  611. * Set Default JSON Decoder
  612. *
  613. * @access public
  614. */
  615. public function setDefaultJsonDecoder()
  616. {
  617. $this->jsonDecoder = function($response) {
  618. $json_obj = json_decode($response, false);
  619. if (!($json_obj === null)) {
  620. $response = $json_obj;
  621. }
  622. return $response;
  623. };
  624. }
  625. /**
  626. * Set Default Timeout
  627. *
  628. * @access public
  629. */
  630. public function setDefaultTimeout()
  631. {
  632. $this->setTimeout(self::DEFAULT_TIMEOUT);
  633. }
  634. /**
  635. * Set Default User Agent
  636. *
  637. * @access public
  638. */
  639. public function setDefaultUserAgent()
  640. {
  641. $user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)';
  642. $user_agent .= ' PHP/' . PHP_VERSION;
  643. $curl_version = curl_version();
  644. $user_agent .= ' curl/' . $curl_version['version'];
  645. $this->setUserAgent($user_agent);
  646. }
  647. /**
  648. * Set Header
  649. *
  650. * @access public
  651. * @param $key
  652. * @param $value
  653. *
  654. * @return string
  655. */
  656. public function setHeader($key, $value)
  657. {
  658. $this->headers[$key] = $value;
  659. $headers = array();
  660. foreach ($this->headers as $key => $value) {
  661. $headers[] = $key . ': ' . $value;
  662. }
  663. $this->setOpt(CURLOPT_HTTPHEADER, $headers);
  664. }
  665. /**
  666. * Set JSON Decoder
  667. *
  668. * @access public
  669. * @param $function
  670. */
  671. public function setJsonDecoder($function)
  672. {
  673. if (is_callable($function)) {
  674. $this->jsonDecoder = $function;
  675. }
  676. }
  677. /**
  678. * Set Opt
  679. *
  680. * @access public
  681. * @param $option
  682. * @param $value
  683. *
  684. * @return boolean
  685. */
  686. public function setOpt($option, $value)
  687. {
  688. $required_options = array(
  689. CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER',
  690. );
  691. if (in_array($option, array_keys($required_options), true) && !($value === true)) {
  692. trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING);
  693. }
  694. $this->options[$option] = $value;
  695. return curl_setopt($this->curl, $option, $value);
  696. }
  697. /**
  698. * Set Referer
  699. *
  700. * @access public
  701. * @param $referer
  702. */
  703. public function setReferer($referer)
  704. {
  705. $this->setReferrer($referer);
  706. }
  707. /**
  708. * Set Referrer
  709. *
  710. * @access public
  711. * @param $referrer
  712. */
  713. public function setReferrer($referrer)
  714. {
  715. $this->setOpt(CURLOPT_REFERER, $referrer);
  716. }
  717. /**
  718. * Set Timeout
  719. *
  720. * @access public
  721. * @param $seconds
  722. */
  723. public function setTimeout($seconds)
  724. {
  725. $this->setOpt(CURLOPT_TIMEOUT, $seconds);
  726. }
  727. /**
  728. * Set Url
  729. *
  730. * @access public
  731. * @param $url
  732. * @param $data
  733. */
  734. public function setURL($url, $data = array())
  735. {
  736. $this->baseUrl = $url;
  737. $this->url = $this->buildURL($url, $data);
  738. $this->setOpt(CURLOPT_URL, $this->url);
  739. }
  740. /**
  741. * Set User Agent
  742. *
  743. * @access public
  744. * @param $user_agent
  745. */
  746. public function setUserAgent($user_agent)
  747. {
  748. $this->setOpt(CURLOPT_USERAGENT, $user_agent);
  749. }
  750. /**
  751. * Success
  752. *
  753. * @access public
  754. * @param $callback
  755. */
  756. public function success($callback)
  757. {
  758. $this->successFunction = $callback;
  759. }
  760. /**
  761. * Unset Header
  762. *
  763. * @access public
  764. * @param $key
  765. */
  766. public function unsetHeader($key)
  767. {
  768. $this->setHeader($key, '');
  769. unset($this->headers[$key]);
  770. }
  771. /**
  772. * Verbose
  773. *
  774. * @access public
  775. * @param $on
  776. */
  777. public function verbose($on = true, $output=STDERR)
  778. {
  779. // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side
  780. // effect of causing Curl::requestHeaders to be empty.
  781. if ($on) {
  782. $this->setOpt(CURLINFO_HEADER_OUT, false);
  783. }
  784. $this->setOpt(CURLOPT_VERBOSE, $on);
  785. $this->setOpt(CURLOPT_STDERR, $output);
  786. }
  787. /**
  788. * Destruct
  789. *
  790. * @access public
  791. */
  792. public function __destruct()
  793. {
  794. $this->close();
  795. }
  796. /**
  797. * Build Url
  798. *
  799. * @access private
  800. * @param $url
  801. * @param $data
  802. *
  803. * @return string
  804. */
  805. private function buildURL($url, $data = array())
  806. {
  807. return $url . (empty($data) ? '' : '?' . http_build_query($data));
  808. }
  809. /**
  810. * Parse Headers
  811. *
  812. * @access private
  813. * @param $raw_headers
  814. *
  815. * @return array
  816. */
  817. private function parseHeaders($raw_headers)
  818. {
  819. $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY);
  820. $http_headers = new CaseInsensitiveArray();
  821. $raw_headers_count = count($raw_headers);
  822. for ($i = 1; $i < $raw_headers_count; $i++) {
  823. list($key, $value) = explode(':', $raw_headers[$i], 2);
  824. $key = trim($key);
  825. $value = trim($value);
  826. // Use isset() as array_key_exists() and ArrayAccess are not compatible.
  827. if (isset($http_headers[$key])) {
  828. $http_headers[$key] .= ',' . $value;
  829. } else {
  830. $http_headers[$key] = $value;
  831. }
  832. }
  833. return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers);
  834. }
  835. /**
  836. * Parse Request Headers
  837. *
  838. * @access private
  839. * @param $raw_headers
  840. *
  841. * @return array
  842. */
  843. private function parseRequestHeaders($raw_headers)
  844. {
  845. $request_headers = new CaseInsensitiveArray();
  846. list($first_line, $headers) = $this->parseHeaders($raw_headers);
  847. $request_headers['Request-Line'] = $first_line;
  848. foreach ($headers as $key => $value) {
  849. $request_headers[$key] = $value;
  850. }
  851. return $request_headers;
  852. }
  853. /**
  854. * Parse Response
  855. *
  856. * @access private
  857. * @param $response_headers
  858. * @param $raw_response
  859. *
  860. * @return array
  861. */
  862. private function parseResponse($response_headers, $raw_response)
  863. {
  864. $response = $raw_response;
  865. if (isset($response_headers['Content-Type'])) {
  866. if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) {
  867. $json_decoder = $this->jsonDecoder;
  868. if (is_callable($json_decoder)) {
  869. $response = $json_decoder($response);
  870. }
  871. } elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) {
  872. $xml_obj = @simplexml_load_string($response);
  873. if (!($xml_obj === false)) {
  874. $response = $xml_obj;
  875. }
  876. }
  877. }
  878. return array($response, $raw_response);
  879. }
  880. /**
  881. * Parse Response Headers
  882. *
  883. * @access private
  884. * @param $raw_response_headers
  885. *
  886. * @return array
  887. */
  888. private function parseResponseHeaders($raw_response_headers)
  889. {
  890. $response_header_array = explode("\r\n\r\n", $raw_response_headers);
  891. $response_header = '';
  892. for ($i = count($response_header_array) - 1; $i >= 0; $i--) {
  893. if (stripos($response_header_array[$i], 'HTTP/') === 0) {
  894. $response_header = $response_header_array[$i];
  895. break;
  896. }
  897. }
  898. $response_headers = new CaseInsensitiveArray();
  899. list($first_line, $headers) = $this->parseHeaders($response_header);
  900. $response_headers['Status-Line'] = $first_line;
  901. foreach ($headers as $key => $value) {
  902. $response_headers[$key] = $value;
  903. }
  904. return $response_headers;
  905. }
  906. /**
  907. * Http Build Multi Query
  908. *
  909. * @access public
  910. * @param $data
  911. * @param $key
  912. *
  913. * @return string
  914. */
  915. public static function http_build_multi_query($data, $key = null)
  916. {
  917. $query = array();
  918. if (empty($data)) {
  919. return $key . '=';
  920. }
  921. $is_array_assoc = self::is_array_assoc($data);
  922. foreach ($data as $k => $value) {
  923. if (is_string($value) || is_numeric($value)) {
  924. $brackets = $is_array_assoc ? '[' . $k . ']' : '[]';
  925. $query[] = urlencode($key === null ? $k : $key . $brackets) . '=' . rawurlencode($value);
  926. } elseif (is_array($value)) {
  927. $nested = $key === null ? $k : $key . '[' . $k . ']';
  928. $query[] = self::http_build_multi_query($value, $nested);
  929. }
  930. }
  931. return implode('&', $query);
  932. }
  933. /**
  934. * Is Array Assoc
  935. *
  936. * @access public
  937. * @param $array
  938. *
  939. * @return boolean
  940. */
  941. public static function is_array_assoc($array)
  942. {
  943. return (bool)count(array_filter(array_keys($array), 'is_string'));
  944. }
  945. /**
  946. * Is Array Multidim
  947. *
  948. * @access public
  949. * @param $array
  950. *
  951. * @return boolean
  952. */
  953. public static function is_array_multidim($array)
  954. {
  955. if (!is_array($array)) {
  956. return false;
  957. }
  958. return (bool)count(array_filter($array, 'is_array'));
  959. }
  960. }
  961. ?>