<?php



/**

 * W3 CloudFlare Class

 */

define('W3TC_CLOUDFLARE_API_URL', 'https://www.cloudflare.com/api_json.html');

define('W3TC_CLOUDFLARE_EXTERNAL_EVENT_URL', 'https://www.cloudflare.com/ajax/external-event.html');

define('W3TC_CLOUDFLARE_IP4_URL', 'https://www.cloudflare.com/ips-v4');

define('W3TC_CLOUDFLARE_IP6_URL', "https://www.cloudflare.com/ips-v6");



/**

 * Class W3_CloudFlare

 */

class W3_CloudFlare {

    /**

     * Config array

     *

     * @var array

     */

    var $_cf_config = array();



    /**

     * @var W3_Config

     */

    var $_config;



    /**

     * Checks if cloudflare error pushed to output

     *

     * @var boolean

     */

    var $_fault_signaled = false;



    /**

     * PHP5-style constructor

     *

     * @param array $config

     */

    function __construct($config = array()) {

        $this->_config = w3_instance('W3_Config');

        if (empty($config)) {

            $this->_cf_config = array(

                        'email' => $this->_config->get_string('cloudflare.email'),

                        'key' => $this->_config->get_string('cloudflare.key'),

                        'zone' => $this->_config->get_string('cloudflare.zone'));

        } else {

            $this->_cf_config = array_merge(array(

                'email' => '',

                'key' => '',

                'zone' => ''

            ), $config);

        }



    }



    /**

     * Makes API request

     *

     * @param string $action

     * @param string $value

     * @return array

     */

    function api_request($action, $value = null) {

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



        $url = sprintf('%s?email=%s&tkn=%s&z=%s&a=%s', W3TC_CLOUDFLARE_API_URL, urlencode($this->_cf_config['email']), urlencode($this->_cf_config['key']), urlencode($this->_cf_config['zone']), urlencode($action));



        if ($value !== null) {

            $url .= sprintf('&v=%s', urlencode($value));

        }



        $response = w3_http_get($url);



        if (!is_wp_error($response)) {

            $response = json_decode($response['body']);

            if (isset($response->result) && $response->result == 'error') {

                $this->_set_last_error('Setting: ' . $action . ' ' . (isset($response->msg) ? $response->msg : 'Unknown error'));

            } else {

                $this->clear_last_error();

            }



            return $response;

        } else {

        	$this->_set_last_error('Setting: ' . $action. ' ' . $response->get_error_message());

    	}



        return false;

    }



    /**

     * Makes external event request

     *

     * @param string $type

     * @param string $value

     * @return array

     */

    function external_event($type, $value) {

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



        $url = sprintf('%s?u=%s&tkn=%s&evnt_t=%s&evnt_v=%s', W3TC_CLOUDFLARE_EXTERNAL_EVENT_URL, urlencode($this->_cf_config['email']), urlencode($this->_cf_config['key']), urlencode($type), urlencode($value));

        $response = w3_http_get($url);



        if (!is_wp_error($response)) {

            return json_decode($response['body']);

        }



        return false;

    }



    /**

     * Fix client's IP-address

     *

     * @return void

     */

    function fix_remote_addr() {

        if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {

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

            if (strpos($_SERVER["REMOTE_ADDR"], ":") === FALSE) {

                $ip4_ranges = $this->_config->get_array('cloudflare.ips.ip4');

                foreach ($ip4_ranges as $range) {

                    if (w3_ipv4_in_range($_SERVER['REMOTE_ADDR'], $range)) {

                        $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];

                        break;

                    }

                }

            } else {

                $ip6_ranges = $this->_config->get_array('cloudflare.ips.ip6');

                $ip6 = w3_get_ipv6_full($_SERVER["REMOTE_ADDR"]);

                foreach ($ip6_ranges as $range) {

                    if (w3_ipv6_in_range($ip6, $range)) {

                        $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];

                        break;

                    }

                }

            }

        }

    }



    static function get_last_error() {

        return get_option('w3tc_clourflare_last_error');

    }



    static function clear_last_error() {

        update_option('w3tc_clourflare_last_error', false);

    }



    private function _set_last_error($message) {

        update_option('w3tc_clourflare_last_error', $message);

    }



    /**

     * Check

     */

    public function update_ip_ranges() {

        $ip4_diff = $ip6_diff = false;

        $config_master = new W3_Config(true);

        $response =w3_http_get(W3TC_CLOUDFLARE_IP4_URL);

        if (!is_wp_error($response)) {

            $ip4_data = $response['body'];

            $ip4_data = explode("\n", $ip4_data);

            $ip4_data_old = $this->_config->get_array('cloudflare.ips.ip4');

            if ($ip4_diff = array_diff($ip4_data, $ip4_data_old))

                $config_master->set('cloudflare.ips.ip4', $ip4_data);

        }

        $response =w3_http_get(W3TC_CLOUDFLARE_IP6_URL);

        if (!is_wp_error($response)) {

            $ip6_data = $response['body'];

            $ip6_data = explode("\n", $ip6_data);

            $ip6_data_old = $this->_config->get_array('cloudflare.ips.ip6');

            if ($ip6_diff = array_diff($ip6_data, $ip6_data_old))

                $config_master->set('cloudflare.ips.ip6', $ip6_data);

        }

        if ($ip4_diff || $ip6_diff)

            $config_master->save();

    }



    // Report spam.

    function report_if_spam($id, $status) {

        // If spam, send this info over to CloudFlare.

        if ($status == "spam") {

            $comment = get_comment($id);

            $value = array("a" => $comment->comment_author,

                "am" => $comment->comment_author_email,

                "ip" => $comment->comment_author_IP,

                "con" => substr($comment->comment_content, 0, 100));

            $this->external_event('WP_SPAM', json_encode($value));

        }

    }



    /**

     * @return array

     */

    function get_settings() {

        $settings = get_transient('w3tc_cloudflare_settings');



        if (false === $settings) {

            $settings = array('sec_lvl' => 'eoff', 'devmode' => 0, 'async' => 0, 'minify' => 0);

            $response = $this->api_request('zone_settings');



            if ($response && $response->result == 'success' && isset($response->response->result->objs[0])) {

                switch ($response->response->result->objs[0]->sec_lvl) {

                    case 'I\'m under attack!':

                        $seclvl = 'help';

                        break;



                    case 'Essentially Off':

                        $seclvl = 'eoff';

                        break;



                    case 'High';

                        $seclvl = 'high';

                        break;



                    case 'Medium';

                        $seclvl = 'med';

                        break;



                    case 'Low';

                        $seclvl = 'low';

                        break;

                    default:

                        $seclvl = $response->response->result->objs[0]->sec_lvl;

                }

                $settings['sec_lvl'] = $seclvl;

                $devmode = ($response->response->result->objs[0]->dev_mode >= time() ? $response->response->result->objs[0]->dev_mode : 0);

                $settings['devmode'] = $devmode;

                $settings['async'] = $response->response->result->objs[0]->async;

                $settings['minify'] = $response->response->result->objs[0]->minify;

                set_transient('w3tc_cloudflare_settings', $settings, 3600);

            }

        }

        return $settings;

    }





    function check_lasterror() {

        if ($this->_config->get_boolean('cloudflare.enabled') && self::get_last_error()) {

            if (!$this->_fault_signaled) {

                $this->_fault_signaled = true;

                return sprintf('Unable to communicate with CloudFlare API: %s.', self::get_last_error());

            }

        }

        return false;

    }



    /**

     * If error has been reported

     * @return bool

     */

    function get_fault_signaled() {

        return $this->_fault_signaled;

    }



    public function get_options() {

        return array(

            'sec_lvl' => array(

                'help' => 'I\'m under attack!',

                'high' => 'High',

                'med' => 'Medium',

                'low' => 'Low',

                'eoff' => 'Essentially Off'

            ),

            'devmode' => array(

                1 => 'On',

                0 => 'Off'

            ),

            'dev_mode' => array(

                1 => 'On',

                0 => 'Off'

            ),

            'async'=> array(

                0 => 'Off',

                'a' => 'Automatic',

                'm' => 'Manual'

            ),

            'minify' => array(

                0 => 'Off',

                1 => 'JavaScript only',

                2 => 'CSS only',

                3 => 'JavaScript and CSS',

                4 => 'HTML only',

                5 => 'JavaScript and HTML',

                6 => 'CSS and HTML',

                7 => 'CSS, JavaScript, and HTML'

            )

        );

    }



    /**

     * @return mixed

     */

    public function purge() {

        @set_time_limit($this->_config->get_integer('timelimit.cloudflare_api_request'));

        delete_transient('w3tc_cloudflare_settings');

        return $this->api_request('fpurge_ts', 1);

    }



    /**

     * If minify is enabled at all

     * @return bool

     */

    public function minify_enabled() {

        $settings  = $this->get_settings();

        return $settings['minify'] > 0;

    }



    /**

     * @param $cf_values

     * @return bool

     */

    public function save_settings($cf_values) {

        @set_time_limit($this->_config->get_integer('timelimit.cloudflare_api_request'));

        ksort($cf_values);

        $cf_values = $this->_cleanup_settings($cf_values);

        foreach ($cf_values as $key => $settings) {

            if ($settings['old'] != $settings['new']) {

                $response = $this->api_request($key, $settings['new']);

                if (!$response || ($response && $response->result != 'success')) {

                    return false;

                }

            }

        }

        delete_transient('w3tc_cloudflare_settings');

        return true;

    }



    /**

     * Takes an array with posted CF settings and reformat it.

     * @param $settings

     * @return array

     */

    private function _cleanup_settings($settings) {

        $keys = array_keys($this->get_options());

        $clean_settings = array();

        foreach ($settings as $key => $value) {

            $setting = substr($key, 0, strlen($key)-4);

            if (in_array($setting, $keys)) {

                if (strpos($key, '_old') !== false) {

                    $clean_settings[$setting]['old'] = $value;

                } elseif (strpos($key, '_new') !== false) {

                    $clean_settings[$setting]['new'] = $value;

                }

            }

        }

        return $clean_settings;

    }



    /*

     * Resets settings cache

     */

    public function reset_settings_cache() {

        return delete_transient('w3tc_cloudflare_settings');

    }

}

