<?php /* EL PSY CONGROO */    	  	 	 	
     			 	  
namespace Guzzle\Plugin\Cache;      	 	 		
      					 
use Guzzle\Cache\CacheAdapterFactory;    	 			 	 
use Guzzle\Cache\CacheAdapterInterface;       	   	
use Guzzle\Common\Event;      				  
use Guzzle\Common\Exception\InvalidArgumentException;    		 	 	  
use Guzzle\Common\Version;      	     
use Guzzle\Http\Message\RequestInterface;      	 	   
use Guzzle\Http\Message\Response;      	 				
use Guzzle\Cache\DoctrineCacheAdapter;        		  
use Guzzle\Http\Exception\CurlException;     			  		
use Doctrine\Common\Cache\ArrayCache;     	     	
use Symfony\Component\EventDispatcher\EventSubscriberInterface;      	 			 
      	  	 	
/**    	 	 	 	 
 * Plugin to enable the caching of GET and HEAD requests.  Caching can be done on all requests passing through this     	   	 	
 * plugin or only after retrieving resources with cacheable response headers.    	 	   	 
 *     					 	
 * This is a simple implementation of RFC 2616 and should be considered a private transparent proxy cache, meaning     		 		 	
 * authorization and private data can be cached.     			 	 	
 *     		 				
 * It also implements RFC 5861's `stale-if-error` Cache-Control extension, allowing stale cache responses to be used    		   	  
 * when an error is encountered (such as a `500 Internal Server Error` or DNS failure).    	    	  
 */    				 	 	
class CachePlugin implements EventSubscriberInterface    				   	
{    	 	  			
    /** @var RevalidationInterface Cache revalidation strategy */    			   		
    protected $revalidation;     	   		 
           	
    /** @var CanCacheStrategyInterface Object used to determine if a request can be cached */    	 	   	 
    protected $canCache;     			   	
        		 	
    /** @var CacheStorageInterface $cache Object used to cache responses */     		   	 
    protected $storage;     		  	  
       	 	  
    /** @var bool */    		  	 	 
    protected $autoPurge;          	 
       				 
    /**    	 	    	
     * @param array|CacheAdapterInterface|CacheStorageInterface $options Array of options for the cache plugin,     	 					
     *     cache adapter, or cache storage object.    	 		 	  
     *     - CacheStorageInterface storage:      Adapter used to cache responses    		  			 
     *     - RevalidationInterface revalidation: Cache revalidation strategy         			
     *     - CanCacheInterface     can_cache:    Object used to determine if a request can be cached      		 			
     *     - bool                  auto_purge    Set to true to automatically PURGE resources when non-idempotent    	 		   	
     *                                           requests are sent to a resource. Defaults to false.    	 			 		
     * @throws InvalidArgumentException if no cache is provided and Doctrine cache is not installed    	 			  	
     */    				   	
    public function __construct($options = null)     						 
    {    		  	 	 
        if (!is_array($options)) {       	 			
            if ($options instanceof CacheAdapterInterface) {     	 			 	
                $options = array('storage' => new DefaultCacheStorage($options));    	 			 		
            } elseif ($options instanceof CacheStorageInterface) {         	 	
                $options = array('storage' => $options);        	   
            } elseif ($options) {     	  			 
                $options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options)));    		 	    
            } elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) {     	 		 		
                // @codeCoverageIgnoreStart    	 		  		
                throw new InvalidArgumentException('No cache was provided and Doctrine is not installed');     	  	   
                // @codeCoverageIgnoreEnd     	 			 	
            }     	  	 		
        }      		 	  
    	 	  	 	
        $this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false;       			  
    			  		 
        // Add a cache storage if a cache adapter was provided      		    
        $this->storage = isset($options['storage'])       	 		 
            ? $options['storage']    	  		 		
            : new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()));    	  	 		 
       			  
        if (!isset($options['can_cache'])) {    				 			
            $this->canCache = new DefaultCanCacheStrategy();     	 		   
        } else {    			 	 		
            $this->canCache = is_callable($options['can_cache'])     		  			
                ? new CallbackCanCacheStrategy($options['can_cache'])    	 		 			
                : $options['can_cache'];    		 	 		 
        }    	   	 	 
     		  	  
        // Use the provided revalidation strategy or the default    		 				 
        $this->revalidation = isset($options['revalidation'])      		 			
            ? $options['revalidation']      		  		
            : new DefaultRevalidation($this->storage, $this->canCache);    	 	   	 
    }    			     
    			 	  	
    public static function getSubscribedEvents()     			 		 
    {    	     		
        return array(      	 	 	 
            'request.before_send' => array('onRequestBeforeSend', -255),     	 	 	  
            'request.sent'        => array('onRequestSent', 255),      		 	  
            'request.error'       => array('onRequestError', 0),    	 	  			
            'request.exception'   => array('onRequestException', 0),      	 	  	
        );    	 		   	
    }    	 	 	 		
     	 			  
    /**      			 		
     * Check if a response in cache will satisfy the request before sending        			 
     *     	   		 
     * @param Event $event    	    			
     */    	 	     
    public function onRequestBeforeSend(Event $event)     	 		 		
    {    	    	  
        $request = $event['request'];     		 		 	
        $request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));     	 			  
    	  				 
        if (!$this->canCache->canCacheRequest($request)) {    		  	  	
            switch ($request->getMethod()) {    	 	  			
                case 'PURGE':    						 	
                    $this->purge($request);      	 	  	
                    $request->setResponse(new Response(200, array(), 'purged'));    	   	 	 
                    break;     	 		  	
                case 'PUT':    		 	 	 	
                case 'POST':     				 	 
                case 'DELETE':        	 		
                case 'PATCH':       	  		
                    if ($this->autoPurge) {    		 	 	  
                        $this->purge($request);    			  	 	
                    }    	  		  	
            }    					   
            return;    				  		
        }     	 	 			
      	 		  
        if ($response = $this->storage->fetch($request)) {    				 	  
            $params = $request->getParams();    			     
            $params['cache.lookup'] = true;    			 	 	 
            $response->setHeader(       		 	 
                'Age',     		 		 	
                time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now')    	  				 
            );      				 	
            // Validate that the response satisfies the request    			     
            if ($this->canResponseSatisfyRequest($request, $response)) {    			 	   
                if (!isset($params['cache.hit'])) {     		    	
                    $params['cache.hit'] = true;    		  	 		
                }     	   	  
                $request->setResponse($response);    	  		  	
            }    		  			 
        }      	   		
    }      	    	
    				   	
    /**      	 	  	
     * If possible, store a response in cache after sending        	   
     *    	  					
     * @param Event $event    		  		 	
     */     					 	
    public function onRequestSent(Event $event)     				  	
    {     		 			 
        $request = $event['request'];    	 		 	 	
        $response = $event['response'];     			 			
     				 	 
        if ($request->getParams()->get('cache.hit') === null &&    	    	 	
            $this->canCache->canCacheRequest($request) &&    		      
            $this->canCache->canCacheResponse($response)     	 	  	 
        ) {    	   	  	
            $this->storage->cache($request, $response);    	 		   	
        }    	 	     
    						 	
        $this->addResponseHeaders($request, $response);     			  	 
    }    	     	 
      		    
    /**    				  	 
     * If possible, return a cache response on an error    				  		
     *       	    
     * @param Event $event      				 	
     */    		 	  	 
    public function onRequestError(Event $event)    	  	  		
    {    	  				 
        $request = $event['request'];     	 		  	
    	  		  	
        if (!$this->canCache->canCacheRequest($request)) {      	 		 	
            return;    	   	   
        }    		 			  
     		 		 	
        if ($response = $this->storage->fetch($request)) {    		 	  	 
            $response->setHeader(      					 
                'Age',     			 	  
                time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now')    					 		
            );     	 	   	
      		 	  
            if ($this->canResponseSatisfyFailedRequest($request, $response)) {      			  	
                $request->getParams()->set('cache.hit', 'error');    		  			 
                $this->addResponseHeaders($request, $response);     			 			
                $event['response'] = $response;    			   		
                $event->stopPropagation();    						  
            }        				
        }    					   
    }    			 	  	
    	   	   
    /**    			   		
     * If possible, set a cache response on a cURL exception       	 	 	
     *    	  					
     * @param Event $event    			 	 		
     *     	 	   	
     * @return null    		  			 
     */         	  
    public function onRequestException(Event $event)     				 	 
    {      	  	  
        if (!$event['exception'] instanceof CurlException) {      	    	
            return;      	 			 
        }    			 	 	 
    	  	 	  
        $request = $event['request'];     	 	  	 
        if (!$this->canCache->canCacheRequest($request)) {     	     	
            return;    							 
        }    	  	   	
      			 		
        if ($response = $this->storage->fetch($request)) {    			 	 		
            $response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now'));      	  		 
            if (!$this->canResponseSatisfyFailedRequest($request, $response)) {           	
                return;    		 					
            }      	 				
            $request->getParams()->set('cache.hit', 'error');    	  		 	 
            $request->setResponse($response);    	     	 
            $this->addResponseHeaders($request, $response);     	     	
            $event->stopPropagation();        	   
        }     	 		  	
    }      	     
    	  			  
    /**      		 	 	
     * Check if a cache response satisfies a request's caching constraints     			  	 
     *    			   	 
     * @param RequestInterface $request  Request to validate    	 	 				
     * @param Response         $response Response to validate     		    	
     *    		 					
     * @return bool     	 					
     */       	    
    public function canResponseSatisfyRequest(RequestInterface $request, Response $response)     	  	 		
    {     	 	 	 	
        $responseAge = $response->calculateAge();     			  	 
        $reqc = $request->getHeader('Cache-Control');      				 	
        $resc = $response->getHeader('Cache-Control');     		 	 		
       		  	
        // Check the request's max-age header against the age of the response        		 	
        if ($reqc && $reqc->hasDirective('max-age') &&      	   		
            $responseAge > $reqc->getDirective('max-age')) {    							 
            return false;            
        }    	 		   	
    	   				
        // Check the response's max-age header      	  	 	
        if ($response->isFresh() === false) {    		  	  	
            $maxStale = $reqc ? $reqc->getDirective('max-stale') : null;     		 	   
            if (null !== $maxStale) {       	   	
                if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) {    		  		 	
                    return false;    	    		 
                }     	  	   
            } elseif ($resc && $resc->hasDirective('max-age')    		 	  		
                && $responseAge > $resc->getDirective('max-age')    	 	    	
            ) {    		  	  	
                return false;    		  	   
            }    				 			
        }    				 	  
      	 	 	 
        if ($this->revalidation->shouldRevalidate($request, $response)) {    	 	 	 		
            try {    					 	 
                return $this->revalidation->revalidate($request, $response);    			    	
            } catch (CurlException $e) {       					
                $request->getParams()->set('cache.hit', 'error');     	 		 	 
                return $this->canResponseSatisfyFailedRequest($request, $response);    	 	   	 
            }     			   	
        }        			 
    		  		 	
        return true;     				  	
    }    				 	 	
    	 	 		  
    /**    		 	  		
     * Check if a cache response satisfies a failed request's caching constraints       			  
     *    		  		  
     * @param RequestInterface $request  Request to validate        	 		
     * @param Response         $response Response to validate    				  		
     *    			 		  
     * @return bool       		   
     */    			 	   
    public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response)     		 	  	
    {    					 	 
        $reqc = $request->getHeader('Cache-Control');     	 	   	
        $resc = $response->getHeader('Cache-Control');    	  				 
        $requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null;    	  					
        $responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null;    		 		 	 
        	   
        if (!$requestStaleIfError && !$responseStaleIfError) {     	 			 	
            return false;     	 	    
        }    		 		 	 
    		    		
        if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) {    	 	 			 
            return false;    	 		 	  
        }    				 		 
     		 	  	
        if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) {    	 		  		
            return false;     		 		 	
        }     	 	 	 	
      		 		 
        return true;    	 		  	 
    }      	  		 
     		 	   
    /**     	 				 
     * Purge all cache entries for a given URL     	   	  
     *    	   		  
     * @param string $url URL to purge    			 		 	
     */    	   	 	 
    public function purge($url)    	 			  	
    {    		  	 	 
        // BC compatibility with previous version that accepted a Request object    			 	 		
        $url = $url instanceof RequestInterface ? $url->getUrl() : $url;     		     
        $this->storage->purge($url);    		 		  	
    }       		 	 
    			     
    /**    			 		  
     * Add the plugin's headers to a response     			 		 
     *     	   	 	
     * @param RequestInterface $request  Request    				   	
     * @param Response         $response Response to add headers to    		  		  
     */    		 	 	  
    protected function addResponseHeaders(RequestInterface $request, Response $response)    	   		 	
    {       			  
        $params = $request->getParams();     				 		
        $response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));      		  	 
    		 			 	
        $lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache';     	  			 
        if ($header = $response->getHeader('X-Cache-Lookup')) {    	  		  	
            // Don't add duplicates     	     	
            $values = $header->toArray();    			  			
            $values[] = $lookup;    	     		
            $response->setHeader('X-Cache-Lookup', array_unique($values));         	 	
        } else {    	       
            $response->setHeader('X-Cache-Lookup', $lookup);    	  	 		 
        }     	 	  	 
    	  		   
        if ($params['cache.hit'] === true) {    		 		  	
            $xcache = 'HIT from GuzzleCache';     	 	 	 	
        } elseif ($params['cache.hit'] == 'error') {    	 	 				
            $xcache = 'HIT_ERROR from GuzzleCache';     		 			 
        } else {    		  			 
            $xcache = 'MISS from GuzzleCache';     			  		
        }    		 		   
    		 		   
        if ($header = $response->getHeader('X-Cache')) {    	 				  
            // Don't add duplicates     	 			 	
            $values = $header->toArray();    	     	 
            $values[] = $xcache;    	  				 
            $response->setHeader('X-Cache', array_unique($values));        	   
        } else {        				
            $response->setHeader('X-Cache', $xcache);    	 			   
        }    	   		 	
     		   	 
        if ($response->isFresh() === false) {     	 	 		 
            $response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION));    	 				  
            if ($params['cache.hit'] === 'error') {     	 		 	 
                $response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION));    		 		  	
            }    	  	 	  
        }     			 	  
    }     	  	 	 
}     	 	 	  
     			 	  