<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents a range constraint for search.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class RangeConstraint extends AbstractConstraint
{
    private $elem; // @var string
    private $ns; // @var string
    private $attr; // @var string
    private $attrNs; // @var string
    private $datatype; // @var string
    private $facet; // @var bool
    private $buckets; // @var array
    protected $fragmentScope; // @var string
    protected $facetOptions; // @var array

    /**
     * Create a RangeConstraint object.
     *
     * @see http://docs.marklogic.com/search:search#opt-constraint
     *
     * @param string $name Constraint name.
     * @param string $datatype Constraint datatype.
     * @param string $facet Whether constraint should be faceted ('true' or 'false').
     * @param string $elem Name of the element.
     * @param string $ns Element namespace.
     * @param string $attr Attribute name.
     * @param string $attrNs An attribute namespace.
     */
    public function __construct(
        $name, $datatype, $facet = 'true', $elem, $ns = '', $attr = '', $attrNs = ''
    )
    {
        $this->elem = (string)$elem;
        $this->ns = (string)$ns;
        $this->attr = (string)$attr;
        $this->attrNs = (string)$attrNs;
        $this->datatype = (string)$datatype;
        $this->facet = (string)$facet;
        $this->buckets = array();
        parent::__construct($name);
    }

    /**
     * Add bucket objects.
     *
     * @param array|Bucket $buckets An array of bucket objects or a bucket object.
     */
    public function addBuckets($buckets) {
        if (is_array($buckets)) {
            $this->buckets = array_merge($this->buckets, $buckets);
        } else {
            $this->buckets[] = $buckets;
        }
    }

    /**
     * Get the constraint as a DOMElement object.
     *
     * @param DOMDocument $dom The DOMDocument object with which to create the element.
     */
    public function getAsElem($dom)
    {
        $constElem = $dom->createElement('constraint');
        $constElem->setAttribute('name', $this->name);
        $rangeElem = $dom->createElement('range');
        $rangeElem->setAttribute('type', $this->datatype);
        $rangeElem->setAttribute('facet', $this->facet);
        $elemElem = $dom->createElement('element');
        $elemElem->setAttribute('ns', $this->ns);
        $elemElem->setAttribute('name', $this->elem);
        $rangeElem->appendChild($elemElem);
        if($this->attr) {
            $attrElem = $dom->createElement('attribute');
            $attrElem->setAttribute('ns', $this->attrNs);
            $attrElem->setAttribute('name', $this->attr);
            $rangeElem->appendChild($attrElem);
        }
        if($this->buckets) {
            foreach ($this->buckets as $buck) {
                $buckElem = $dom->createElement('bucket');
                $buckElem->setAttribute('name', $buck->getName());
                foreach ($buck->getOptions() as $key => $val) {
                    $buckElem->setAttribute($key, $val);
                }
                $rangeElem->appendChild($buckElem);
            }
        }
        // Note: No term options for range constraints
        $this->addFacetOptions($dom, $rangeElem);
        $this->addFragmentScope($dom, $rangeElem);
        $constElem->appendChild($rangeElem);
    /* <constraint name='rating'>
               <range type='xs:decimal'>
                   <element ns='http://example.com' name='rating'/>
               </range>
           </constraint> */
        return $constElem;
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents an XML document.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class XMLDocument extends Document
{
    private $dom; // @var DOMDocument object

    /**
     * Create an XML document object.
     *
     * @param RESTClient $client A REST client object.
     * @param string $uri A document URI.
     */
    public function __construct($client, $uri = null)
    {
        parent::__construct($client, $uri);
        $this->dom = new \DOMDocument();
        $this->contentType = 'application/xml';
    }

    /**
     * Read an XML document from the database.
     *
     * @param string $uri A document URI.
     * @param array $params Optional additional parameters to pass when reading.
     * @return string The document content.
     */
    public function read($uri = null, $params = array())
    {
        $this->uri = (isset($uri)) ? (string)$uri : $this->uri;
        $params = array_merge(array('format' => 'xml'), $params);
        return parent::read($this->uri, $params);
    }

    /**
     * Write an XML document to the database.
     *
     * @param string $uri A document URI.
     * @param array $params Optional additional parameters to pass when writing.
     * @return Document $this
     */
    public function write($uri = null, $params = array())
    {
        if ($this->isValidXML($this->getContent())) {
            $this->uri = (isset($uri)) ? (string)$uri : $this->uri;
            $params = array_merge(array('format' => 'xml'), $params);
            return parent::write($this->uri, $params);
        } else {
            throw new \Exception('Attempting to write invalid XML content');
        }
    }

    /**
     * Check if XML content is valid.
     *
     * @return boolean true or false.
     */
    public function isValidXML($xml)
    {
        $doc = new \DOMDocument();
        try {
            return $doc->loadXML($xml) === true;
        } catch(\Exception $e) {
            $this->logger->warning('XMLDocument::isValidXML() - ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Get the document as a DOMDocument object.
     *
     * @return DOMDocument|null A DOMDocument object.
     */
    public function getAsDOMDocument()
    {
        return $this->dom->loadXML($this->getContent());
    }

}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents a properties constraint for search.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class PropertiesConstraint extends AbstractConstraint
{
    /**
     * Create a PropertiesConstraint object.
     *
     * @see http://docs.marklogic.com/search:search#opt-constraint
     *
     * @param string $name Constraint name.
     */
    public function __construct($name)
    {
        parent::__construct($name);
    }

    /**
     * Get the constraint as a DOMElement object.
     *
     * @param DOMDocument $dom The DOMDocument object with which to create the element.
     */
    public function getAsElem($dom)
    {
        $constElem = $dom->createElement('constraint');
        $constElem->setAttribute('name', $this->name);
        $propElem = $dom->createElement('properties');
        $constElem->appendChild($propElem);
        /* <constraint name='sample-property-constraint'>
                <properties />
       </constraint> */
        return $constElem;
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents a values setting in a search options node.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class Values
{
    private $name; // @var string
    private $type; // @var string
    private $elem; // @var string
    private $ns; // @var string
    private $attr; // @var string
    private $attrNs; // @var string
    private $field; // @var string
    private $index; // @var string
    private $datatype; // @var string

    private $aggregate; // @var string

    private $valuesOptions; // @var array

    /**
     * Create a Values object.
     *
     * @see http://docs.marklogic.com/guide/rest-dev/search#id_53938
     * @see http://docs.marklogic.com/search:search?q=search:search#opt-values
     * @see http://docs.marklogic.com/guide/admin/range_index
     *
     * @param string $name Values name.
     */
    public function __construct($name)
    {
        $this->name = (string)$name;
    }

    /**
     * Set up range values.
     *
     * @param string $elem Name of the element.
     * @param string $ns Element namespace.
     * @param string $attr Attribute name.
     * @param string $attrNs An attribute namespace.
     * @param string $datatype Constraint datatype.
     */
    public function setUpRange($elem, $ns = '', $attr = '', $attrNs = '', $datatype = 'string')
    {
        $this->type = 'range';
        $this->elem = (string)$elem;
        $this->ns = (string)$ns;
        $this->attr = (string)$attr;
        $this->attrNs = (string)$attrNs;
        $this->datatype = (string)$datatype;
    }

    /**
     * Set up uri values.
     */
    public function setUpUri()
    {
        $this->type = 'uri';
    }

    /**
     * Set up collection values.
     */
    public function setUpCollection()
    {
        $this->type = 'collection';
    }

    /**
     * Set values options.
     *
     * @param array $valuesOptions An array of values options.
     */
    public function setValuesOptions($valuesOptions)
    {
        $this->valuesOptions = $valuesOptions;
    }

    /**
     * Set aggregate operation to apply.
     *
     * @param string $aggregate Aggregate operation to apply.
     */
    public function setAggregate($aggregate)
    {
        $this->aggregate = $aggregate;
    }

    /**
     * Get the values setting as a DOMElement object.
     *
     * @param DOMDocument $dom The DOMDocument object in which to create the element.
     */
    public function getValuesAsElem($dom)
    {
        $valsElem = $dom->createElement('values');
        $valsElem->setAttribute('name', $this->name);

        switch ($this->type) {
            case 'range':
                $rangeElem = $dom->createElement('range');
                $rangeElem->setAttribute('type', $this->datatype);
                $elemElem = $dom->createElement('element');
                $elemElem->setAttribute('ns', $this->ns);
                $elemElem->setAttribute('name', $this->elem);
                $rangeElem->appendChild($elemElem);
                if($this->attr) {
                    $attrElem = $dom->createElement('attribute');
                    $attrElem->setAttribute('ns', $this->attrNs);
                    $attrElem->setAttribute('name', $this->attr);
                    $rangeElem->appendChild($attrElem);
                }
                $valsElem->appendChild($rangeElem);
                break;
                /* <values name='example'>
                    <range type='xs:integer'>
                        <element ns='' name='game'/>
                        <attribute ns='' name='runs'/>
                    </range>
                    <aggregate apply='sum'/>
                </values> */
            case 'uri':
                $uriElem = $dom->createElement('uri');
                $valsElem->appendChild($uriElem);

                break;
               /* <values name='uri'>
                      <uri/>
                      <values-option>limit=10</values-option>
                    </values> */
            case 'collection':
                $collectionElem = $dom->createElement('collection');
                $valsElem->appendChild($collectionElem);
                break;
               /* <values name="coll">
                      <collection/>
                      <values-option>limit=10</values-option>
                    </values> */
        }
        $this->addAggregate($dom, $valsElem);
        $this->addValuesOptions($dom, $valsElem);
        return $valsElem;
    }

    /**
     * Add values options to an element.
     *
     * @param DOMDocument $dom A DOM document.
     * @param DOMElement $elem A DOM element.
     * @return DOMElement The updated element.
     */
    private function addValuesOptions($dom, $elem)
    {
        if (!empty($this->valuesOptions)) {
            foreach($this->valuesOptions as $opt) {
                $valuesElem = $dom->createElement('values-option');
                $valuesElem->nodeValue = $opt;
                $elem->appendChild($valuesElem);
            }
        }
        return $elem;
    }

    /**
     * Add aggregate setting to an element.
     *
     * @param DOMDocument $dom A DOM document.
     * @param DOMElement $elem A DOM element.
     * @return DOMElement The updated element.
     */
    private function addAggregate($dom, $elem)
    {
        if(!empty($this->aggregate)) {
            $aggregateElem = $dom->createElement('aggregate');
            $aggregateElem->setAttribute('apply', $this->aggregate);
            $elem->appendChild($aggregateElem);
        }
        return $elem;
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents a value constraint for search.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class ValueConstraint extends AbstractConstraint
{
    private $elem; // @var string
    private $ns; // @var string
    private $attr; // @var string
    private $attrNs; // @var string
    protected $fragmentScope; // @var string
    protected $termOptions; // @var array

    /**
     * Create a ValueConstraint object.
     *
     * @see http://docs.marklogic.com/search:search#opt-constraint
     *
     * @param string $name Constraint name.
     * @param string $elem Element name.
     * @param string $ns An element namespace.
     * @param string $attr Attribute name.
     * @param string $attrNs An attribute namespace.
     */
    public function __construct($name, $elem, $ns = '', $attr = '', $attrNs = '')
    {
        $this->elem = (string)$elem;
        $this->ns = (string)$ns;
        $this->attr = (string)$attr;
        $this->attrNs = (string)$attrNs;
        parent::__construct($name);
    }

    /**
     * Get the constraint as a DOMElement object.
     *
     * @param DOMDocument $dom The DOMDocument object with which to create the element.
     */
    public function getAsElem($dom)
    {
        $constElem = $dom->createElement('constraint');
        $constElem->setAttribute('name', $this->name);
        $valElem = $dom->createElement('value');
        $elemElem = $dom->createElement('element');
        $elemElem->setAttribute('ns', $this->ns);
        $elemElem->setAttribute('name', $this->elem);
        $valElem->appendChild($elemElem);
        if($this->attr) {
            $attrElem = $dom->createElement('attribute');
            $attrElem->setAttribute('ns', $this->attrNs);
            $attrElem->setAttribute('name', $this->attr);
            $valElem->appendChild($attrElem);
        }
        $this->addTermOptions($dom, $valElem);
        $this->addFragmentScope($dom, $valElem);
        $constElem->appendChild($valElem);
    /* <constraint name='flavor'>
            <value>
                <element ns='http://example.com' name='flavor-descriptor'/>
            </value>
           </constraint> */
        return $constElem;
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;
//use MarkLogic\MLPHP\SearchResult;

/**
 * Represents a search.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class Search
{
    private $client; // @var RESTClient
    private $query; // @var string
    private $key; // @var string
    private $element; // @var string
    private $attribute; // @var string
    private $value; // @var string
    private $start; // @var int
    private $pageLength; // @var int
    private $options; // @var string
    private $view; // @var string
    private $format; // @var string
    private $collection; // @var string
    private $directory; // @var string

    /**
     * Create a Search object.
     *
     * @param RESTClient $client A REST client object.
     */
    public function __construct(
        $client = null, $start = 1, $pageLength = 10,
        $view = 'all', $format = 'xml'
    )
    {
        $this->client = $client;
        $this->start = (int)$start;
        $this->pageLength = (int)$pageLength;
        $this->view = (string)$view;
        $this->format = (string)$format;
    }

    /**
     * Set the REST client connection.
     *
     * @param RESTClient $client The RestClient object.
     */
    public function setConnection($client)
    {
        $this->client = $client;
    }

    /**
     * Get the common search parameters as an associated array.
     *
     * @return array An associated array of name/value pairs.
     */
    public function getParams()
    {
        $params = array(
            'start' => $this->start, 'pageLength' => $this->pageLength,
            'view' => $this->view, 'format' => $this->format
        );
        if(!empty($this->options)) {
            $params['options'] = $this->options;
        }
        if(!empty($this->collection)) {
            $params['collection'] = $this->collection;
        }
        if(!empty($this->directory)) {
            $params['directory'] = $this->directory;
        }
        return $params;
    }

    /**
     * Retrieve the search results using the REST client.
     *
     * @param string $query The search query.
     * @param array $params
     * @param bool $structured defaults to false
     * @return SearchResults A search results object.
     */
    public function retrieve($query, $params = array(), $structured = false)
    {
        $this->query = (string)$query;
        $params = array_merge(array(($structured ? 'structuredQuery' : 'q') =>
            $this->query), $this->getParams(), $params);
        $request = new RESTRequest('GET', 'search', $params);

        try {
            $response = $this->client->send($request);
            //print_r($response);
            $results = new SearchResults($response->getBody());
            return $results;
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() .
                ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Retrieve the key-value search results for JSON content using the
     * REST client.
     *
     * @param string $key The key (property name) in JSON content.
     * @param string $value The value (property value) in JSON content.
     * @return SearchResults A search results object.
     */
    public function retrieveKeyValue($key, $value, $params = array())
    {
        // /v1/keyvalue is deprecated, use /v1/search with structured
        $this->key = (string)$key;
        $this->value = (string)$value;
        $query = '<query xmlns="http://marklogic.com/appservices/search">
                    <container-query>
                      <json-property name="' . $this->key . '" ns="" />
                      <term-query>
                        <text>' . $this->value . '</text>
                      </term-query>
                    </container-query>
                  </query>';

        try {
            $params = array_merge(
                array('structuredQuery' => $query),
                $this->getParams(), $params
            );
            $request = new RESTRequest('GET', 'search', $params);
            $response = $this->client->send($request);
            $results = new SearchResults($response->getBody());
            return $results;
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() .
            ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Retrieve the key-value search results, where the key is an element
     * name, for XML content using the REST client.
     *
     * @param string $element The element name.
     * @param string $attribute The attribute for the element, to search for
     *        attribute content.
     * @param string $value The value for that element (or attribute).
     * @return SearchResults A search results object.
     */
    public function retrieveKeyValueElement(
        $element, $attribute, $value, $params = array()
    )
    {
        $this->element = (string)$element;
        $this->attribute = (string)$attribute;
        $this->value = (string)$value;
        // /v1/keyvalue is deprecated, use /v1/search with structured
        // $query = '<query xmlns="http://marklogic.com/appservices/search">
        //             <container-query>
        //               <element name="' . $this->element . '" ns="" />';
        // $query .= $this->attribute ?
        //     '<attribute name=' . $this->attribute . ' ns="" />' :
        //     '';
        // $query .= '<term-query>
        //                 <text>' . $this->value . '</text>
        //               </term-query>
        //             </container-query>
        //           </query>';

        try {
            // Only include attribute in final array if it is set
            $array_attr = (!empty($attribute)) ?
                array('attribute' => $this->attribute) : array();
            $params = array_merge(
                array('element' => $this->element, 'value' => $this->value),
                $array_attr, $this->getParams(), $params);
            $request = new RESTRequest('GET', 'keyvalue', $params);

            $params = array_merge(
                array('element' => $this->element, 'value' => $this->value),
                $array_attr, $this->getParams(), $params);
            // $params = array_merge(
            //     array('structuredQuery' => $query),
            //     $this->getParams(), $params
            // );
            $request = new RESTRequest('GET', 'keyvalue', $params);
            $response = $this->client->send($request);
            $results = new SearchResults($response->getBody());
            return $results;
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() .
            ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Highlight hits in the given content based on the given query
     *
     * Implemented via a MarkLogic REST API resource extension.
     *
     * @param string $content
     * @param string $contentType
     *        - describes content to be highlighted
     *        - support for application/xml and text'plain
     * @param string $query The search query.
     * @param array $params
     * @param bool $structured defaults to false (only false is
     *        supported today)
     * @return string hit-highlighted content
     */
    public function highlight(
        $content, $contentType, $class, $query,
        $params = array(), $structured = false
    )
    {
        // Install the API extension
        $resource = "resources/highlight";
        $this->client->installExtension("config/" . $resource, array(
            'method' => 'post',
            'post:q?' => 'string',
            'post:class' => 'string',
            'post:structuredQuery?' => 'string',
            'post:c' => 'string',
            'post:ct' => 'string',
            'post:provider?' => 'string'
        ), "highlight.xqy");

        // Use it
        $this->query = (string)$query;
        $params = array_merge(array(
            ($structured ? 'structuredQuery' : 'q') => $this->query,
            'c' => $content,
            'ct' => $contentType,
            'class' => $class
        ), $this->getParams(), $params);

        $request = new RESTRequest('POST', $resource, $params, "", array(
            'Content-type' => 'application/x-www-form-urlencoded'
        ));

        try {
            $response = $this->client->send($request);
            //print_r($response);
            $results = $response->getBody();
            if ($contentType === "text/plain") {
                return $results;
            } else {
                // Strip off XML decl
                return substr( $results, strpos($results, "\n")+1 );
            }
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() .
            ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Get the start setting.
     *
     * @return string The debug setting, 'true' or 'false'.
     */
    public function getStart()
    {
        return $this->start;
    }

    /**
     * Set the start setting.
     *
     * @param string $start The debug setting, 'true' or 'false'.
     */
    public function setStart($start)
    {
        $this->start = (int)$start;
        return $this;
    }

    /**
     * Get the page length.
     *
     * @return int The page length.
     */
    public function getPageLength()
    {
        return $this->pageLength;
    }

    /**
     * Set the page length.
     *
     * @see http://docs.marklogic.com/REST/GET/v1/search
     *
     * @param int $pageLength The page length.
     */
    public function setPageLength($pageLength)
    {
        $this->pageLength = (int)$pageLength;
        return $this;
    }

    /**
     * Get the search-options name.
     *
     * @return string The search-options name.
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Set the search-options name.
     *
     * @param string $option The search-options name.
     */
    public function setOptions($options)
    {
        $this->options = (string)$options;
        return $this;
    }

    /**
     * Get the view setting.
     *
     * @return string The search options as XML.
     */
    public function getView()
    {
        return $this->view;
    }

    /**
     * Set the view setting ('facets', 'results', 'metadata', or 'all').
     *
     * @see http://docs.marklogic.com/REST/GET/v1/search
     *
     * @param string $view The search options as XML.
     */
    public function setView($view)
    {
        $this->view = (int)$view;
        return $this;
    }

    /**
     * Get the format setting.
     *
     * @return string The format setting.
     */
    public function getFormat()
    {
        return $this->format;
    }

    /**
     * Set the format setting ('xml' or 'json'). MLPHP only supports
     * 'xml' currently.
     *
     * @see http://docs.marklogic.com/REST/GET/v1/search
     *
     * @param string $format The format setting.
     */
    public function setFormat($format)
    {
        $this->format = (int)$format;
        return $this;
    }

    /**
     * Get the collection to filter by.
     *
     * @return string A collection string.
     */
    public function getCollection()
    {
        return $this->collection;
    }

    /**
     * Set the collection to filter by.
     *
     * @see http://docs.marklogic.com/guide/rest-dev/search#id_74024
     *
     * @param array|string $collection An array of collection strings or a
     *        collection string.
     */
    public function setCollection($collection)
    {
        $this->collection = $collection;
        return $this;
    }

    /**
     * Get the directory to filter by.
     *
     * @return string A directory string.
     */
    public function getDirectory()
    {
        return $this->directory;
    }

    /**
     * Set the directory to filter by.
     *
     * @see http://docs.marklogic.com/guide/rest-dev/search#id_74024
     *
     * @param array|string $directory An array of directory strings or a
     *        directory string.
     */
    public function setDirectory($directory)
    {
        $this->directory = $directory;
        return $this;
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

use Psr\Log\NullLogger;

/**
 * MLPHP Global State
 *
 * @package MLPHP
 * @author Eric Bloch <eric.bloch@gmail.com>
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class MLPHP
{
    /**
     *  Array of configuration parameters used to create clients and REST APIs.
     *  @var mixed[]
     *  @see MLPHP#__construct
     */
    public $config = array();

    /**
     * Constructor, used to set configuration parameters.
     *
     * @param array config Configuration settings.
     *
     */
    public function __construct($config = array())
    {
        $this->config = array_merge(array(
            'host' => '127.0.0.1',
            'port' => 8003,
            'managePort' => 8002,
            'adminPort' => 8001,
            'api' => 'mlphp-rest-api',
            'db' => 'mlphp-db',
            'username' => 'admin',
            'password' => 'admin',
            'path' => '',
            'managePath' => 'manage',
            'adminPath' => 'admin',
            'version' => 'v1',
            'manageVersion' => 'v2',
            'adminVersion' => 'v1',
            'auth' => 'digest',
            'options' => 'mlphp-options',
            'logger' => new NullLogger()
        ), $config);

    }

    /**
     * Merge the passed in config parameters.  Only affects future clients.
     *
     * @param config
     */
    public function mergeConfig($config)
    {
        $this->config = array_merge($this->config, $config);
    }

    /**
     * Return a REST client based on current configuration.
     *
     * @return RESTClient
     */
    public function getClient()
    {
        return new RESTClient(
            $this->config['host'],
            $this->config['port'],
            $this->config['path'],
            $this->config['version'],
            $this->config['username'],
            $this->config['password'],
            $this->config['auth'],
            $this->config['logger']
        );
    }

    /**
     * Return a REST client to the management API.
     *
     * @return RESTClient
     */
    public function getManageClient()
    {
        return new RESTClient(
            $this->config['host'],
            $this->config['managePort'],
            $this->config['managePath'],
            $this->config['manageVersion'],
            $this->config['username'],
            $this->config['password'],
            $this->config['auth'],
            $this->config['logger']
        );
    }

    /**
     * Return a REST client to the admin API.
     *
     * @return RESTClient
     */
    public function getAdminClient()
    {
        return new RESTClient(
            $this->config['host'],
            $this->config['adminPort'],
            $this->config['adminPath'],
            $this->config['adminVersion'],
            $this->config['username'],
            $this->config['password'],
            $this->config['auth'],
            $this->config['logger']
        );
    }

    /**
     * Create and return a REST API based on current configuration.
     *
     * @return RESTAPI
     */
    public function getAPI()
    {
        return new RESTAPI(
            $this->config['api'],
            $this->config['host'],
            $this->config['db'],
            $this->config['port'],
            $this->config['username'],
            $this->config['password']
        );
    }

    /**
     * Return a Document object.
     *
     * @return Document
     */
    public function getDocument($uri = null)
    {
        return new Document(
            $this->getClient(),
            $uri
        );
    }

    /**
     * Return a Database object.
     *
     * @return Database
     */
    public function getDatabase($name = null)
    {
        $name = $name ? $name : $this->config['db'];
        return new Database(
            $this->getManageClient(),
            $name
        );
    }

    /**
     * Return an Options object.
     *
     * @return Options
     */
    public function getOptions($name = null)
    {
        $name = $name ? $name : $this->config['options'];
        return new Options(
            $this->getClient(),
            $name
        );
    }

    /**
     * Return an assoc array of MarkLogic server config information.
     * Keys: 'version', 'platform', 'edition'
     * @see http://docs.marklogic.com/REST/GET/admin/v1/server-config
     *
     * @return array
     */
    public function getServerConfig()
    {
        $adminClient = $this->getAdminClient();
        $request = new RESTRequest('GET', 'server-config');
        try {
            $response = $adminClient->send($request);
            $dom = new \DOMDocument();
            $dom->loadXML($response->getBody());
            return array(
              'version' => $dom->getElementsByTagName('version')->item(0)->nodeValue,
              'platform' => $dom->getElementsByTagName('platform')->item(0)->nodeValue,
              'edition' => $dom->getElementsByTagName('edition')->item(0)->nodeValue
            );
        } catch(Exception $e) {
            echo 'MLPHP::getServerConfig() failed.' . PHP_EOL;
            echo $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * PSR-0 autoloader.
     *
     * Do NOT use if you are using Composer to autoload dependencies.
     *
     * @todo Delete this if we are requiring Composer for MLPHP.
     *
     * @param $className
     */
    public static function autoload($className)
    {
        $thisClass = str_replace(__NAMESPACE__ . '\\', '', __CLASS__);

        $baseDir = __DIR__;

        if (substr($baseDir, -strlen($thisClass)) === $thisClass) {
            $baseDir = substr($baseDir, 0, -strlen($thisClass));
        }

        $className = ltrim($className, '\\');
           // echo "class " . $className;
           // echo '
';
        $fileName  = (dirname($baseDir)) . DIRECTORY_SEPARATOR;
                // echo "file " . $fileName;
                // echo '
';
        if ($lastNsPos = strripos($className, '\\')) {
            $namespace = substr($className, 0, $lastNsPos);
               // echo "ns " . $namespace;
               // echo '
';
            $className = substr($className, $lastNsPos + 1);
               // echo "class " . $className;
               // echo '
';
            $fileName .= str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
               // echo "file " . $fileName;
               // echo '
';
        }
        $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

           // echo "FILE: " . $fileName;

        if (file_exists($fileName)) {
            require $fileName;
        }
    }

    /**
     * Register PSR-0 autoloader.
     *
     * Do NOT use if you are using Composer to
     * autoload dependencies.
     */
    public static function registerAutoloader()
    {
        $n = __NAMESPACE__; // workaround for bug in phpdoctor
        spl_autoload_register($n . '\\MLPHP::autoload');
    }
}


<?php
/*
Copyright 2002-2012 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents query options for search.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 * @see Parameter list: http://docs.marklogic.com/search:search
 * @todo Support complete of properties (see below)
 */
class Options
{
    private $dom; // @var DOMDocument
    private $client; // @var RESTClient
    private $name; // @var string

    private $constraints; // @var array of constraint objects
    private $values; // @var array of Values objects
    private $extracts; // @var Extracts object (handles 'extract-metadata' parameter)
    private $transformResults; // @var TransformResults object
    private $term; // @var Term object

    private $additionalQuery; // @var string
    private $concurrencyLevel; // @var int
    private $debug; // @var bool
    private $forest; // @var int
    private $pageLength; // @var int
    private $qualityWeight; // @var float
    private $returnConstraints; // @var string
    private $returnFacets; // @var string
    private $returnMetrics; // @var string
    private $returnPlan; // @var string
    private $returnQtext; // @var string
    private $returnQuery; // @var string
    private $returnResults; // @var string
    private $returnSimilar; // @var string
    private $searchOptions; // @var array

    // TODO (*** higher priority):
    // custom constraint
    // bucketed range constraint ***
    // default-suggestion-source
    // grammar
    // operator ***
    // searchable-expression
    // sort-order ***
    // suggestion-source
    // term ***
    // tuples

    /**
     * Create an Options object.
     *
     * @param RESTClient $client A REST client object.
     * @param string $name Name of the options.
     */
    public function __construct($client, $name = null)
    {
        $this->client = $client;
        $this->name = $name;
        $this->dom = new \DOMDocument();
        $this->constraints = array();
        $this->values = array();
    }

    /**
     * Get the options name.
     *
     * @return string The options name.
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the options name.
     *
     * @param string $name The options name.
     * @return Options $this
     */
    public function setName($name)
    {
        $this->name = (string)$name;
        return $this;
    }

    /**
     * Add a constraint.
     *
     * @param mixed $constraint constraint object.
     * @return Options $this
     */
    public function addConstraint($constraint)
    {
        $this->constraints[] = $constraint;
        return $this;
    }

    /**
     * Add a values setting.
     *
     * @param Values $values Values object.
     * @return Options $this
     */
    public function addValues($values)
    {
        $this->values[] = $values;
        return $this;
    }

    /**
     * Set the metadata extracts.
     *
     * @param Extracts $extracts A metadata extracts object.
     * @return Options $this
     */
    public function setExtracts($extracts)
    {
        $this->extracts = $extracts;
        return $this;
    }

    /**
     * Set the transform-results setting.
     *
     * @param TransformResults $transformResults A TransformResults object.
     * @return Options $this
     */
    public function setTransformResults($transformResults)
    {
        $this->transformResults = $transformResults;
        return $this;
    }

    /**
     * Set the term setting.
     *
     * @param Term $term A Term object.
     * @return Options $this
     */
    public function setTerm($term)
    {
        $this->term = $term;
        return $this;
    }

    /**
     * Get the query options as XML.
     *
     * @return string The query options as XML.
     */
    public function getAsXML()
    {
        // root
        $this->dom = new \DOMDocument(); // reset so we don't continually to add to the options node
        $root = $this->dom->createElement('options');
        $root->setAttribute('xmlns', 'http://marklogic.com/appservices/search');
        $this->dom->appendChild($root);

        // constraints
        foreach ($this->constraints as $constraint) {
            $root->appendChild($constraint->getAsElem($this->dom));
        }

        // values
        foreach ($this->values as $value) {
            $root->appendChild($value->getValuesAsElem($this->dom));
        }

        // metadata extracts
        if(isset($this->extracts)) {
            $extractsElem = $this->extracts->getExtractsAsElem($this->dom);
            $root->appendChild($extractsElem);
        }

        // transform results (snippetting)
        if(isset($this->transformResults)) {
            $transElem = $this->transformResults->getTransformResultsAsElem($this->dom);
            $root->appendChild($transElem);
        }

        // term
        if(isset($this->term)) {
            $termElem = $this->term->getAsElem($this->dom);
            $root->appendChild($termElem);
        }

        $this->addOptionXML($root, 'additional-query', $this->additionalQuery);
        $this->addOption($root, 'concurrency-level', $this->concurrencyLevel);
        $this->addOption($root, 'debug', $this->debug);
        $this->addOption($root, 'forest', $this->forest);
        $this->addOption($root, 'page-length', $this->pageLength);
        $this->addOption($root, 'quality-weight', $this->qualityWeight);
        $this->addOption($root, 'return-constraints', $this->returnConstraints);
        $this->addOption($root, 'return-facets', $this->returnFacets);
        $this->addOption($root, 'return-metrics', $this->returnMetrics);
        $this->addOption($root, 'return-plan', $this->returnPlan);
        $this->addOption($root, 'return-qtext', $this->returnQtext);
        $this->addOption($root, 'return-query', $this->returnQuery);
        $this->addOption($root, 'return-results', $this->returnResults);
        $this->addOption($root, 'return-similar', $this->returnSimilar);
        if (!empty($this->searchOptions)) {
            foreach($this->searchOptions as $opt) {
                $this->addOption($root, 'search-option', $this->returnSimilar);
            }
        }
        return $this->dom->saveXML();
    }

    /**
     * If an option value is set, create a DOM element with an option name
     * and value and append it to the root element of the options node.
     *
     * @param DOMElement $root The root element of the options node.
     * @param string $name The element name (option name).
     * @param string $value The node value.
     */
    public function addOption($root, $name, $value)
    {
        if (isset($value)) {
            $elem = $this->dom->createElement($name);
            $elem->nodeValue = $value;
            $root->appendChild($elem);
        }
    }

    /**
     * If an option value is set, create a DOM element with an option name
     * and XML string value and append it to the root element of the options node.
     *
     * @param DOMElement $root The root element of the options node.
     * @param string $name The element name (option name).
     * @param string $value The node value as an XML string.
     */
    public function addOptionXML($root, $name, $xmlString)
    {
        if (isset($xmlString)) {
            $elem = $this->dom->createElement($name);
            $xmlDoc = new \DOMDocument();
            $xmlDoc->loadXML($xmlString);
            // http://stackoverflow.com/questions/5783716/php-xml-dom-uncaught-exception-domexception-with-message-wrong-document-error
            $elem->appendChild($this->dom->importNode($xmlDoc->documentElement, true));
            $root->appendChild($elem);
        }
    }

    /**
     * Read the query options from the database.
     *
     * @param string $name The query options name.
     * @return string The query options as XML.
     */
    public function read($name = null)
    {
        $name = $name ? $name : $this->name;
        try {
            $params = array('format' => 'xml');
            $request = new RESTRequest('GET', 'config/query/' . $name, $params);
            $response = $this->client->send($request);
            return $response->getBody();
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Write the query options to the database.
     *
     * @param string $name The query options name.
     * @return Options $this
     */
    public function write($name = null)
    {
        $name = $name ? $name : $this->name;
        try {
            $params = array('format' => 'xml');
            $headers = array('Content-type' => 'application/xml');
            $request = new RESTRequest('PUT', 'config/query/' . $name, $params, $this->getAsXML(), $headers);
            $this->response = $this->client->send($request);
            return $this;
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Delete the query options from the database.
     *
     * @param string $name The query options name.
     * @return Options $this
     */
    public function delete($name)
    {
        $name = $name ? $name : $this->name;
        try {
            $request = new RESTRequest('DELETE', 'config/query/' . $name);
            $this->response = $response = $this->client->send($request);
            return $this;
        } catch(Exception $e) {
            echo $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() . PHP_EOL;
        }
    }

    /**
     * Get the additional-query setting.
     *
     * @return string The additional-query setting.
     */
    public function getAdditionalQuery()
    {
        return $this->additionalQuery;
    }

    /**
     * Set The additional-query setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-additional-query
     *
     * @param string $additionalQuery The additional-query setting.
     * @return Options $this
     */
    public function setAdditionalQuery($additionalQuery)
    {
        $this->additionalQuery = (string)$additionalQuery;
        return $this;
    }

    /**
     * Get the concurrency level.
     *
     * @return int The concurrency level.
     */
    public function getConcurrencyLevel()
    {
        return $this->concurrencyLevel;
    }

    /**
     * Set The concurrency level.
     *
     * @see http://docs.marklogic.com/search:search#opt-concurrency-level
     *
     * @param int $concurrencyLevel The concurrency level.
     * @return Options $this
     */
    public function setConcurrencyLevel($concurrencyLevel)
    {
        $this->concurrencyLevel = (int)$concurrencyLevel;
        return $this;
    }

    /**
     * Get the debug setting.
     *
     * @return string The debug setting, 'true' or 'false'.
     */
    public function getDebug()
    {
        return $this->debug;
    }

    /**
     * Set the debug setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-debug
     *
     * @param string $debug The debug setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setDebug($debug)
    {
        $this->debug = (string)$debug;
        return $this;
    }

    /**
     * Get the forest ID.
     *
     * @return int The forest ID.
     */
    public function getForest()
    {
        return $this->forest;
    }

    /**
     * Set the forest ID.
     *
     * @see http://docs.marklogic.com/search:search#opt-forest
     *
     * @param int $forest The forest ID.
     * @return Options $this
     */
    public function setForest($forest)
    {
        $this->forest = (int)$forest;
        return $this;
    }

    /**
     * Get the page length.
     *
     * @return int The page length.
     */
    public function getPageLength()
    {
        return $this->pageLength;
    }

    /**
     * Set the page length.
     *
     * @see http://docs.marklogic.com/search:search#opt-page-length
     *
     * @param int $pageLength The page length.
     * @return Options $this
     */
    public function setPageLength($pageLength)
    {
        $this->pageLength = (int)$pageLength;
        return $this;
    }

    /**
     * Get the quality weight.
     *
     * @return float The quality weight.
     */
    public function getQualityWeight()
    {
        return $this->qualityWeight;
    }

    /**
     * Set the quality weight.
     *
     * @see http://docs.marklogic.com/search:search#opt-quality-weight
     *
     * @param float $qualityWeight The quality weight.
     * @return Options $this
     */
    public function setQualityWeight($qualityWeight)
    {
        $this->qualityWeight = (float)$qualityWeight;
        return $this;
    }

    /**
     * Get the return-constraints setting.
     *
     * @return string The return-constraints setting.
     */
    public function getReturnConstraints()
    {
        return $this->returnConstraints;
    }

    /**
     * Set the return-constraints setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-return-constraints
     *
     * @param string $returnConstraints The return-constraints setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnConstraints($returnConstraints)
    {
        $this->returnConstraints = (string)$returnConstraints;
        return $this;
    }

    /**
     * Get the return-facets setting.
     *
     * @return string The return-facets setting.
     */
    public function getReturnFacets()
    {
        return $this->returnFacets;
    }

    /**
     * Set the return-facets setting. (Alternative: set view search parameter.)
     *
     * @see http://docs.marklogic.com/search:search#opt-return-facets
     *
     * @param string $returnFacets The return-facets setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnFacets($returnFacets)
    {
        $this->returnFacets = (string)$returnFacets;
        return $this;
    }

    /**
     * Get the return-metrics setting.
     *
     * @return string The return-metrics setting.
     */
    public function getReturnMetrics()
    {
        return $this->returnMetrics;
    }

    /**
     * Set the return-metrics setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-return-metrics
     *
     * @param string $returnMetrics The return-metrics setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnMetrics($returnMetrics)
    {
        $this->returnMetrics = (string)$returnMetrics;
        return $this;
    }

    /**
     * Get the return-plan setting.
     *
     * @return string The return-plan setting.
     */
    public function getReturnPlan()
    {
        return $this->returnPlan;
    }

    /**
     * Set the return-plan setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-return-plan
     *
     * @param string $returnPlan The return-plan setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnPlan($returnPlan)
    {
        $this->returnPlan = (string)$returnPlan;
        return $this;
    }

    /**
     * Get the return-qtext setting.
     *
     * @return string The return-qtext setting.
     */
    public function getReturnQtext()
    {
        return $this->returnQtext;
    }

    /**
     * Set the return-qtext setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-return-qtext
     *
     * @param string $returnQtext The return-qtext setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnQtext($returnQtext)
    {
        $this->returnQtext = (string)$returnQtext;
        return $this;
    }

    /**
     * Get the return-query setting.
     *
     * @return string The return-query setting.
     */
    public function getReturnQuery()
    {
        return $this->returnQuery;
    }

    /**
     * Set the return-query setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-return-query
     *
     * @param string $returnQuery The return-query setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnQuery($returnQuery)
    {
        $this->returnQuery = (string)$returnQuery;
        return $this;
    }

    /**
     * Get the return-results setting.
     *
     * @return string The return-results setting.
     */
    public function getReturnResults()
    {
        return $this->returnResults;
    }

    /**
     * Set the return-results setting. (Alternative: set view search parameter.)
     *
     * @todo broken, setting to 'false' doesn't stop results from returning
     * @see http://docs.marklogic.com/search:search#opt-return-results
     *
     * @param string $returnResults The return-results setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnResults($returnResults)
    {
        $this->returnResults = (string)$returnResults;
        return $this;
    }

    /**
     * Get the return-similar setting.
     *
     * @return string The return-similar setting.
     */
    public function getReturnSimilar()
    {
        return $this->returnSimilar;
    }

    /**
     * Set the return-similar setting.
     *
     * @see http://docs.marklogic.com/search:search#opt-return-similar
     *
     * @param string $returnSimilar The return-similar setting, 'true' or 'false'.
     * @return Options $this
     */
    public function setReturnSimilar($returnSimilar)
    {
        $this->returnSimilar = (string)$returnSimilar;
        return $this;
    }

    /**
     * Get the query options.
     *
     * @return array The query options.
     */
    public function getSearchOptions()
    {
        return $this->searchOptions;
    }

    /**
     * Set the query options.
     *
     * @see http://docs.marklogic.com/search:search#opt-search-option
     *
     * @param string|array $searchOptions The query options as a string (single option) or array of strings.
     * @return Options $this
     */
    public function setSearchOptions($searchOptions)
    {
        if (is_array($searchOptions)) {
            $this->searchOptions = array_unique(array_merge($this->searchOptions, $searchOptions));
        } else {
            $this->searchOptions[] = $searchOptions;
            $this->searchOptions = array_unique($this->searchOptions);
        }
        return $this;
    }

    /**
     * Get the last REST response received. Useful for testing.
     *
     * @return RESTRresponse A REST response object.
     */
    public function getResponse()
    {
        return $this->response;
    }
}


<?php
/*
Copyright 2002-2013 MarkLogic Corporation.  All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace MarkLogic\MLPHP;

/**
 * Represents a document.
 *
 * @package MLPHP
 * @author Mike Wooldridge <mike.wooldridge@marklogic.com>
 */
class Document
{
    private $uri; // @var string
    private $content; // @var string
    protected $contentType; // @var string
    private $client; // @var RESTClient
    protected $logger; // @var LoggerInterface
    private $response; // @var RESTResponse

    /**
     * Create a Document object.
     *
     * @param RESTClient $client A REST client object.
     * @param string $uri A document URI.
     */
    public function __construct($client, $uri = null)
    {
        $this->client = $client;
        $this->logger = $client->getLogger();
        $this->uri = (string)$uri;
    }

    /**
     * Read a document from the database.
     *
     * @see Document::getContent()
     *
     * @param string $uri A document URI.
     * @param array $params Optional additional parameters to pass when reading.
     * @return string|bool The document content or false on failure.
     */
    public function read($uri = null, $params = array())
    {
        $this->uri = (isset($uri)) ? (string)$uri : $this->uri;
        try {
            $params = array_merge(array('uri' => $this->uri), $params);
            $request = new RESTRequest('GET', 'documents', $params);
            $this->response = $this->client->send($request);
            $this->content = $this->response->getBody();
            $this->contentType = $this->response->getContentType();
            return $this->content;
        } catch(\Exception $e) {
            // TODO: error codes for not-found and other reasonable, unexceptional errors.
            $this->logger->warning( $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
            return false;
        }
    }

    /**
     * Write a document to the database.
     *
     * @param string $uri A document URI.
     * @param array $params Optional additional parameters to pass when writing.
     * @return Document $this
     */
    public function write($uri = null, $params = array())
    {
        $this->uri = (isset($uri)) ? (string)$uri : $this->uri;
        try {
            $params = array_merge(array('uri' => $this->uri), $params);
            $headers = array();
            if ($this->getContentType()) {
                $headers = array('Content-type' => $this->getContentType());
            }
            $request = new RESTRequest('PUT', 'documents', $params, $this->content, $headers);
            $this->response = $this->client->send($request);
        } catch(\Exception $e) {
            $this->logger->error( $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
            throw $e;
        }
        return $this;
    }

    /**
     * Delete a document from the database. Note that no properties for object are changed.
     *
     * @param string $uri The document URI.
     * @return Document $this
     */
    public function delete($uri = null)
    {
        $this->uri = (isset($uri)) ? (string)$uri : $this->uri;
        try {
            $params = array('uri' => $this->uri);
            $request = new RESTRequest('DELETE', 'documents', $params);
            $this->response = $this->client->send($request);
        } catch(\Exception $e) {
            $this->logger->error(  $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
        }
        return $this;
    }

    /**
     * Read document metadata from the database.
     *
     * @return Metadata A Metadata object.
     */
    public function readMetadata()
    {
        try {
            $params = array('uri' => $this->uri, 'category' => 'metadata');
            $request = new RESTRequest('GET', 'documents', $params);
            $this->response = $this->client->send($request);
            $metadata = new Metadata();
            $metadata->loadFromXML($this->response->getBody());
            return $metadata;
        } catch(\Exception $e) {
            $this->logger->error( $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
            return null;
        }
    }

    /**
     * Write document metadata to the database.
     *
     * @param Metadata $metadata A Metadata object.
     * @return Document $this
     */
    public function writeMetadata($metadata)
    {
        $metaxml = $metadata->getAsXML();
        try {
            $params = array('uri' => $this->uri, 'category' => 'metadata', 'format' => 'xml');
            $headers = array('Content-type' => 'application/xml');
            $request = new RESTRequest('PUT', 'documents', $params, $metaxml, $headers);
            $this->response = $this->client->send($request);
        } catch(\Exception $e) {
            $this->logger->error( $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
        }
        return $this;
    }

    /**
     * Delete document metadata. Metadata reverts to default metadata state.
     *
     * @return Document $this
     */
    public function deleteMetadata()
    {
        try {
            $params = array('uri' => $this->uri, 'category' => 'metadata');
            $request = new RESTRequest('DELETE', 'documents', $params);
            $this->response = $this->client->send($request);
        } catch(\Exception $e) {
            $this->logger->error( $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() );
        }
        return $this;
    }

    /**
     * Get the document URI.
     *
     * @return string The document URI.
     */
    public function getURI()
    {
        return $this->uri;
    }

    /**
     * Set the document URI.
     *
     * @param string $uri The document URI.
     * @return Document $this
     */
    public function setURI($uri)
    {
        $this->uri = (string)$uri;
        return $this;
    }

    /**
     * Get the document content type.
     *
     * @return string The document content type.
     */
    public function getContentType()
    {
        return $this->contentType;
    }

    /**
     * Set the document content type.
     *
     * @param string $contentType The document content type.
     * @return Document $this
     */
    public function setContentType($contentType)
    {
        $this->contentType = (string)$contentType;
        return $this;
    }

    /**
     * Get the document content.
     *
     * @return string The document content.
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Set the document content.
     *
     * @param string $content The document content.
     * @return Document $this
     */
    public function setContent($content)
    {
        $this->content = (string)$content;
        return $this;
    }

    /**
     * Set the document content from the file system.
     *
     * @param string $file The file location.
     * @return Document $this
     */
    public function setContentFile($file)
    {
        try {
            $content = file_get_contents((string)$file);
        } catch(\Exception $e) {
            $this->logger->error( $e->getMessage() );
        }
        $this->setContent($content);
        return $this;
    }

    /**
     * Check if the document has content associated with it.
     *
     * @return bool true if the document has content, false otherwise.
     */
    public function hasContent()
    {
        return !empty($this->content);
    }

    /**
     * Set the REST client for the document.
     *
     * @param RESTClient $client A REST client object.
     * @return Document $this
     */
    public function setConnection($client)
    {
        $this->client = $client;
        return $this;
    }

    /**
     * Get the REST client for the document.
     *
     * @return RESTClient  A REST client object.
     */
    public function getConnection()
    {
        return $this->client;
    }

    /**
     * Get the mimetype of a file.
     *
     * @param string $file The file.
     * @return string The the mimetype of the file.
     */
    public function getFileMimeType($file)
    {
        if (function_exists('finfo_file')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $type = finfo_file($finfo, $file);
            finfo_close($finfo);
        } else if (function_exists('mime_content_type')) {
            $type = mime_content_type($file);
        } else {
        	$type = '';
        }
        return $type;
    }

    /**
     * Get the last REST response received. Useful for testing.
     *
     * @return RESTRresponse A REST response object.
     */
    public function getResponse()
    {
        return $this->response;
    }
}