<?php
# The MIT License

# Copyright (c) 2011 Nicolas Martin

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# http://joliclic.free.fr/php/oembed-proxy/en/

class OEmbedProxy {
    private $_url = '';
    
    private $_format = 'json';
    
    private $_maxwidth = null;
    
    private $_maxheight = null;
    
    //TODO add wmode ? (Flash transparency)
    
    private $_jsCallback = null;
    
    private $_provider = null;
    
    public function __construct
    ($aUrl = '', $aFormat = 'json', $aMaxWidth = null, $aMaxheight = null,
     $aJSCallback = null)
    {
        $this->setUrl($aUrl);
        $this->setFormat($aFormat);
        if (!is_null($aMaxWidth))
            $this->setMaxWidth($aMaxWidth);
        if (!is_null($aMaxheight))
            $this->setMaxHeight($aMaxheight);
        if (!is_null($aJSCallback))
            $this->setJSCallback($aJSCallback);
    }
    
    public function forward() {
        if (is_null($this->_provider))
            throw new Exception('Error: invalid url');
        
        if (isset($this->_provider['endpoint-'.$this->_format]))
            $url = $this->_provider['endpoint-'.$this->_format];
        else
            $url = $this->_provider['endpoint'];
        
        $url .= '?url='.rawurlencode($this->_url).'&format='.$this->_format;
        
        if (!is_null($this->_maxwidth))
            $url .= '&maxwidth='.$this->_maxwidth;
        if (!is_null($this->_maxheight))
            $url .= '&maxheight='.$this->_maxheight;
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        // don't display the http headers
        curl_setopt($ch, CURLOPT_HEADER, false);
        // don't display the result directly to the browser
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // 5 seconds timeout
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        // follow redirections
        // doesn't work on free.fr :( (safe_mode)
        //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        
        if (isset($_SERVER['HTTP_USER_AGENT']))
            curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
        
        $rv = curl_exec($ch);
        if (!$rv) {
            self::setHttpStatus(503);
            exit;
        }
        
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if ($http_code != 200) {
            self::setHttpStatus($http_code);
            exit;
        }
        
        header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
        header('Pragma: no-cache');
        
        if (!is_null($this->_jsCallback)) {
            header('Content-type: text/javascript');
            header('Content-disposition: filename="oembed-'.time().'.js"; '); 
            echo $this->_jsCallback.'('.$rv.')';
        } else {
            switch ($this->_format) {
                case 'xml':
                    header('Content-type: text/xml');
                    header('Content-disposition: filename="oembed-'.time().'.xml"; '); 
                    break;
                case 'json':
                default:
                    header('Content-type: application/json');
                    header('Content-disposition: filename="oembed-'.time().'.json"; '); 
            }
            echo $rv;
        }
        exit;
    }
    
    public function setUrl($aStr) {
        if (!$aStr) {
            $this->_provider = null;
            $this->_url = '';
            
            return false;
        }
        
        foreach (self::$providers as $provider) {
            if (preg_match('/' . $provider['url_regexp'] . '/', $aStr)) {
                $this->_provider = $provider;
                $this->_url = $aStr;
                
                return true;
                break;
            }
        }
        
        $this->_provider = null;
        $this->_url = '';
        
        return false;
    }
    
    public function setFormat($aStr) {
        switch ($aStr) {
            case 'json':
                $this->_format = 'json';
                break;
            case 'xml':
                $this->_format = 'xml';
                break;
            default:
                throw new Exception('Error: only json and xml formats are supported');
        }
        
        return $this->_format;
    }
    
    public function getFormat() {
        return $this->_format;
    }
    
    public function setMaxWidth($aInt) {
        $i = (int) $aInt;
        if ($i > 0)
            $this->_maxwidth = $i;
        
        return $this->_maxwidth;
    }
    
    public function getMaxWidth() {
        return $this->_maxwidth;
    }
    
    public function setMaxHeight($aInt) {
        $i = (int) $aInt;
        if ($i > 0)
            $this->_maxheight = $i;
        
        return $this->_maxheight;
    }
    
    public function getMaxHeight() {
        return $this->_maxheight;
    }
    
    private $_reservedJSWords =
        array('break', 'case', 'catch', 'continue', 'debugger', 'default',
              'delete', 'do', 'else', 'finally', 'for', 'function', 'if', 'in',
              'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try',
              'typeof', 'var', 'void', 'while', 'with',
              'null', 'true', 'false',
              'class', 'enum', 'export', 'extends', 'import', 'super',
              'implements', 'interface', 'let', 'package', 'private',
              'protected', 'public', 'static', 'yield', 'const');
    
    public function setJSCallback($aStr) {
        if (is_null($aStr)) {
            $this->_jsCallback = null;
            return null;
        }
        
        if (!preg_match('/^[$A-Za-z_][0-9A-Za-z_]*$/', $aStr))
            throw new Exception('Error: callback is not a valid JavaScript name');
        
        if (in_array($aStr, $this->_reservedJSWords))
            throw new Exception('Error: callback is not a valid JavaScript name');
        
        $this->_jsCallback = $aStr;
        
        return $aStr;
    }
    
    public static $http_status = array(
        // [Informational 1xx]
        100 => 'Continue',
        101 => 'Switching Protocols',
        
        // [Successful 2xx]
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        
        // [Redirection 3xx]
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => '(Unused)',
        307 => 'Temporary Redirect',
        
        // [Client Error 4xx]
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        
        // [Server Error 5xx]
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported'
    );
    
    public static function setHttpStatus($aStatusCode) {
        $msg = '-';
        if (isset(self::$http_status[$aStatusCode]))
            $msg = self::$http_status[$aStatusCode];
        
        if (preg_match('/cgi/', PHP_SAPI))
            header('Status: '.$aStatusCode.' '.$msg);
        else
            header($msg, true, $aStatusCode);
    }
    
    public static $providers = array(
        // a name => array of properties
        // properties:
        //  - endpoint: the real url to request
        //  - endpoint-json: the real url to request the json response
        //  - endpoint-xml: the real url to request the xml response
        //  - url_regexp: the regexp pattern that the data requested should
        //    match
        //  - title: a readable label of the real service
        //
        // Note that a provider must define a 'endpoint' or the 2
        // 'endpoint-json' and 'endpoint-xml' properties (some services uses 2
        // distinct endpoints)
        
        'youtube' =>
            array('endpoint'   => 'http://www.youtube.com/oembed',
                  'url_regexp' => 'youtube\\.com\\/watch.+v=[\\w-]+&?',
                  // url example: "http://www.youtube.com/watch?v=vk1HvP7NO5w"
                  'title'      => 'YouTube'),
        'dailymotion' =>
            array('endpoint'   => 'http://www.dailymotion.com/api/oembed/',
                  'url_regexp' => 'dailymotion\\.com\\/.+',
                  // url example: "http://www.dailymotion.com/video/x5ioet_phoenix-mars-lander_tech"
                  'title'      => 'Dailymotion'),
        'vimeo' =>
            array('endpoint-json' => 'http://www.vimeo.com/api/oembed.json',
                  'endpoint-xml'  => 'http://www.vimeo.com/api/oembed.json',
                  'url_regexp'    => 'vimeo\\.com\\/.*',
                  // url example: "http://www.vimeo.com/1211060"
                  'title'         => 'Vimeo'),
    );
}
?>