Response.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. namespace Grav\Common\GPM;
  3. class Response
  4. {
  5. /**
  6. * The callback for the progress
  7. * @var callable Either a function or callback in array notation
  8. */
  9. public static $callback = null;
  10. /**
  11. * Which method to use for HTTP calls, can be 'curl', 'fopen' or 'auto'. Auto is default and fopen is the preferred method
  12. * @var string
  13. */
  14. private static $method = 'auto';
  15. /**
  16. * Default parameters for `curl` and `fopen`
  17. * @var array
  18. */
  19. private static $defaults = [
  20. 'curl' => [
  21. CURLOPT_REFERER => 'Grav GPM',
  22. CURLOPT_USERAGENT => 'Grav GPM',
  23. CURLOPT_RETURNTRANSFER => true,
  24. CURLOPT_FOLLOWLOCATION => true,
  25. CURLOPT_TIMEOUT => 15,
  26. CURLOPT_HEADER => false,
  27. /**
  28. * Example of callback parameters from within your own class
  29. */
  30. //CURLOPT_NOPROGRESS => false,
  31. //CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
  32. ],
  33. 'fopen' => [
  34. 'method' => 'GET',
  35. 'user_agent' => 'Grav GPM',
  36. 'max_redirects' => 5,
  37. 'follow_location' => 1,
  38. 'timeout' => 15,
  39. /**
  40. * Example of callback parameters from within your own class
  41. */
  42. //'notification' => [$this, 'progress']
  43. ]
  44. ];
  45. /**
  46. * Sets the preferred method to use for making HTTP calls.
  47. * @param string $method Default is `auto`
  48. */
  49. public static function setMethod($method = 'auto')
  50. {
  51. if (!in_array($method, ['auto', 'curl', 'fopen'])) {
  52. $method = 'auto';
  53. }
  54. self::$method = $method;
  55. return new self();
  56. }
  57. /**
  58. * Makes a request to the URL by using the preferred method
  59. * @param string $uri URL to call
  60. * @param array $options An array of parameters for both `curl` and `fopen`
  61. * @return string The response of the request
  62. */
  63. public static function get($uri = '', $options = [], $callback = null)
  64. {
  65. if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
  66. throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
  67. }
  68. $options = array_replace_recursive(self::$defaults, $options);
  69. $method = 'get' . ucfirst(strtolower(self::$method));
  70. self::$callback = $callback;
  71. return static::$method($uri, $options, $callback);
  72. }
  73. /**
  74. * Progress normalized for cURL and Fopen
  75. * @param args Variable length of arguments passed in by stream method
  76. * @return array Normalized array with useful data.
  77. * Format: ['code' => int|false, 'filesize' => bytes, 'transferred' => bytes, 'percent' => int]
  78. */
  79. public static function progress()
  80. {
  81. static $filesize = null;
  82. $args = func_get_args();
  83. $isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl';
  84. $notification_code = !$isCurlResource ? $args[0] : false;
  85. $bytes_transferred = $isCurlResource ? $args[2] : $args[4];
  86. if ($isCurlResource) {
  87. $filesize = $args[1];
  88. } elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
  89. $filesize = $args[5];
  90. }
  91. if ($bytes_transferred > 0) {
  92. if ($notification_code == STREAM_NOTIFY_PROGRESS|STREAM_NOTIFY_COMPLETED || $isCurlResource) {
  93. $progress = [
  94. 'code' => $notification_code,
  95. 'filesize' => $filesize,
  96. 'transferred' => $bytes_transferred,
  97. 'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1)
  98. ];
  99. if (self::$callback !== null) {
  100. call_user_func_array(self::$callback, [$progress]);
  101. }
  102. }
  103. }
  104. }
  105. /**
  106. * Checks if cURL is available
  107. * @return boolean
  108. */
  109. public static function isCurlAvailable()
  110. {
  111. return function_exists('curl_version');
  112. }
  113. /**
  114. * Checks if the remote fopen request is enabled in PHP
  115. * @return boolean
  116. */
  117. public static function isFopenAvailable()
  118. {
  119. return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
  120. }
  121. /**
  122. * Automatically picks the preferred method
  123. * @return string The response of the request
  124. */
  125. private static function getAuto()
  126. {
  127. if (self::isFopenAvailable()) {
  128. return self::getFopen(func_get_args());
  129. }
  130. if (self::isCurlAvailable()) {
  131. return self::getCurl(func_get_args());
  132. }
  133. }
  134. /**
  135. * Starts a HTTP request via cURL
  136. * @return string The response of the request
  137. */
  138. private static function getCurl()
  139. {
  140. $args = func_get_args();
  141. $args = count($args) > 1 ? $args : array_shift($args);
  142. $uri = $args[0];
  143. $options = $args[1];
  144. $callback = $args[2];
  145. $ch = curl_init($uri);
  146. curl_setopt_array($ch, $options['curl']);
  147. if ($callback) {
  148. curl_setopt_array(
  149. $ch,
  150. [
  151. CURLOPT_NOPROGRESS => false,
  152. CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
  153. ]
  154. );
  155. }
  156. $response = curl_exec($ch);
  157. if ($errno = curl_errno($ch)) {
  158. $error_message = curl_strerror($errno);
  159. throw new \RuntimeException("cURL error ({$errno}):\n {$error_message}");
  160. }
  161. curl_close($ch);
  162. return $response;
  163. }
  164. /**
  165. * Starts a HTTP request via fopen
  166. * @return string The response of the request
  167. */
  168. private static function getFopen()
  169. {
  170. if (count($args = func_get_args()) == 1) {
  171. $args = $args[0];
  172. }
  173. $uri = $args[0];
  174. $options = $args[1];
  175. $callback = $args[2];
  176. if ($callback) {
  177. $options['fopen']['notification'] = ['self', 'progress'];
  178. }
  179. $stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
  180. $content = @file_get_contents($uri, false, $stream);
  181. if ($content === false) {
  182. throw new \RuntimeException("Error while trying to download '$uri'");
  183. }
  184. return $content;
  185. }
  186. }