Merge pull request #3643 from davecivicrm/CRM-14940a
[civicrm-core.git] / CRM / Utils / HttpClient.php
index da84a25f2b38eb6a104030ca61eb0d607c43371e..3148c536058f9e9538ae72670abd21aef5991d1c 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 4.3                                                |
+ | CiviCRM version 4.5                                                |
  +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2013                                |
+ | Copyright CiviCRM LLC (c) 2004-2014                                |
  +--------------------------------------------------------------------+
  | This file is a part of CiviCRM.                                    |
  |                                                                    |
 */
 
 /**
- * This class handles downloads of remotely-provided extensions
+ * This class handles HTTP downloads
+ *
+ * FIXME: fetch() and get() report errors differently -- e.g.
+ * fetch() returns fatal and get() returns an error code. Should
+ * refactor both (or get a third-party HTTP library) but don't
+ * want to deal with that so late in the 4.3 dev cycle.
  *
  * @package CRM
- * @copyright CiviCRM LLC (c) 2004-2013
+ * @copyright CiviCRM LLC (c) 2004-2014
  * $Id$
  *
  */
@@ -39,6 +44,33 @@ class CRM_Utils_HttpClient {
   const STATUS_WRITE_ERROR = 'write-error';
   const STATUS_DL_ERROR = 'dl-error';
 
+  /**
+   * @var CRM_Utils_HttpClient
+   */
+  protected static $singleton;
+
+  /**
+   * @var int|NULL seconds; or NULL to use system default
+   */
+  protected $connectionTimeout;
+
+  /**
+   * @return CRM_Utils_HttpClient
+   */
+  public static function singleton() {
+    if (!self::$singleton) {
+      self::$singleton = new CRM_Utils_HttpClient();
+    }
+    return self::$singleton;
+  }
+
+  /**
+   * @param null $connectionTimeout
+   */
+  public function __construct($connectionTimeout = NULL) {
+    $this->connectionTimeout = $connectionTimeout;
+  }
+
   /**
    * Download the remote zipfile.
    *
@@ -46,33 +78,17 @@ class CRM_Utils_HttpClient {
    * @param string $localFile path at which to store the .zip file
    * @return STATUS_OK|STATUS_WRITE_ERROR|STATUS_DL_ERROR
    */
-  public static function fetch($remoteFile, $localFile) {
-    require_once 'CA/Config/Curl.php';
-    $caConfig = CA_Config_Curl::probe(array(
-      'verify_peer' => (bool) CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL', NULL, TRUE)
-    ));
-
+  public function fetch($remoteFile, $localFile) {
     // Download extension zip file ...
     if (!function_exists('curl_init')) {
       CRM_Core_Error::fatal('Cannot install this extension - curl is not installed!');
     }
+
+    list($ch, $caConfig) = $this->createCurl($remoteFile);
     if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
       CRM_Core_Error::fatal('Cannot install this extension - does not support SSL');
     }
 
-    //setting the curl parameters.
-    $ch = curl_init();
-    curl_setopt($ch, CURLOPT_URL, $remoteFile);
-    curl_setopt($ch, CURLOPT_HEADER, FALSE);
-    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
-    curl_setopt($ch, CURLOPT_VERBOSE, 0);
-    if (preg_match('/^https:/', $remoteFile)) {
-      curl_setopt_array($ch, $caConfig->toCurlOptions());
-    }
-
-    //follow redirects
-    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
-
     $fp = @fopen($localFile, "w");
     if (!$fp) {
       CRM_Core_Session::setStatus(ts('Unable to write to %1.<br />Is the location writable?', array(1 => $localFile)), ts('Write Error'), 'error');
@@ -94,4 +110,114 @@ class CRM_Utils_HttpClient {
 
     return self::STATUS_OK;
   }
+
+  /**
+   * Send an HTTP GET for a remote resource
+   *
+   * @param string $remoteFile URL of remote file
+   * @return array array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string)
+   */
+  public function get($remoteFile) {
+    // Download extension zip file ...
+    if (!function_exists('curl_init')) {
+      // CRM-13805
+      CRM_Core_Session::setStatus(
+        ts('As a result, actions like retrieving the CiviCRM news feed will fail. Talk to your server administrator or hosting company to rectify this.'),
+        ts('Curl is not installed')
+      );
+      return array(self::STATUS_DL_ERROR, NULL);
+    }
+
+    list($ch, $caConfig) = $this->createCurl($remoteFile);
+
+    if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
+      //CRM_Core_Error::fatal('Cannot install this extension - does not support SSL');
+      return array(self::STATUS_DL_ERROR, NULL);
+    }
+
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    $data = curl_exec($ch);
+    if (curl_errno($ch)) {
+      return array(self::STATUS_DL_ERROR, $data);
+    }
+    else {
+      curl_close($ch);
+    }
+
+    return array(self::STATUS_OK, $data);
+  }
+
+  /**
+   * Send an HTTP POST for a remote resource
+   *
+   * @param string $remoteFile URL of a .zip file
+   * @param $params
+   *
+   * @internal param string $localFile path at which to store the .zip file
+   * @return array array(0 => STATUS_OK|STATUS_DL_ERROR, 1 => string)
+   */
+  public function post($remoteFile, $params) {
+    // Download extension zip file ...
+    if (!function_exists('curl_init')) {
+      //CRM_Core_Error::fatal('Cannot install this extension - curl is not installed!');
+      return array(self::STATUS_DL_ERROR, NULL);
+    }
+
+    list($ch, $caConfig) = $this->createCurl($remoteFile);
+
+    if (preg_match('/^https:/', $remoteFile) && !$caConfig->isEnableSSL()) {
+      //CRM_Core_Error::fatal('Cannot install this extension - does not support SSL');
+      return array(self::STATUS_DL_ERROR, NULL);
+    }
+
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_POST,count($params));
+    curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
+    $data = curl_exec($ch);
+    if (curl_errno($ch)) {
+      return array(self::STATUS_DL_ERROR, $data);
+    }
+    else {
+      curl_close($ch);
+    }
+
+    return array(self::STATUS_OK, $data);
+  }
+
+  /**
+   * @param string $remoteFile
+   * @return array (0 => resource, 1 => CA_Config_Curl)
+   */
+  protected function createCurl($remoteFile) {
+    require_once 'CA/Config/Curl.php';
+    $caConfig = CA_Config_Curl::probe(array(
+      'verify_peer' => (bool) CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL', NULL, TRUE)
+    ));
+
+    $ch = curl_init();
+    curl_setopt($ch, CURLOPT_URL, $remoteFile);
+    curl_setopt($ch, CURLOPT_HEADER, FALSE);
+    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
+    curl_setopt($ch, CURLOPT_VERBOSE, 0);
+    if ($this->isRedirectSupported()) {
+      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
+    }
+    if ($this->connectionTimeout !== NULL) {
+      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout);
+    }
+    if (preg_match('/^https:/', $remoteFile) && $caConfig->isEnableSSL()) {
+      curl_setopt_array($ch, $caConfig->toCurlOptions());
+    }
+
+    return array($ch, $caConfig);
+  }
+
+  /**
+   * @return bool
+   */
+  public function isRedirectSupported() {
+    return (ini_get('open_basedir') == '') && (ini_get('safe_mode') == 'Off' || ini_get('safe_mode') === FALSE);
+  }
+
 }