Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 6 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
Sag
===

Note: I have not maintained this project for years since I no longer use
CouchDB or PHP. I assume it still works with CouchDB, but haven't been active
in the community for a while and won't be testing or responding to tickets.

Version %VERSION%

http://www.saggingcouch.com

Sag is a PHP library for working with CouchDB. It is designed to not force any
particular programming method on its users - you just pass PHP objects, and get
stdClass objects and Exceptions back. This makes it trivial to incorporate Sag
into your application, build different functionality on top of it, and expand
Sag to incorporate new CouchDB functionality.

Compatability
This is a *fork* of the original SAG Library that was hosted on saggincouch.com.

Compatibility
-------------

Each Sag release is tested with an automated testing suite against all the
combinations of:

- PHP 5.5.x

- CouchDB 1.6.x
- PHP 7.x

- Cloudant
- CouchDB 3.x

Lower versions of CouchDB and PHP will likely work with Sag, but they are not
officially supported, so your mileage may vary.

If you are running pre-1.5.1 CouchDB (important security fix) or pre-5.3 PHP,
then you probably want to look into updating your environment.

Error Handling
--------------
Expand Down Expand Up @@ -142,8 +132,7 @@ as a stdClass.
Functions
---------

Detailed documentation of the functions and API are available at
http://www.saggingcouch.com/documentation.php.
_to be updated_

License
-------
Expand All @@ -153,8 +142,3 @@ LICENSE for more information.

Copyright information is in the NOTICE file.

More?
-----

See http://www.saggingcouch.com for more detailed information, bug reporting,
planned features, etc.
171 changes: 166 additions & 5 deletions src/Sag.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@
/**
* The Sag class provides the core functionality for talking to CouchDB.
*
* @version %VERSION%
* @version 0.9.1
* @package Core
*/
class Sag {

/**
* @var string Used by login() to use HTTP Basic Authentication.
* @static
*/
public static $VERSION = "0.9.1";

/**
* @var string Used by login() to use HTTP Basic Authentication.
* @static
Expand Down Expand Up @@ -210,6 +217,32 @@ public function login($user, $pass, $type = null) {
//should never reach this line
throw new SagException("Unknown auth type for login().");
}


/**
* Sets the internal (private) variables to use cookie authentication in all subsequent calls to
* $this->procPacket(), which gets used by all worker functions in Sag.
*
* @param string $cookieValue the cookie value $this->authSession from $this->login ($username, $password, Sag::$AUTH_COOKIE);
*/
public function loginByCookie ($cookieValue=null) {
if (is_null($cookieValue)) {
throw new SagException('loginByCookie() expects a string parameter which should be the cookie (the return value) generated by login($username, $password, Sag::$AUTH_COOKIE).');
}

$this->authType = Sag::$AUTH_COOKIE;
$this->authSession = $cookieValue;
//echo 'loginByCookie():';var_dump ($this->authType);
}

public function checkSession () {
$res = $this->procPacket(
'GET',
'/_session'
);

echo 'high level checkSession() results=<pre>'; var_dump ($res); echo '</pre>'.PHP_EOL;
}

/**
* Get current session information on the server with /_session.
Expand Down Expand Up @@ -379,6 +412,7 @@ public function put($id, $data)

$toSend = (is_string($data)) ? $data : json_encode($data);
$id = urlencode($id);
$id = str_replace("%2F", "/", $id); /** Url Encoding a / confuses Couch **/

$url = "/{$this->db}/$id";
$response = $this->procPacket('PUT', $url, $toSend);
Expand Down Expand Up @@ -448,6 +482,32 @@ public function post($data, $path = null) {

return $this->procPacket('POST', "/{$this->db}{$path}", $data);
}

/**
* Makes a request to the _changes feed.
*
* @param array $parameters The feed parameters
* @param mixed $request The request available in the filter function
*
* @return mixed
*/
public function changes($parameters = array(), $request = null) {
if(!$this->db) {
throw new SagException('No database specified');
}

if(!is_null($request) && (!is_string($request) && !is_object($request) && !is_array($request))) {
throw new SagException('changes() needs an object for request.');
}

if(!is_string($request)) {
$request = json_encode($request);
}

$queryString = (count($parameters) > 0 ? "?" : "").http_build_query($parameters);

return $this->procPacket('POST', "/{$this->db}/_changes{$queryString}", $request);
}

/**
* Bulk pushes documents to the database.
Expand Down Expand Up @@ -688,6 +748,41 @@ public function generateIDs($num = 10) {
}

/**
* Uses CouchDB to generate IDs with custom prefix ad separator.
*
* @param int $num The number of IDs to generate (>= 0). Defaults to 10.
* @param string $prefix The prefix for the ID
* @param string $separator the separatore between prefix and ID
* @return array
*/
public function generateIDsCustom($num = 10, $prefix = "", $separator = ":") {
$ids = array();

$docs = $this->generateIDs($num);
if ($docs->status == 200) {
if (isset($docs->body->uuids) && is_array($docs->body->uuids)) {
foreach ($docs->body->uuids as $uuid) {
$ids[] = $prefix . $separator . $uuid;
}
}
}

return $ids;
}

/**
* Uses CouchDB to generate only one ID with custom prefix ad separator.
*
* @param string $prefix The prefix for the ID
* @param string $separator the separatore between prefix and ID
* @return string
*/
public function generateIDCustom($prefix, $separator = ":") {
$ids = $this->generateIDsCustom(1, $prefix, $separator);
return $ids[0];
}

/**
* Creates a database with the specified name.
*
* @param string $name The name of the database you want to create.
Expand Down Expand Up @@ -732,15 +827,16 @@ public function deleteDatabase($name) {
* @param mixed $filterQueryParams An object or associative array of
* parameters to be passed to the filter function via query_params. Only used
* if $filter is set.
* @param array $doc_ids Array of document IDs to be synchronized
*
* @return mixed
*/
public function replicate($src, $target, $continuous = false, $createTarget = null, $filter = null, $filterQueryParams = null) {
if(empty($src) || !is_string($src)) {
public function replicate($src, $target, $continuous = false, $createTarget = null, $filter = null, $filterQueryParams = null, $doc_ids = null) {
if(empty($src) || (!is_string($src) && !is_object($src))) {
throw new SagException('replicate() is missing a source to replicate from.');
}

if(empty($target) || !is_string($target)) {
if(empty($target) || (!is_string($target)) && !is_object($target)) {
throw new SagException('replicate() is missing a target to replicate to.');
}

Expand All @@ -762,6 +858,10 @@ public function replicate($src, $target, $continuous = false, $createTarget = nu
}
}

if (isset($doc_ids) && !is_array($doc_ids)) {
throw new SagException('Doc IDs needs to be an array.');
}

$data = new stdClass();
$data->source = $src;
$data->target = $target;
Expand All @@ -786,6 +886,10 @@ public function replicate($src, $target, $continuous = false, $createTarget = nu
}
}

if ($doc_ids) {
$data->doc_ids = $doc_ids;
}

return $this->procPacket('POST', '/_replicate', json_encode($data));
}

Expand Down Expand Up @@ -1063,6 +1167,60 @@ public function getPathPrefix() {
return $this->pathPrefix;
}

/**
* Interface to /db/_index
*
* @param mixed $data (see https://docs.couchdb.org/en/stable/api/database/find.html#db-index)
* $data = array (
* 'index' => array(
* 'fields' => array ('foo')
* ),
* 'name' => 'foo-index',
* 'type' => 'json'
* );
*
*
* @return Sag Returns $this->procPacket() results.
*/
public function setIndex($data) {
if (!is_string($data)) $data = json_encode($data);
return $this->procPacket ('POST', '/'.$this->db.'/_index', $data);
}

/**
* Interface to /db/_find
*
* @param mixed $data
* $data = array (
* 'selector' => array(
* 'shortened' => $newID
* ),
* 'fields' => array(
* '_id', 'creator', 'destination', 'shortened'
* )
* );
*
*
* @return Sag Returns $this->procPacket() results.
*/
public function find($data) {
if (!is_string($data)) $data = json_encode($data);
return $this->procPacket ('POST', '/'.$this->db.'/_find', $data);
}

/**
* Interface to /db/_security
*
* @param mixed $data
* $data = '{ "admins": { "names": [], "roles": ["guests"] }, "members": { "names": ["Administrator"], "roles": ["guests"] } }';
*
* @return Sag Returns $this->procPacket() results.
*/
public function setSecurity($data) {
if (!is_string($data)) $data = json_encode($data);
return $this->procPacket ('PUT', '/'.$this->db.'/_security', $data);
}

// The main driver - does all the socket and protocol work.
private function procPacket($method, $url, $data = null, $headers = array()) {
/*
Expand Down Expand Up @@ -1093,7 +1251,7 @@ private function procPacket($method, $url, $data = null, $headers = array()) {

// Build the request packet.
$headers["Host"] = "{$this->host}:{$this->port}";
$headers["User-Agent"] = "Sag/%VERSION%";
$headers["User-Agent"] = "Sag/" . self::$VERSION;

/*
* This prevents some unRESTful requests, such as inline attachments in
Expand All @@ -1103,13 +1261,16 @@ private function procPacket($method, $url, $data = null, $headers = array()) {
$headers['Accept'] = 'application/json';

//usernames and passwords can be blank
//$dbg = [ '$this->authType' => $this->authType, 'Sag::$AUTH_BASIC' => Sag::$AUTH_BASIC, 'Sag::$AUTH_COOKIE' => Sag::$AUTH_COOKIE ];
//var_dump ($dbg);
if($this->authType == Sag::$AUTH_BASIC && (isset($this->user) || isset($this->pass))) {
$headers["Authorization"] = 'Basic '.base64_encode("{$this->user}:{$this->pass}");
}
elseif($this->authType == Sag::$AUTH_COOKIE && isset($this->authSession)) {
$headers['Cookie'] = array( 'AuthSession' => $this->authSession );
$headers['X-CouchDB-WWW-Authenticate'] = 'Cookie';
}
//var_dump ($headers);

if(is_array($this->globalCookies) && sizeof($this->globalCookies)) {
//might have been set before by auth handling
Expand Down
9 changes: 7 additions & 2 deletions src/httpAdapters/SagCURLHTTPAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public function procPacket($method, $url, $data = null, $reqHeaders = array(), $
$opts[CURLOPT_POSTFIELDS] = $data;
}

if($method == 'GET') {
$opts[CURLOPT_ENCODING] = "";
}

// special considerations for HEAD requests
if($method == 'HEAD') {
$opts[CURLOPT_NOBODY] = true;
Expand Down Expand Up @@ -92,8 +96,9 @@ public function procPacket($method, $url, $data = null, $reqHeaders = array(), $

curl_reset($this->ch);
curl_setopt_array($this->ch, $opts);

//echo 'curl options = '; var_dump ($opts); echo PHP_EOL;
$chResponse = curl_exec($this->ch);
//echo 'curl response = '; var_dump ($chResponse);

if($chResponse !== false) {
// prepare the response object
Expand Down Expand Up @@ -122,7 +127,7 @@ public function procPacket($method, $url, $data = null, $reqHeaders = array(), $
else {
$line = explode(':', $respHeaders[$i], 2);
$line[0] = strtolower($line[0]);
$response->headers->$line[0] = ltrim($line[1]);
$response->headers->{$line[0]} = ltrim($line[1]);

if($line[0] == 'set-cookie') {
$response->cookies = $this->parseCookieString($line[1]);
Expand Down
3 changes: 2 additions & 1 deletion src/httpAdapters/SagHTTPAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ protected function makeResult($response, $method) {
if(
$method != 'HEAD' &&
isset($response->headers->{'content-length'}) &&
! isset($response->headers->{'content-encoding'}) &&
strlen($response->body) != $response->headers->{'content-length'}
) {
throw new SagException('Unexpected end of packet.');
Expand Down Expand Up @@ -67,7 +68,7 @@ protected function makeResult($response, $method) {
) {
$json = json_decode($response->body);

if(isset($json)) {
if(isset($json) && $json !== FALSE) {
if(!empty($json->error)) {
throw new SagCouchException("{$json->error} ({$json->reason})", $response->headers->_HTTP->status);
}
Expand Down
Loading