<?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 object
     */
    function api_request($action, $value = null) {
        w3_require_once(W3TC_INC_DIR . '/functions/http.php');
        if (empty($this->_cf_config['email']) || !filter_var($this->_cf_config['email'], FILTER_VALIDATE_EMAIL))
            return false;

        if (empty($this->_cf_config['key']) || !is_string($this->_cf_config['key']))
            return false;

        if (empty($this->_cf_config['zone']) || !is_string($this->_cf_config['zone']) || strpos($this->_cf_config['zone'], '.') === false)
            return false;

        $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');
        if (empty($this->_cf_config['email']) || !filter_var($this->_cf_config['email'], FILTER_VALIDATE_EMAIL))
            return false;

        if (empty($this->_cf_config['key']) || !is_string($this->_cf_config['key']))
            return false;

        if ($this->get_last_error())
            return false;

        $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 (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && !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
     * @throws FilesystemOperationException
     * @throws FileOperationException
     */
    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');
    }
}
