<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Crypt\PublicKey\Rsa;

/**
 * RSA private key
 */
class PrivateKey extends AbstractKey
{
    
/**
     * Public key
     *
     * @var PublicKey
     */
    
protected $publicKey null;

    
/**
     * Create private key instance from PEM formatted key file
     *
     * @param  string      $pemFile
     * @param  string|null $passPhrase
     * @return PrivateKey
     * @throws Exception\InvalidArgumentException
     */
    
public static function fromFile($pemFile$passPhrase null)
    {
        if (!
is_readable($pemFile)) {
            throw new 
Exception\InvalidArgumentException(
                
"PEM file '{$pemFile}' is not readable"
            
);
        }

        return new static(
file_get_contents($pemFile), $passPhrase);
    }

    
/**
     * Constructor
     *
     * @param  string $pemString
     * @param  string $passPhrase
     * @throws Exception\RuntimeException
     */
    
public function __construct($pemString$passPhrase null)
    {
        
$result openssl_pkey_get_private($pemString$passPhrase);
        if (
false === $result) {
            throw new 
Exception\RuntimeException(
                
'Unable to load private key; openssl ' openssl_error_string()
            );
        }

        
$this->pemString          $pemString;
        
$this->opensslKeyResource $result;
        
$this->details            openssl_pkey_get_details($this->opensslKeyResource);
    }

    
/**
     * Get the public key
     *
     * @return PublicKey
     */
    
public function getPublicKey()
    {
        if (
$this->publicKey === null) {
            
$this->publicKey = new PublicKey($this->details['key']);
        }

        return 
$this->publicKey;
    }

    
/**
     * Encrypt using this key
     *
     * @param  string $data
     * @return string
     * @throws Exception\RuntimeException
     * @throws Exception\InvalidArgumentException
     */
    
public function encrypt($data)
    {
        if (empty(
$data)) {
            throw new 
Exception\InvalidArgumentException('The data to encrypt cannot be empty');
        }

        
$encrypted '';
        
$result openssl_private_encrypt($data$encrypted$this->getOpensslKeyResource());
        if (
false === $result) {
            throw new 
Exception\RuntimeException(
                
'Can not encrypt; openssl ' openssl_error_string()
            );
        }

        return 
$encrypted;
    }

    
/**
     * Decrypt using this key
     *
     * @param  string $data
     * @return string
     * @throws Exception\RuntimeException
     * @throws Exception\InvalidArgumentException
     */
    
public function decrypt($data)
    {
        if (!
is_string($data)) {
            throw new 
Exception\InvalidArgumentException('The data to decrypt must be a string');
        }
        if (
'' === $data) {
            throw new 
Exception\InvalidArgumentException('The data to decrypt cannot be empty');
        }

        
$decrypted '';
        
$result openssl_private_decrypt($data$decrypted$this->getOpensslKeyResource());
        if (
false === $result) {
            throw new 
Exception\RuntimeException(
                
'Can not decrypt; openssl ' openssl_error_string()
            );
        }

        return 
$decrypted;
    }

    
/**
     * @return string
     */
    
public function toString()
    {
        return 
$this->pemString;
    }
}
  }

        if (!
$result $this->findFromProperty($page$rel$type)) {
            
$result $this->findFromSearch($page$rel$type);
        }

        return 
$result;
    }

    
/**
     * Finds relations of given $type for $page by checking if the
     * relation is specified as a property of $page
     *
     * @param  AbstractPage        $page        page to find relations for
     * @param  string              $rel         relation, 'rel' or 'rev'
     * @param  string              $type        link type, e.g. 'start', 'next'
     * @return AbstractPage|array|null  page(s), or null if not found
     */
    
protected function findFromProperty(AbstractPage $page$rel$type)
    {
        
$method 'get' ucfirst($rel);
        
$result $page->$method($type);
        if (
$result) {
            
$result $this->convertToPages($result);
            if (
$result) {
                if (!
is_array($result)) {
                    
$result = array($result);
                }

                foreach (
$result as $key => $page) {
                    if (!
$this->accept($page)) {
                        unset(
$result[$key]);
                    }
                }

                return 
count($result) == $result[0] : $result;
            }
        }

        return 
null;
    }

    
/**
     * Finds relations of given $rel=$type for $page by using the helper to
     * search for the relation in the root container
     *
     * @param  AbstractPage        $page   page to find relations for
     * @param  string              $rel    relation, 'rel' or 'rev'
     * @param  string              $type   link type, e.g. 'start', 'next', etc
     * @return array|null                  array of pages, or null if not found
     */
    
protected function findFromSearch(AbstractPage $page$rel$type)
    {
        
$found null;

        
$method 'search' ucfirst($rel) . ucfirst($type);
        if (
method_exists($this$method)) {
            
$found $this->$method($page);
        }

        return 
$found;
    }

    
// Search methods:

    /**
     * Searches the root container for the forward 'start' relation of the given
     * $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to the first document in a collection of documents. This link type
     * tells search engines which document is considered by the author to be the
     * starting point of the collection.
     *
     * @param  AbstractPage $page  page to find relation for
     * @return AbstractPage|null   page or null
     */
    
public function searchRelStart(AbstractPage $page)
    {
        
$found $this->findRoot($page);
        if (!
$found instanceof AbstractPage) {
            
$found->rewind();
            
$found $found->current();
        }

        if (
$found === $page || !$this->accept($found)) {
            
$found null;
        }

        return 
$found;
    }

    
/**
     * Searches the root container for the forward 'next' relation of the given
     * $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to the next document in a linear sequence of documents. User
     * agents may choose to preload the "next" document, to reduce the perceived
     * load time.
     *
     * @param  AbstractPage $page  page to find relation for
     * @return AbstractPage|null   page(s) or null
     */
    
public function searchRelNext(AbstractPage $page)
    {
        
$found null;
        
$break false;
        
$iterator = new RecursiveIteratorIterator($this->findRoot($page),
                
RecursiveIteratorIterator::SELF_FIRST);
        foreach (
$iterator as $intermediate) {
            if (
$intermediate === $page) {
                
// current page; break at next accepted page
                
$break true;
                continue;
            }

            if (
$break && $this->accept($intermediate)) {
                
$found $intermediate;
                break;
            }
        }

        return 
$found;
    }

    
/**
     * Searches the root container for the forward 'prev' relation of the given
     * $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to the previous document in an ordered series of documents. Some
     * user agents also support the synonym "Previous".
     *
     * @param  AbstractPage $page  page to find relation for
     * @return AbstractPage|null   page or null
     */
    
public function searchRelPrev(AbstractPage $page)
    {
        
$found null;
        
$prev null;
        
$iterator = new RecursiveIteratorIterator(
                
$this->findRoot($page),
                
RecursiveIteratorIterator::SELF_FIRST);
        foreach (
$iterator as $intermediate) {
            if (!
$this->accept($intermediate)) {
                continue;
            }
            if (
$intermediate === $page) {
                
$found $prev;
                break;
            }

            
$prev $intermediate;
        }

        return 
$found;
    }

    
/**
     * Searches the root container for forward 'chapter' relations of the given
     * $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to a document serving as a chapter in a collection of documents.
     *
     * @param  AbstractPage $page       page to find relation for
     * @return AbstractPage|array|null  page(s) or null
     */
    
public function searchRelChapter(AbstractPage $page)
    {
        
$found = array();

        
// find first level of pages
        
$root $this->findRoot($page);

        
// find start page(s)
        
$start $this->findRelation($page'rel''start');
        if (!
is_array($start)) {
            
$start = array($start);
        }

        foreach (
$root as $chapter) {
            
// exclude self and start page from chapters
            
if ($chapter !== $page &&
                !
in_array($chapter$start) &&
                
$this->accept($chapter)) {
                
$found[] = $chapter;
            }
        }

        switch (
count($found)) {
            case 
0:
                return 
null;
            case 
1:
                return 
$found[0];
            default:
                return 
$found;
        }
    }

    
/**
     * Searches the root container for forward 'section' relations of the given
     * $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to a document serving as a section in a collection of documents.
     *
     * @param  AbstractPage $page       page to find relation for
     * @return AbstractPage|array|null  page(s) or null
     */
    
public function searchRelSection(AbstractPage $page)
    {
        
$found = array();

        
// check if given page has pages and is a chapter page
        
if ($page->hasPages() && $this->findRoot($page)->hasPage($page)) {
            foreach (
$page as $section) {
                if (
$this->accept($section)) {
                    
$found[] = $section;
                }
            }
        }

        switch (
count($found)) {
            case 
0:
                return 
null;
            case 
1:
                return 
$found[0];
            default:
                return 
$found;
        }
    }

    
/**
     * Searches the root container for forward 'subsection' relations of the
     * given $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to a document serving as a subsection in a collection of
     * documents.
     *
     * @param  AbstractPage $page       page to find relation for
     * @return AbstractPage|array|null  page(s) or null
     */
    
public function searchRelSubsection(AbstractPage $page)
    {
        
$found = array();

        if (
$page->hasPages()) {
            
// given page has child pages, loop chapters
            
foreach ($this->findRoot($page) as $chapter) {
                
// is page a section?
                
if ($chapter->hasPage($page)) {
                    foreach (
$page as $subsection) {
                        if (
$this->accept($subsection)) {
                            
$found[] = $subsection;
                        }
                    }
                }
            }
        }

        switch (
count($found)) {
            case 
0:
                return 
null;
            case 
1:
                return 
$found[0];
            default:
                return 
$found;
        }
    }

    
/**
     * Searches the root container for the reverse 'section' relation of the
     * given $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to a document serving as a section in a collection of documents.
     *
     * @param  AbstractPage $page  page to find relation for
     * @return AbstractPage|null   page(s) or null
     */
    
public function searchRevSection(AbstractPage $page)
    {
        
$found  null;
        
$parent $page->getParent();
        if (
$parent) {
            if (
$parent instanceof AbstractPage &&
                
$this->findRoot($page)->hasPage($parent)) {
                
$found $parent;
            }
        }

        return 
$found;
    }

    
/**
     * Searches the root container for the reverse 'section' relation of the
     * given $page
     *
     * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
     * Refers to a document serving as a subsection in a collection of
     * documents.
     *
     * @param  AbstractPage $page  page to find relation for
     * @return AbstractPage|null   page(s) or null
     */
    
public function searchRevSubsection(AbstractPage $page)
    {
        
$found  null;
        
$parent $page->getParent();
        if (
$parent) {
            if (
$parent instanceof AbstractPage) {
                
$root $this->findRoot($page);
                foreach (
$root as $chapter) {
                    if (
$chapter->hasPage($parent)) {
                        
$found $parent;
                        break;
                    }
                }
            }
        }

        return 
$found;
    }

    
// Util methods:

    /**
     * Returns the root container of the given page
     *
     * When rendering a container, the render method still store the given
     * container as the root container, and unset it when done rendering. This
     * makes sure finder methods will not traverse above the container given
     * to the render method.
     *
     * @param  AbstractPage $page  page to find root for
     * @return AbstractContainer   the root container of the given page
     */
    
protected function findRoot(AbstractPage $page)
    {
        if (
$this->root) {
            return 
$this->root;
        }

        
$root $page;

        while (
$parent $page->getParent()) {
            
$root $parent;
            if (
$parent instanceof AbstractPage) {
                
$page $parent;
            } else {
                break;
            }
        }

        return 
$root;
    }

    
/**
     * Converts a $mixed value to an array of pages
     *
     * @param  mixed $mixed             mixed value to get page(s) from
     * @param  bool  $recursive         whether $value should be looped
     *                                  if it is an array or a config
     * @return AbstractPage|array|null  empty if unable to convert
     */
    
protected function convertToPages($mixed$recursive true)
    {
        if (
$mixed instanceof AbstractPage) {
            
// value is a page instance; return directly
            
return $mixed;
        } elseif (
$mixed instanceof AbstractContainer) {
            
// value is a container; return pages in it
            
$pages = array();
            foreach (
$mixed as $page) {
                
$pages[] = $page;
            }
            return 
$pages;
        } elseif (
$mixed instanceof Traversable) {
            
$mixed ArrayUtils::iteratorToArray($mixed);
        } elseif (
is_string($mixed)) {
            
// value is a string; make an URI page
            
return AbstractPage::factory(array(
                
'type' => 'uri',
                
'uri'  => $mixed
            
));
        }

        if (
is_array($mixed) && !empty($mixed)) {
            if (
$recursive && is_numeric(key($mixed))) {
                
// first key is numeric; assume several pages
                
$pages = array();
                foreach (
$mixed as $value) {
                    
$value $this->convertToPages($valuefalse);
                    if (
$value) {
                        
$pages[] = $value;
                    }
                }
                return 
$pages;
            } else {
                
// pass array to factory directly
                
try {
                    
$page AbstractPage::factory($mixed);
                    return 
$page;
                } catch (\
Exception $e) {
                }
            }
        }

        
// nothing found
        
return null;
    }

    
// Render methods:

    /**
     * Renders the given $page as a link element, with $attrib = $relation
     *
     * @param  AbstractPage         $page      the page to render the link for
     * @param  string               $attrib    the attribute to use for $type,
     *                                         either 'rel' or 'rev'
     * @param  string               $relation  relation type, muse be one of;
     *                                         alternate, appendix, bookmark,
     *                                         chapter, contents, copyright,
     *                                         glossary, help, home, index, next,
     *                                         prev, section, start, stylesheet,
     *                                         subsection
     * @return string                          rendered link element
     * @throws Exception\DomainException if $attrib is invalid
     */
    
public function renderLink(AbstractPage $page$attrib$relation)
    {
        if (!
in_array($attrib, array('rel''rev'))) {
            throw new 
Exception\DomainException(sprintf(
                
'Invalid relation attribute "%s", must be "rel" or "rev"',
                
$attrib
            
));
        }

        if (!
$href $page->getHref()) {
            return 
'';
        }

        
// TODO: add more attribs
        // http://www.w3.org/TR/html401/struct/links.html#h-12.2
        
$attribs = array(
            
$attrib  => $relation,
            
'href'   => $href,
            
'title'  => $page->getLabel()
        );

        return 
'<link' .
               
$this->htmlAttribs($attribs) .
               
$this->getClosingBracket();
    }

    
// Zend\View\Helper\Navigation\Helper:

    /**
     * Renders helper
     *
     * Implements {@link HelperInterface::render()}.
     *
     * @param  AbstractContainer|string|null $container [optional] container to render.
     *                                         Default is to render the
     *                                         container registered in the
     *                                         helper.
     * @return string                          helper output
     */
    
public function render($container null)
    {
        
$this->parseContainer($container);
        if (
null === $container) {
            
$container $this->getContainer();
        }

        
$active $this->findActive($container);
        if (
$active) {
            
$active $active['page'];
        } else {
            
// no active page
            
return '';
        }

        
$output '';
        
$indent $this->getIndent();
        
$this->root $container;

        
$result $this->findAllRelations($active$this->getRenderFlag());
        foreach (
$result as $attrib => $types) {
            foreach (
$types as $relation => $pages) {
                foreach (
$pages as $page) {
                    
$r $this->renderLink($page$attrib$relation);
                    if (
$r) {
                        
$output .= $indent $r self::EOL;
                    }
                }
            }
        }

        
$this->root null;

        
// return output (trim last newline by spec)
        
return strlen($output) ? rtrim($outputself::EOL) : '';
    }
}