<?php
/**
* FastCurl (PHP object-oriented wrapper for {@link http://curl.haxx.se/ cURL} inspired on {@link http://jamessocol.com/projects/oocurl.php OOCurl})
*
* Copyright (c) 2010 Antonio López Vivar
*
* LICENSE:
*
* This library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any
* later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @category Library
* @package FastCurl
* @author Antonio López Vivar <hide@address.com>
* @copyright 2010 Antonio López Vivar
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @version 1.0
*/
require_once('FastCurlMulti.inc');
/**
* @property mixed $_ch cURL handle
* @property array $_fastcurlopt Assoc array with CURLOPTs that have been set in cURL handle.
* @property bool $_multilock Indicates if the object is associated to a FastCurlMulti container.
* @property string $_response Keeps the response of last curl_exec() operation.
* @property int $_exec_mode self::FCURL_EXEC_BODY: body, self::FCURL_EXEC_HEADERS: headers, self::FCURL_EXEC_HEADERSBODY: headers + body
*/
class FastCurl implements iFastCurl
{
const VERSION='1.0';
const FCURL_EXEC_BODY=1;
const FCURL_EXEC_HEADERS=2;
const FCURL_EXEC_HEADERSBODY=3;
const FCURL_TIMEOUT=10;
private $_ch=NULL;
private $_fastcurlopt=NULL;
private $_multilock=NULL;
private $_response=NULL;
private $_exec_mode=NULL;
/**
* (All keys in arrays are optional and case/order insensitive).
*
* @param string $url
* @param string $referrer
* @param array $proxy array('proxy' => host:port, etc...)
* @param array $exec_mode array('_exec_mode' => value, 'followlocation' => value, 'autoreferer' => value, 'returntransfer' => value)
* @param array $cookies array('cookiefile' => value, 'cookiejar' => value, etc...)
* @param array $custom_headers array('headername' => 'headervalue', ...)
* @param int $timeout CURLOPT_TIMEOUT
* @param array $ssl array('ssl_verifyhost' => value, 'ssl_verifypeer' => value, etc...) By default you can use HTTPS without caring about certs, keys, etc...
* @return FastCurl $this
*/
public function __construct($url=NULL, $referrer=NULL, Array $proxy=NULL, Array $exec_mode=NULL, Array $cookies=NULL, Array $custom_headers=NULL, $timeout=self::FCURL_TIMEOUT, Array $ssl=NULL)
{
if(!extension_loaded('curl'))
throw new ErrorException("cURL library is not loaded. Please recompile PHP with the cURL library.");
return $this->reset($url, $referrer, $proxy, $exec_mode, $cookies, $custom_headers, $timeout, $ssl);
}
/**
* Use this function to FULL RESET a FastCurl object. (All keys in arrays are optional and case/order insensitive).
*
* @param string $url
* @param string $referrer
* @param array $proxy array('proxy' => host:port, etc...)
* @param array $exec_mode array('_exec_mode' => value, 'followlocation' => value, 'autoreferer' => value, 'returntransfer' => value)
* @param array $cookies array('cookiefile' => value, 'cookiejar' => value, etc...)
* @param array $custom_headers array('headername' => 'headervalue', ...)
* @param int $timeout CURLOPT_TIMEOUT
* @param array $ssl array('ssl_verifyhost' => value, 'ssl_verifypeer' => value, etc...) By default you can use HTTPS without caring about certs, keys, etc...
* @return FastCurl $this
*/
public function reset($url=NULL, $referrer=NULL, Array $proxy=NULL, Array $exec_mode=NULL, Array $cookies=NULL, Array $custom_headers=NULL, $timeout=self::FCURL_TIMEOUT, Array $ssl=NULL)
{
if($this->_ch)
curl_close($this->_ch);
$this->_ch=curl_init();
$this->_fastcurlopt=array();
$this->_response=NULL;
$this->_multilock=NULL;
$this->failnoerror=TRUE;
$this->useragent=implode('_', array('FastCurl', self::VERSION, FastCurlMulti::VERSION));
if($exec_mode)
{
$aux=array();
foreach($exec_mode as $key => $value)
$aux[trim(strtolower($key))]=$value;
$this->set_exec_mode($aux['_exec_mode'], $aux['followlocation'], $aux['autoreferer'], $aux['returntransfer']);
}
else
$this->set_exec_mode();
$this->url=$url;
$this->referer=$referrer;
if($proxy)
{
foreach($proxy as $key => $value)
{
$key=strtolower(trim($key));
$this->$key=$value;
}
}
if($cookies)
{
foreach($cookies as $key => $value)
{
$key=strtolower(trim($key));
$this->$key=$value;
}
}
else
$this->cookiefile='cookies';
$this->httpheader=$custom_headers?$custom_headers:$this->default_headers();
$this->timeout=is_int($timeout)?$timeout:self::FCURL_TIMEOUT;
if($ssl)
{
foreach($ssl as $key => $value)
{
$key=strtolower(trim($key));
$this->$key=$value;
}
}
else
{
$this->ssl_verifyhost=0;
$this->ssl_verifypeer=FALSE;
}
return $this;
}
/**
* @return mixed $this->_ch
*/
public function get_ch()
{
return $this->_ch;
}
/**
* @param int $exec_mode self::FCURL_EXEC_BODY: body, self::FCURL_EXEC_HEADERS: headers, self::FCURL_EXEC_HEADERSBODY: headers + body
* @param bool $followloc CURLOPT_FOLLOWLOCATION
* @param bool $autoref CURLOPT_AUTOREFERER
* @param bool $ret_transfer CURLOPT_RETURNTRANSFER
* @return bool TRUE
*/
public function set_exec_mode($exec_mode=self::FCURL_EXEC_BODY, $followloc=TRUE, $autoref=TRUE, $ret_transfer=TRUE)
{
switch($exec_mode)
{
case self::FCURL_EXEC_HEADERS:
/* HEADERS */
$this->header=TRUE;
$this->nobody=TRUE;
$this->_exec_mode=$exec_mode;
break;
case self::FCURL_EXEC_BODY:
/* BODY */
$this->header=FALSE;
$this->nobody=FALSE;
$this->_exec_mode=$exec_mode;
break;
case self::FCURL_EXEC_HEADERSBODY:
/* HEADERS + BODY */
$this->header=TRUE;
$this->nobody=FALSE;
$this->_exec_mode=$exec_mode;
break;
default:
/* BODY */
$this->header=FALSE;
$this->nobody=FALSE;
$this->_exec_mode=FCURL_EXEC_BODY;
}
$this->followlocation=is_bool($followloc)?$followloc:TRUE;
$this->autoreferer=is_bool($autoref)?$autoref:TRUE;
$this->returntransfer=is_bool($ret_transfer)?$ret_transfer:TRUE;
return TRUE;
}
/**
* @return array array('_exec_mode' => $this->_exec_mode, 'followlocation' => $this->followlocation, 'autoreferer' => $this->autoreferer, 'returntransfer' => $this->returntransfer)
*/
public function get_exec_mode()
{
return $this->_exec_mode?array('_exec_mode' => $this->_exec_mode, 'followlocation' => $this->followlocation, 'autoreferer' => $this->autoreferer, 'returntransfer' => $this->returntransfer):NULL;
}
/**
* @return FastCurlMulti $this->_multilock
*/
public function get_multilock()
{
return $this->_multilock;
}
/**
* Set the fastcurlopt array. (It merges current _fastcurlopt array with the new one passed in $curlopt, overwritting values with the same key).
*
* @param array $curlopt array('CURLOPT_NAME' => VALUE, etc...) (Remember header's array format is array('hname' => 'hvalue', etc...))
* @param bool $keep_res TRUE to keep safe current $this->_response
* @return int $tot Number of CURLOPTs MODIFIED.
*/
public function set_fastcurlopt(Array $curlopt, $keep_res=TRUE)
{
$ncurlopt=array();
foreach($curlopt as $opt => $value)
{
if(!preg_match('/^CURLOPT_/i', trim($opt)))
$opt='CURLOPT_'.trim(strtoupper($opt));
else
$opt=trim(strtoupper($opt));
if(defined($opt))
$ncurlopt[$opt]=$value;
}
if(($tot=count($ncurlopt))>0)
{
if($keep_res===TRUE)
$res=$this->_response;
$this->_fastcurlopt=array_merge($this->_fastcurlopt, $ncurlopt);
foreach($this->_fastcurlopt as $opt => $value)
{
$opt=strtolower(preg_replace('/^CURLOPT_(.+)$/i', '\1', $opt));
$this->$opt=$value;
}
if($keep_res===TRUE)
$this->_response=$res;
}
return $tot;
}
/**
* @return array $this->_fastcurlopt
*/
public function get_fastcurlopt()
{
return $this->_fastcurlopt;
}
/**
* Calls curl_exec() and returns the response.
*
* @param int $exec_mode
* @param bool $followloc CURLOPT_FOLLOWLOCATION (JUST FOR THIS REQUEST)
* @param bool $autoref CURLOPT_AUTOREFERER (JUST FOR THIS REQUEST)
* @param bool $ret_transfer CURLOPT_RETURNTRANSFER (JUST FOR THIS REQUEST)
* @return string|bool $this->response FRESH server's response (or FALSE if FastCurl object is locked to a FastCurlMulti container).
*/
public function exec($exec_mode=NULL, $followloc=TRUE, $autoref=TRUE, $ret_transfer=TRUE)
{
if(!$this->_multilock)
{
if($exec_mode)
{
$exec_mode_orig=$this->get_exec_mode();
$this->set_exec_mode($exec_mode, $followloc, $autoref, $ret_transfer);
$res=curl_exec($this->_ch);
$this->set_exec_mode($exec_mode_orig['_exec_mode'], $exec_mode_orig['followlocation'], $exec_mode_orig['autoreferer'], $exec_mode_orig['returntransfer']);
$this->_response=$res;
}
else
$this->_response=curl_exec($this->_ch);
}
return $this->_multilock?FALSE:$this->_response;
}
/**
* Returns the LAST curl_exec() or curl_multi_exec() response (Every time a CURLOPT is set with $this->opt=value, the response is reset).
*
* @param string $regex REGEX pattern for looking in the response.
* @param bool $regex_all preg_match or preg_match_all?
* @param bool $trim trim before preg_match?
* @return string $this->response LAST server's response.
*/
public function fetch($regex=NULL, $regex_all=FALSE, $trim=TRUE)
{
if(!$this->_response && $this->_multilock)
$this->_response=curl_multi_getcontent($this->_ch);
else if(!$this->_response)
$this->_response=curl_exec($this->_ch);
if($regex)
{
$preg='preg_match'.($regex_all===TRUE?'_all':'');
if(!$preg($regex, $trim?trim($this->_response):$this->_response, $ret))
$ret=NULL;
}
return $regex?$ret:$this->_response;
}
/**
* @return string curl_error
*/
public function error()
{
return curl_error($this->_ch);
}
/**
* @return int curl_errno
*/
public function errno()
{
return curl_errno($this->_ch);
}
/**
* @param int $opt
* @return mixed
*/
public function info($opt=NULL)
{
return defined('CURLINFO_'.strtoupper($opt))?curl_getinfo($this->_ch, constant('CURLINFO_'.strtoupper($opt))):curl_getinfo($this->_ch);
}
/**
* Each CURLOPT is a FastCurl property (not defined), so this "magic" method is called by PHP engine every time we set a CURLOPT.
* First we search for a constant CURLOPT_$opt and if it exists we call curl_setopt() and we store the value at $this->_fastcurlopt array
* for retrieve with {@link __get()}
* @param string $opt
* @param mixed $value
* @return bool CURLOPT was set ok
*/
public function __set($opt, $value)
{
if(defined(($const = 'CURLOPT_'.strtoupper($opt))))
{
switch($const)
{
case 'CURLOPT_HTTPHEADER':
if(is_array($value) && count($value)>0)
{
$headers=array();
$nvalue=array();
foreach($value as $hname => $hvalue)
{
if(!array_key_exists(($hname=trim(strtolower($hname))), $headers))
{
$headers[$hname]=$hvalue;
$nvalue[]=$hvalue?implode(': ', array($hname, trim($hvalue))):($hname.':');
}
}
if(($ret=curl_setopt($this->_ch, constant($const), $nvalue)))
$this->_fastcurlopt[$const]=$headers;
}
break;
case 'CURLOPT_NOBODY':
if(($ret=curl_setopt($this->_ch, constant($const), $value)))
{
if($value===FALSE)
{
if($this->post)
$this->post=TRUE;
else
$this->httpget=TRUE;
}
$this->_fastcurlopt[$const]=$value;
}
break;
default:
if(($ret=curl_setopt($this->_ch, constant($const), $value)))
$this->_fastcurlopt[$const]=$value;
}
//Reset _response after some CURLOPT is set
$this->_response=NULL;
}
return $ret;
}
/**
* @param string $opt
* @return mixed CURLOPT_$opt
*/
public function __get($opt)
{
return $this->_fastcurlopt['CURLOPT_'.strtoupper($opt)];
}
/**
* @param string $opt
* @return bool CURLOPT_$opt has been set.
*/
public function __isset($opt)
{
return isset($this->_fastcurlopt['CURLOPT_'.strtoupper($opt)]);
}
/**
* Used to "lock" the object to a FastCurlMulti container. (Remember: FastCurl N:1 FastCurlMulti)
*
* @param FastCurlMulti $mh
* @return bool FastCurl object locked ok.
*/
public function lockMulti(FastCurlMulti $mh)
{
if(!$this->_multilock)
{
$mh->accept($this);
$this->_response=NULL;
$this->_multilock=$mh;
}
else
$ret=FALSE;
return $ret===FALSE?$ret:TRUE;
}
/**
* Used to "unlock" the object to a FastCurlMulti container. (Remember: FastCurl N:1 FastCurlMulti)
*
* @param FastCurlMulti $mh
* @return bool FastCurl object unlocked ok.
*/
public function unlockMulti(FastCurlMulti $mh)
{
if($this->_multilock===$mh)
{
$mh->release($this);
$this->_multilock=NULL;
}
else
$ret=FALSE;
return $ret===FALSE?$ret:TRUE;
}
/**
* If http header already exists, it will be overwritten. array('hname' => 'hvalue')
*
* @param array $header
* @return bool TRUE
*/
public function add_header(Array $header)
{
list($hname, $hvalue) = each($header);
$this->httpheader=array_merge($this->_fastcurlopt['CURLOPT_HTTPHEADER'], array(trim(strtolower($hname)) => trim($hvalue)));
return TRUE;
}
/**
* DELETES the http header.
*
* @param string $hname
* @return bool TRUE
*/
public function delete_header($hname)
{
$this->_fastcurlopt['CURLOPT_HTTPHEADER'][trim(strtolower($hname))]=NULL;
$this->httpheader=$this->_fastcurlopt['CURLOPT_HTTPHEADER'];
return TRUE;
}
/**
* Restores the default value for that http header.
*
* @param string $hname
* @return bool Header reset ok.
*/
public function reset_header($hname)
{
if(($ret=(isset($this->_fastcurlopt['CURLOPT_HTTPHEADER'][trim(strtolower($hname))]) && curl_setopt($this->_ch, CURLOPT_HTTPHEADER, array()))))
{
unset($this->_fastcurlopt['CURLOPT_HTTPHEADER'][trim(strtolower($hname))]);
$this->httpheader=$this->_fastcurlopt['CURLOPT_HTTPHEADER'];
}
return $ret;
}
/**
* Get header's value.
*
* @param string $hname
* @return string
*/
public function get_header($hname)
{
return $this->_fastcurlopt['CURLOPT_HTTPHEADER'][trim(strtolower($hname))];
}
/**
* Enable POST method.
*
* @param bool|string|array $postdata Content-Type header will be set by cURL depending on the type of this param. Urlencoded string -> application/x-www-form-urlencoded # Array -> multipart/form-data {@link http://www.php.net/manual/es/function.curl-setopt.php More info}
* @param string $ctype Custom content-type
* @param string $url Updates CURLOPT_URL
* @param string $referrer Updates CURLOPT_REFERER
* @return bool $postdata format is correct
*/
public function enable_post($postdata, $ctype=NULL, $url=NULL, $referrer=NULL)
{
if(is_bool($postdata))
{
if($postdata===FALSE)
$this->reset_header('content-type');
$this->post=$postdata;
$this->httpget=!$postdata;
}
else if(!empty($postdata))
{
if($ctype)
$this->add_header(array('content-type' => trim($ctype)));
else if(isset($this->_fastcurlopt['CURLOPT_HTTPHEADER']['content-type']))
$this->reset_header('content-type');
$this->httpget=FALSE;
$this->post=TRUE;
$this->postfields=$ctype?$postdata:(is_array($postdata)?$postdata:trim($postdata, " \r\n&"));
if($url)
$this->url=$url;
if($referrer)
$this->referer=$referrer;
}
else
$ret=FALSE;
if($this->nobody)
$this->nobody=TRUE;
return $ret===FALSE?$ret:TRUE;
}
/**
* @return array
*/
public function version()
{
$version = curl_version();
$version['fastcurl_version'] = self::VERSION;
$version['fastcurlmulti_version'] = FastCurlMulti::VERSION;
return $version;
}
/**
* Change it to return your default headers.
* @return array $headers array('header1_name' => header1_value, 'header2_name' => header2_value, ...)
*/
public function default_headers()
{
return array('Expect' => NULL);
}
/**
* @param bool HTML format output
* @param int $rows
* @param int $cols
*/
public function dump($html=TRUE, $rows=50, $cols=100)
{
if($html)
echo '<div><textarea rows="'.(is_int($rows)?$rows:50).'" cols="'.(is_int($cols)?$cols:100).'">';
var_dump($this);
if($html)
echo '</textarea></div>';
}
/**
* If the object is locked to a FastCurlMulti container we unlock it before closing curl handle.
*/
public function __destruct()
{
if($this->_multilock)
$this->unlockMulti($this->_multilock);
curl_close($this->_ch);
}
}
interface iFastCurl
{
function default_headers();
}
/* ?> */