dev/core#3160 fix inability to import 'just contactid' and add to group
[civicrm-core.git] / CRM / Utils / HttpClient.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * This class handles HTTP downloads
14 *
15 * FIXME: fetch() and get() report errors differently -- e.g.
16 * fetch() returns fatal and get() returns an error code. Should
17 * refactor both (or get a third-party HTTP library) but don't
18 * want to deal with that so late in the 4.3 dev cycle.
19 *
20 * @package CRM
21 * @copyright CiviCRM LLC https://civicrm.org/licensing
22 */
23 class CRM_Utils_HttpClient {
24
25 const STATUS_OK = 'ok';
26 const STATUS_WRITE_ERROR = 'write-error';
27 const STATUS_DL_ERROR = 'dl-error';
28
29 /**
30 * @var CRM_Utils_HttpClient
31 */
32 protected static $singleton;
33
34 /**
35 * @var int|null
36 * seconds; or NULL to use system default
37 */
38 protected $connectionTimeout;
39
40 /**
41 * @return CRM_Utils_HttpClient
42 */
43 public static function singleton() {
44 if (!self::$singleton) {
45 self::$singleton = new CRM_Utils_HttpClient();
46 }
47 return self::$singleton;
48 }
49
50 /**
51 * @param int|null $connectionTimeout
52 * seconds; or NULL to use system default
53 */
54 public function __construct($connectionTimeout = NULL) {
55 $this->connectionTimeout = $connectionTimeout;
56 }
57
58 /**
59 * Download the remote zipfile.
60 *
61 * @param string $remoteFile
62 * URL of a .zip file.
63 * @param string $localFile
64 * Path at which to store the .zip file.
65 * @return STATUS_OK|STATUS_WRITE_ERROR|STATUS_DL_ERROR
66 *
67 * @throws CRM_Core_Exception
68 */
69 public function fetch($remoteFile, $localFile) {
70 // Download extension zip file ...
71 if (!function_exists('curl_init')) {
72 throw new CRM_Core_Exception('Cannot install this extension - curl is not installed!');
73 }
74
75 list($ch, $caConfig) = $this->createCurl($remoteFile);
76 if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
77 throw new CRM_Core_Exception('Cannot install this extension - does not support SSL');
78 }
79
80 $fp = @fopen($localFile, "w");
81 if (!$fp) {
82 return self::STATUS_WRITE_ERROR;
83 }
84 curl_setopt($ch, CURLOPT_FILE, $fp);
85
86 curl_exec($ch);
87 if (curl_errno($ch)) {
88 return self::STATUS_DL_ERROR;
89 }
90 else {
91 curl_close($ch);
92 }
93
94 fclose($fp);
95
96 return self::STATUS_OK;
97 }
98
99 /**
100 * Send an HTTP GET for a remote resource.
101 *
102 * @param string $remoteFile
103 * URL of remote file.
104 * @return array
105 * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string)
106 */
107 public function get($remoteFile) {
108 // Download extension zip file ...
109 if (!function_exists('curl_init')) {
110 // CRM-13805
111 CRM_Core_Session::setStatus(
112 ts('As a result, actions like retrieving the CiviCRM news feed will fail. Talk to your server administrator or hosting company to rectify this.'),
113 ts('Curl is not installed')
114 );
115 return [self::STATUS_DL_ERROR, NULL];
116 }
117
118 list($ch, $caConfig) = $this->createCurl($remoteFile);
119
120 if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
121 return [self::STATUS_DL_ERROR, NULL];
122 }
123
124 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
125 $data = curl_exec($ch);
126 if (curl_errno($ch)) {
127 return [self::STATUS_DL_ERROR, $data];
128 }
129 else {
130 curl_close($ch);
131 }
132
133 return [self::STATUS_OK, $data];
134 }
135
136 /**
137 * Send an HTTP POST for a remote resource.
138 *
139 * @param string $remoteFile
140 * URL of a .zip file.
141 * @param array $params
142 *
143 * @return array
144 * array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string)
145 */
146 public function post($remoteFile, $params) {
147 // Download extension zip file ...
148 if (!function_exists('curl_init')) {
149 return [self::STATUS_DL_ERROR, NULL];
150 }
151
152 list($ch, $caConfig) = $this->createCurl($remoteFile);
153
154 if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
155 return [self::STATUS_DL_ERROR, NULL];
156 }
157
158 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
159 curl_setopt($ch, CURLOPT_POST, TRUE);
160 curl_setopt($ch, CURLOPT_POST, count($params));
161 curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
162 $data = curl_exec($ch);
163 if (curl_errno($ch)) {
164 return [self::STATUS_DL_ERROR, $data];
165 }
166 else {
167 curl_close($ch);
168 }
169
170 return [self::STATUS_OK, $data];
171 }
172
173 /**
174 * @param string $remoteFile
175 * @return array
176 * (0 => resource, 1 => CA_Config_Curl)
177 */
178 protected function createCurl($remoteFile) {
179 $caConfig = CA_Config_Curl::probe([
180 'verify_peer' => (bool) Civi::settings()->get('verifySSL'),
181 ]);
182
183 $ch = curl_init();
184 curl_setopt($ch, CURLOPT_URL, $remoteFile);
185 curl_setopt($ch, CURLOPT_HEADER, FALSE);
186 curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
187 curl_setopt($ch, CURLOPT_VERBOSE, 0);
188 if ($this->isRedirectSupported()) {
189 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
190 }
191 if ($this->connectionTimeout !== NULL) {
192 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout);
193 }
194 if (preg_match('/^https:/', $remoteFile) && $caConfig->isEnableSSL()) {
195 curl_setopt_array($ch, $caConfig->toCurlOptions());
196 }
197
198 return [$ch, $caConfig];
199 }
200
201 /**
202 * @return bool
203 */
204 public function isRedirectSupported() {
205 return (ini_get('open_basedir') == '');
206 }
207
208 }