<?php



/**

 * W3 CDN Base class

 */

define('W3TC_CDN_RESULT_HALT', -1);

define('W3TC_CDN_RESULT_ERROR', 0);

define('W3TC_CDN_RESULT_OK', 1);

define('W3TC_CDN_HEADER_NONE', 'none');

define('W3TC_CDN_HEADER_UPLOADABLE', 'uploadable');

define('W3TC_CDN_HEADER_MIRRORING', 'mirroring');



/**

 * Class W3_Cdn_Base

 */

class W3_Cdn_Base {

    /**

     * Engine configuration

     *

     * @var array

     */

    var $_config = array();



    /**

     * Cache config

     * @var array

     */

    var $cache_config = array();



    /**

     * gzip extension

     *

     * @var string

     */

    var $_gzip_extension = '.gzip';



    /**

     * Last error

     *

     * @var string

     */

    var $_last_error = '';



    /**

     * PHP5 Constructor

     *

     * @param array $config

     */

    function __construct($config = array()) {

        $this->_config = array_merge(array(

            'debug' => false,

            'ssl' => 'auto',

            'compression' => false

        ), $config);

    }



    /**

     * Upload files to CDN

     *

     * @param array $files takes array consisting of array(array('local_path'=>'', 'remote_path'=>''))

     * @param array $results

     * @param boolean $force_rewrite

     * @return boolean

     */

    function upload($files, &$results, $force_rewrite = false) {

        $results = $this->_get_results($files, W3TC_CDN_RESULT_HALT, 'Not implemented.');



        return false;

    }



    /**

     * Delete files from CDN

     *

     * @param array $files

     * @param array $results

     * @return boolean

     */

    function delete($files, &$results) {

        $results = $this->_get_results($files, W3TC_CDN_RESULT_HALT, 'Not implemented.');



        return false;

    }



    /**

     * Purge files from CDN

     *

     * @param array $files

     * @param array $results

     * @return boolean

     */

    function purge($files, &$results) {

        return $this->upload($files, $results, true);

    }



    /**

     * Purge CDN completely

     * @param $results

     * @return bool

     */

    function purge_all(&$results) {

        $results = $this->_get_results(array(), W3TC_CDN_RESULT_HALT, 'Not implemented.');



        return false;

    }



    /**

     * Test CDN server

     *

     * @param string $error

     * @return boolean

     */

    function test(&$error) {

        if (!$this->_test_domains($error)) {

            return false;

        }



        return true;

    }



    /**

     * Create bucket / container for some CDN engines

     *

     * @param string $container_id

     * @param string $error

     * @return boolean

     */

    function create_container(&$container_id, &$error) {

        $error = 'Not implemented.';



        return false;

    }



    /**

     * Returns first domain

     *

     * @param string $path

     * @return string

     */

    function get_domain($path = '') {

        $domains = $this->get_domains();

        $count = count($domains);



        if ($count) {

            switch (true) {

                /**

                 * Reserved CSS

                 */

                case (isset($domains[0]) && $this->_is_css($path)):

                    $domain = $domains[0];

                    break;



                /**

                 * Reserved JS in head

                 */

                case (isset($domains[1]) && $this->_is_js($path)):

                    $domain = $domains[1];

                    break;



                /**

                 * Reserved JS after body

                 */

                case (isset($domains[2]) && $this->_is_js_body($path)):

                    $domain = $domains[2];

                    break;



                /**

                 * Reserved JS before /body

                 */

                case (isset($domains[3]) && $this->_is_js_footer($path)):

                    $domain = $domains[3];

                    break;



                default:

                    if ($count > 4) {

                        $domain = $this->_get_domain(array_slice($domains, 4), $path);

                    } else {

                        $domain = $this->_get_domain($domains, $path);

                    }

            }



            /**

             * Custom host for SSL

             */

            list($domain_http, $domain_https) = array_map('trim', explode(',', $domain . ','));



            $scheme = $this->_get_scheme();



            switch ($scheme) {

                case 'http':

                    $domain = $domain_http;

                    break;



                case 'https':

                    $domain = ($domain_https ? $domain_https : $domain_http);

                    break;

            }



            return $domain;

        }



        return false;

    }



    /**

     * Returns array of CDN domains

     *

     * @return array

     */

    function get_domains() {

        return array();

    }



    /**

     * Returns via string

     *

     * @return string

     */

    function get_via() {

        $domain = $this->get_domain();



        if ($domain) {

            return $domain;

        }



        return 'N/A';

    }



    /**

     * Formats URL

     *

     * @param string $path

     */

    function format_url($path) {

        $url = $this->_format_url($path);



        if ($url && $this->_config['compression'] && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false && $this->_may_gzip($path)) {

            if (($qpos = strpos($url, '?')) !== false) {

                $url = substr_replace($url, $this->_gzip_extension, $qpos, 0);

            } else {

                $url .= $this->_gzip_extension;

            }

        }



        return $url;

    }



    /**

     * Returns prepend path

     *

     * @param string $path

     * @return string

     */

    function get_prepend_path($path) {

        $domain = $this->get_domain($path);



        if ($domain) {

            $scheme = $this->_get_scheme();

            $url = sprintf('%s://%s', $scheme, $domain);



            return $url;

        }



        return false;

    }



    /**

     * Formats URL

     *

     * @param string $path

     * @return string

     */

    function _format_url($path) {

        $domain = $this->get_domain($path);



        if ($domain) {

            $scheme = $this->_get_scheme();

            $url = sprintf('%s://%s/%s', $scheme, $domain, $path);



            return $url;

        }



        return false;

    }



    /**

     * Returns results

     *

     * @param array $files

     * @param integer $result

     * @param string $error

     * @return array

     */

    function _get_results($files, $result = W3TC_CDN_RESULT_OK, $error = 'OK') {

        $results = array();



        foreach ($files as $key => $file) {

            if (is_array($file)) {

                $local_path = $file['local_path'];

                $remote_path = $file['remote_path'];

            } else {

                $local_path = $key;

                $remote_path = $file;

            }



            $results[] = $this->_get_result($local_path, $remote_path, $result, $error);

        }



        return $results;

    }



    /**

     * Returns file process result

     *

     * @param string $local_path

     * @param string $remote_path

     * @param integer $result

     * @param string $error

     * @return array

     */

    function _get_result($local_path, $remote_path, $result = W3TC_CDN_RESULT_OK, $error = 'OK') {

        if ($this->_config['debug']) {

            $this->_log($local_path, $remote_path, $error);

        }



        return array(

            'local_path' => $local_path,

            'remote_path' => $remote_path,

            'result' => $result,

            'error' => $error

        );

    }



    /**

     * Check for errors

     *

     * @param array $results

     * @return bool

     */

    function _is_error($results) {

        foreach ($results as $result) {

            if ($result['result'] != W3TC_CDN_RESULT_OK) {

                return true;

            }

        }



        return false;

    }



    /**

     * Returns headers for file

     *

     * @param array $file CDN file array

     * @return array

     */

    function _get_headers($file) {

        w3_require_once(W3TC_INC_DIR . '/functions/mime.php');

        $local_path = $file['local_path'];

        $mime_type = w3_get_mime_type($local_path);

        $last_modified = time();



        $link = $file['original_url'];



        $headers = array(

            'Content-Type' => $mime_type,

            'Last-Modified' => w3_http_date($last_modified),

            'Access-Control-Allow-Origin' => '*',

            'Link' => '<' . $link  .'>; rel="canonical"'

        );



        if (isset($this->cache_config[$mime_type])) {

            if ($this->cache_config[$mime_type]['etag']) {

                $headers['Etag'] = @md5_file($local_path);

            }



            if ($this->cache_config[$mime_type]['w3tc']) {

                $headers['X-Powered-By'] = W3TC_POWERED_BY;

            }



            if ($this->cache_config[$mime_type]['expires']) {

                $headers['Expires'] = w3_http_date(time() + $this->cache_config[$mime_type]['lifetime']);

            }



            switch ($this->cache_config[$mime_type]['cache_control']) {

                case 'cache':

                    $headers = array_merge($headers, array(

                        'Pragma' => 'public',

                        'Cache-Control' => 'public'

                    ));

                    break;



                case 'cache_public_maxage':

                    $headers = array_merge($headers, array(

                        'Pragma' => 'public',

                        'Cache-Control' => ($this->cache_config[$mime_type]['expires'] ? '' : 'max-age=' . $this->cache_config[$mime_type]['lifetime'] .', ') . 'public'

                    ));

                    break;



                case 'cache_validation':

                    $headers = array_merge($headers, array(

                        'Pragma' => 'public',

                        'Cache-Control' => 'public, must-revalidate, proxy-revalidate'

                    ));

                    break;



                case 'cache_noproxy':

                    $headers = array_merge($headers, array(

                        'Pragma' => 'public',

                        'Cache-Control' => 'public, must-revalidate'

                    ));

                    break;



                case 'cache_maxage':

                    $headers = array_merge($headers, array(

                        'Pragma' => 'public',

                        'Cache-Control' => ($this->cache_config[$mime_type]['expires'] ? '' : 'max-age=' . $this->cache_config[$mime_type]['lifetime'] .', ') . 'public, must-revalidate, proxy-revalidate'

                    ));

                    break;



                case 'no_cache':

                    $headers = array_merge($headers, array(

                        'Pragma' => 'no-cache',

                        'Cache-Control' => 'max-age=0, private, no-store, no-cache, must-revalidate'

                    ));

                    break;

            }

        }



        return $headers;

    }



    /**

     * Use gzip compression only for text-based files

     *

     * @param string $file

     * @return boolean

     */

    function _may_gzip($file) {

        /**

         * Remove query string

         */

        $file = preg_replace('~\?.*$~', '', $file);



        /**

         * Check by file extension

         */

        if (preg_match('~\.(ico|js|css|xml|xsd|xsl|svg|htm|html|txt)$~i', $file)) {

            return true;

        }



        return false;

    }



    /**

     * Test domains

     *

     * @param string $error

     * @return boolean

     */

    function _test_domains(&$error) {

        $domains = $this->get_domains();



        if (!count($domains)) {

            $error = 'Empty hostname / CNAME list.';



            return false;



        }



        foreach ($domains as $domain) {

            $_domains = array_map('trim', explode(',', $domain));



            foreach ($_domains as $_domain) {

                $matches = null;



                if (preg_match('~^([a-z0-9\-\.]*)~i', $_domain, $matches)) {

                    $hostname = $matches[1];

                } else {

                    $hostname = $_domain;

                }



                if (!$hostname) {

                    $error = 'Empty hostname';



                    return false;

                }



                if (gethostbyname($hostname) === $hostname) {

                    $error = sprintf('Unable to resolve hostname: %s.', $hostname);



                    return false;

                }

            }

        }



        return true;

    }



    /**

     * Check if css file

     *

     * @param string $path

     * @return boolean

     */

    function _is_css($path) {

        return preg_match('~[a-z0-9\-_]+\.include\.[0-9]+\.css$~', $path);

    }



    /**

     * Check if JS file in heeader

     *

     * @param string $path

     * @return boolean

     */

    function _is_js($path) {

        return preg_match('~[a-z0-9\-_]+\.include\.[a-z0-9]+\.js$~', $path);

    }



    /**

     * Check if JS file after body

     *

     * @param string $path

     * @return boolean

     */

    function _is_js_body($path) {

        return preg_match('~[a-z0-9\-_]+\.include-body\.[a-z0-9]+\.js$~', $path);

    }



    /**

     * Check if JS file before /body

     *

     * @param string $path

     * @return boolean

     */

    function _is_js_footer($path) {

        return preg_match('~[a-z0-9\-_]+\.include-footer\.[a-z0-9]+\.js$~', $path);

    }



    /**

     * Returns domain for path

     *

     * @param array $domains

     * @param string $path

     * @return string

     */

    function _get_domain($domains, $path) {

        $count = count($domains);



        if ($count) {

            /**

             * Use for equal URLs same host to allow caching by browser

             */

            $hash = $this->_get_hash($path);

            $domain = $domains[$hash % $count];



            return $domain;

        }



        return false;

    }



    /**

     * Returns integer hash for key

     *

     * @param string $key

     * @return integer

     */

    function _get_hash($key) {

        $hash = abs(crc32($key));



        return $hash;

    }



    /**

     * Returns scheme

     *

     * @return string

     */

    function _get_scheme() {

        switch ($this->_config['ssl']) {

            default:

            case 'auto':

                $scheme = (w3_is_https() ? 'https' : 'http');

                break;



            case 'enabled':

                $scheme = 'https';

                break;



            case 'disabled':

                $scheme = 'http';

                break;

            case 'rejected':

                $scheme = 'http';

                break;

        }



        return $scheme;

    }



    /**

     * Write log entry

     *

     * @param string $local_path

     * @param string $remote_path

     * @param string $error

     * @return bool|int

     */

    function _log($local_path, $remote_path, $error) {

        $data = sprintf("[%s] [%s => %s] %s\n", date('r'), $local_path, $remote_path, $error);



        $filename = w3_debug_log('cdn');



        return @file_put_contents($filename, $data, FILE_APPEND);

    }



    /**

     * Our error handler

     *

     * @param integer $errno

     * @param string $errstr

     * @return boolean

     */

    function _error_handler($errno, $errstr) {

        $this->_last_error = $errstr;



        return false;

    }



    /**

     * Returns last error

     *

     * @return string

     */

    function _get_last_error() {

        return $this->_last_error;

    }



    /**

     * Set our error handler

     *

     * @return void

     */

    function _set_error_handler() {

        set_error_handler(array(

            &$this,

            '_error_handler'

        ));

    }



    /**

     * Restore prev error handler

     *

     * @return void

     */

    function _restore_error_handler() {

        restore_error_handler();

    }



    /**

     * If CDN suppost path of type folder/*

     * @return bool

     */

    function supports_folder_asterisk() {

        return false;

    }



    /**

     * How and if headers should be set

     * @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING

     */

    function headers_support() {

        return W3TC_CDN_HEADER_NONE;

    }



    /**

     * If the CDN supports fullpage mirroring

     * @return bool

     */

    function supports_full_page_mirroring() {

        return false;

    }

}

