<?php

/*
Plugin Name: 1-Click WP SEO (Share On 20script.iR)
Plugin URI: http://1clickwpseo.com
Description: Does on-page SEO of all your posts and pages with 1 click.
Author: George Katsoudas
Developer: A. Oliinyk (20Script.iR)
Version: 4.0.4
Author URI: http://www.20script.ir
Tested up to: 3.6
*/

/**
* Version history:
* 4.0.4
*   fixed ETH-VM5-XBTM: default optimization disn't work with XML-RPC API calls
* 4.0.3
*   new update logic for less server load
* 4.0.2
*   fixed: default SEO invoked on post delete event
* 4.0.1
*   new update URL
* 4.0
*   added: default optimizaion settings
*   added: home page meta fields
* 3.0.3
*   added: rich message for Bing API not subscribed error
* 3.0.2
*   added: try proxy workaround on 403 errors
* 3.0.1
*   added: try proxy workaround on 404 errors
* 3.0
*   added: Extra content keywords mode selection
* 2.4.14
*   fixed: bug in getting post error handling
* 2.4.13
*   added: embedded auto-update
* 2.4.12
*   fixed: WP 3.5 incompatibility: remove "media" post type
* 2.4.11
*   fixed 4W9-T1S-26WP: use application/json content type for AJAX responses
* 2.4.10
*   fixed T44-QJ5-TR8Y: post filtering broken in v2.4.9
* 2.4.9
*   fixed: PHP 5.4 compatibility issues
* 2.4.8
*   fixed PD2-N46-X89P: bottom set keywords button wasn't working
*   fixed XMN-2D1-XSA5: categories were lost on permalink optimization
* 2.4.7
*   fixed 9P3-W9X-RS2Q: output from other plugins may distort AJAX response
* 2.4.6
*   fixed XMN-2D1-XSA5: <url> malformed error on redirects to relatve URLs
* 2.4.5
*   fixed XMN-2D1-XSA5: workaround for compatibility with "Share Buttons by Lockerz (add-to-any)"
*       plugin
* 2.4.4
*   fixed: workaround for iconv function missing
* 2.4.3
*   fixed: conflict with Cloack.fm plug-in
* 2.4.2
*   added (DW2-N7G-JH1D): explanation and instructions for 403 error on Bing API
* 2.4.1
*   fixed ANR-GRD-T41U: workaround for hosts do not liking custom user agents
* 2.4
*   users now use their own Bing API keys
* 2.3.1
*   workaround for security check on proxy hosting
* 2.3
*   related terms now use new Bing API
* 2.2
*   insert added content elements between paragraphs of post content
* 2.1.1
*   fixed: wp_reset_query called from other plug-in brakes image redirect
* 2.1
*   added: img name and img alt filters now apply to all images in post
* 2.0.10
*   fixed: AJAX error handling
* 2.0.9
*   added: adility to reset posts per page setting from admin page loading
* notice to avoid being stuck with the error.
* 2.0.8:
*   workaround for get_the_ID returning empty result
* 2.0.7:
*   fixed: issues with empty post excerpts
* 2.0.6:
*   fixed: page not found on post editor page in WP admin
* 2.0.5:
*   posts per page limit removed
* 2.0.4:
*   memory usage optimizations
*   extended posts per page limit to 1000
* 2.0.3:
*   workaround for compatibility with Semiologic theme
* 2.0.2:
*   fixed: incompatibility with older WP versions
* 2.0.1:
*   fixed: permalink factor now requires exact match
* 2.0:
*   added: meta title, keywords, description and permalink factors
*   added: buttons to set post title as keyword on mass optimize admin panel
*   added: post optimize status indicators on on mass optimize admin panel
* 1.1.1:
*   added: force related keywords request language based on WP language
* 1.1:
*   added: workaround for blocked loopback connections
* 1.0.2
*   fixed: invalid processing of dynamic image URLs
* 1.0.1
*   removed: session initialiation
*/

require_once dirname(__FILE__) . '/library/compatibility.php';

//Register plug-in:
Ocs_WpPlugin::init();

class Ocs_WpPlugin
{
//------------------------------------------------------------------------------
// Static members
//------------------------------------------------------------------------------
    const PLUGIN_BASE_URL = 'http://1clickwpseo.com/';
    const AUTHOR_BASE_URL = 'http://www.georgekatsoudas.com/';

    /**
    * Version tag. Used for CSS & JS versioning.
    */
    const VERSION = '04.00.04';

    /**
    * Unique slug
    */
    const SLUG = 'OneClickSeo';

    /**
    * Product name for auto-update system
    */
    const PRODUCT_NAME = 'one_click_seo';

    /**
    * Unique prefix
    */
    const PREFIX = 'ocs';

    /**
    * User-friendly title
    */
    const TITLE = '1-Click WP SEO';

    /**
    * Menu item text
    */
    const MENU_LABEL = '1-Click WP SEO';

    /**
    * Required capability to access plug-in pages
    */
    const CAPABILITY = 'publish_posts';

    const CLOACKED_IMAGE_FOLDER = 'ocs-images';

    static protected $_instance;

    static protected $_pluginBaseUrl;
    static protected $_pluginPath;

    static protected $_postTemplateVars;

    static protected $_cloakedImageInfo;

    static protected $_requiredPostMeta = array(
        'filters',
        'keyword'
    );

    /**
    * Temporarily disable the_content filter
    * (used to prevent issues when called from get_the_exceprt)
    *
    * @var bool
    */
    static protected $_disableFilterContent = FALSE;

    static protected $_updater;
    static protected $_updaterCronHook;
    const UPDATE_CHECK_UNTERVAL = 12;

    /**
    * Registers plug-in module
    */
    static public function init()
    {
        // Auto update
        require dirname(__FILE__) . '/plugin-updates/plugin-update-checker.php';
        self::$_updater = new PluginUpdateChecker(
            'http://wppluginupdate.com/check/' . self::PRODUCT_NAME . '.json',
            __FILE__,
            self::PRODUCT_NAME,
            0
        );
        self::$_updaterCronHook = 'auto_check_updates-' . self::PRODUCT_NAME;
        add_action(self::$_updaterCronHook, array(self::$_updater, 'checkForUpdates'));

        self::$_pluginBaseUrl = trailingslashit(get_bloginfo('wpurl')) . PLUGINDIR
            . '/' . dirname(plugin_basename(__FILE__));

        self::$_pluginPath = dirname(__FILE__);

        $class = get_class();

        register_activation_hook(__FILE__, array($class, 'activate'));
        register_deactivation_hook(__FILE__, array($class, 'deactivate'));

        // Admin actions
        if (is_admin()) {
            add_action('admin_init', array($class, 'adminInit'));
            add_action('admin_init', array($class, 'autoCheckUpdates'));
            add_action('admin_menu', array($class, 'adminMenu'));
            add_action('wp_ajax_' . self::PREFIX, array($class, 'adminAjax'));
            add_action('admin_enqueue_scripts', array($class, 'adminEnqueueScripts'));
            add_action('save_post', array($class, 'savePost'), 10, 2);
        } else {
            if (self::$_cloakedImageInfo = self::parseCloakedImageUrl()) {
                add_filter('wp_loaded', array($class, 'cloakedImageRedirect'));
            } else {
                if (!(isset($_GET['ocs_preview']) && $_GET['ocs_preview'])) {
                    add_action('template_redirect', array($class, 'filterHeadStart'), -500);
                }
            }
        }
        add_action('transition_post_status', array($class, 'transitionPostStatus'), 10, 3);

        self::addLibraryIncludePath();

        // Disable admin bar on retrieving post HTML
        if (!is_admin() && isset($_GET['p']) && isset($_GET['preview'])
            && isset($_GET['admin_bar']) && 'true' == $_GET['preview']
            && 'false' == $_GET['admin_bar']
        ) {
            if (function_exists('show_admin_bar')) {
                show_admin_bar(FALSE);
            }
        }
    }

    /**
    * Plug-in activate hook
    */
    static public function activate()
    {
        if (version_compare(PHP_VERSION, '5.1', '<')) {
            // Deactivate Plugin
            if(function_exists('deactivate_plugins')) {
                deactivate_plugins(plugin_basename(__FILE__), true);
            }

            // Display Error Message
            trigger_error(sprintf('Sorry, but Associate Goliath requires PHP 5.0 or newer. Your version is %s. Please, ask your web host to upgrade to PHP 5.0.', phpversion()), E_USER_ERROR);
        }

        // Requires Wordpress 3.0+
        global $wp_version;
        if (version_compare($wp_version, '3.0', '<' )) {
            // Deactivate Plugin
            if(function_exists('deactivate_plugins')) {
                deactivate_plugins(plugin_basename(__FILE__), true);
            }

            // Display Error Message
            trigger_error(sprintf('Sorry, but Associate Goliath requires Wordpress 3.0 or newer. Your version is %s. Please, upgrade to the latest version of Wordpress.', $wp_version), E_USER_ERROR);
        }
    }

    /**
    * Plug-in deactivation hook
    */
    static public function deactivate()
    {

    }

    static public function loadApplication()
    {
        self::addLibraryIncludePath();
        require_once(self::$_pluginPath . '/application/Application.php');
    }

    static public function addLibraryIncludePath()
    {
        $includePaths = explode(PATH_SEPARATOR, get_include_path());
        $path = realpath(dirname(__FILE__) . '/library');
        if (!in_array($path, $includePaths)) {
            foreach ($includePaths as $key=>$item) {
                if ('.' == $item) {
                    unset($includePaths[$key]);
                    break;
                }
            }
            array_unshift($includePaths, $path);
            array_unshift($includePaths, '.');
            set_include_path(implode(PATH_SEPARATOR, $includePaths));
        }
    }

    static public function adminInit()
    {
    }

    static public function autoCheckUpdates()
    {
        $updater = self::$_updater;
        $state = $updater->getUpdateState();

        $shouldCheck = (empty($state)
            || !isset($state->lastCheck)
            || ((time() - $state->lastCheck) >= (self::UPDATE_CHECK_UNTERVAL * 3600)))
            && !wp_next_scheduled(self::$_updaterCronHook);

        if ($shouldCheck) {
            wp_schedule_single_event(time(), self::$_updaterCronHook);
            spawn_cron();
        }
    }

    /**
    * Hooks
    */
    static public function adminMenu()
    {
        self::getInstance()
            ->_registerAdminPages()
            ->_registerMetaBoxes();
    }

    static public function adminAjax()
    {
        self::getInstance()->dispatchAjax();
    }

    static public function savePost($postId, $post)
    {
        // Ignore new posts, autosave and revisions:
        if (wp_is_post_autosave($postId)
            || wp_is_post_revision($postId)
            || 'auto-draft' == $post->post_status
            || 'trash' == $post->post_status
            || (isset($_POST['action']) && 'autosave' == $_POST['action'])
            || 'nav_menu_item' == $post->post_type
        ) {
            return;
        }
        if (isset($_POST['ocs_metabox_save']) && $_POST['ocs_metabox_save'] == 1){
            unset($_POST['ocs_metabox_save']);
            self::getInstance()->dispatch__metabox__handle_save_post($postId, $post);
        }
    }

    static public function transitionPostStatus($new, $old, $post)
    {
        if ('new' != $old || 'inherit' == $new) {
            return;
        }

        $mode = get_option(self::PREFIX . '_auto_optimization');
        if ('' == $mode || 'disabled' == $mode) {
            return;
        }

        if ('auto-draft' != $new && isset($_POST['ocs_metabox_save'])) {
            return;
        }

        self::getInstance()->dispatch__metabox__handle_new_post($post->ID, $post, $new);
    }

    static public function checkPost()
    {
        if (!is_singular()) {
            return FALSE;
        }

        $postId = Ocs_WpPlugin::getCurrentPostId();
        foreach (self::$_requiredPostMeta as $metaName) {
            if ('' == get_post_meta($postId, self::PREFIX . '_' . $metaName, TRUE)) {
                return FALSE;
            }
        }

        return TRUE;
    }

    static public function filterHeadStart()
    {
        if (self::checkPost()) {
            $class = get_class();
            add_action('wp_head', array($class, 'filterHeadStop'), 1000);
            add_filter('the_content', array($class, 'filterContent'), 99);
        } else if (is_home() && get_option(self::PREFIX . '_filter_home_head')) {
            $class = get_class();
            add_action('wp_head', array($class, 'filterHeadStopHome'), 1000);
        } else {
            return;
        }

        self::getInstance()->dispatch('filter', 'head-start');
    }

    static public function filterHeadStop()
    {
         self::getInstance()->dispatch('filter', 'head-stop');
    }

    static public function filterHeadStopHome()
    {
         self::getInstance()->dispatch('filter', 'head-stop-home');
    }

    static public function filterContent($content)
    {
        if (self::$_disableFilterContent) {
            return $content;
        }

        return self::getInstance()
            ->dispatch('filter', 'content', array('content' => $content));
    }

    static public function setDisableFilterContent($value)
    {
        self::$_disableFilterContent = $value;
    }

    static public function cloakedImageRedirect()
    {
        $postId = self::$_cloakedImageInfo['post_id'];
        $imageIndex = self::$_cloakedImageInfo['image_index'];
        if (!get_post($postId)) {
            header("HTTP/1.0 404 Not found");
            exit ("Post with ID {$postId} not found");
        }

        self::loadApplication();

        $postContent = Ocs_Application::getModel('Post_Content')
            ->get(get_post($postId));
        $realUrl = Ocs_Application::getService('ImageCloak')
            ->getRealImageUrl($postContent, $imageIndex);

        if (!$realUrl) {
            header("HTTP/1.0 404 Not found");
            exit ("Image redirect URL not found");
        }

        if (!parse_url($realUrl, PHP_URL_SCHEME)) {
           $realUrl = Ocs_Application::getService('AbsoluteUrl')
               ->makeAbsolute(get_permalink(), $realUrl);
        }

        header('Location: ' . $realUrl, TRUE, 302);
        exit();
    }

    static public function parseCloakedImageUrl()
    {
         $uri = substr($_SERVER['REQUEST_URI'], strlen(self::getHomePath()));
         $parts = explode('/', $uri);

         if (!$parts) {
             return NULL;
         }

         if ('index.php' == $parts[0]) {
            array_shift($parts);
         }

         if (self::CLOACKED_IMAGE_FOLDER != $parts[0] || !in_array(count($parts), array(3, 4))) {
            return NULL;
         }

         $result = array(
            'post_id' => (int)$parts[1],
         );

         // Backwords compatibility to keep old links working
         if (4 == count($parts)) {
            $result['image_index'] = $parts[2];
            return $result;
         }

         $imageName = pathinfo($parts[2], PATHINFO_FILENAME);
         $nameParts = explode('--', $imageName, 2);

         $result['image_index'] = isset($nameParts[1]) && $nameParts[1] > 0
            ? (int)$nameParts[1] : 1;

         return $result;
    }

    static public function getHomePath()
    {
         return trailingslashit((string) parse_url(site_url(), PHP_URL_PATH));
    }

    static public function adminEnqueueScripts($hook)
    {
        $pageSlug = $pageSlug = str_replace(' ', '-', strtolower(self::MENU_LABEL));

        switch ($hook) {
            case 'toplevel_page_' . self::SLUG:
                // Admin page
                wp_enqueue_style(self::PREFIX . '_admin',
                    self::$_pluginBaseUrl . '/css/admin.css', array(),
                    self::VERSION);
                wp_enqueue_script(self::PREFIX . '_admin',
                    self::$_pluginBaseUrl . '/js/admin.js', array(),
                    self::VERSION);

                wp_enqueue_style(self::PREFIX . '_ui',
                    self::$_pluginBaseUrl . '/css/ui.css', array(),
                    self::VERSION);
                wp_enqueue_script('jquery');
                wp_enqueue_script('jquery-ui-core');
                wp_enqueue_script('jquery-ui-widget',
                    self::$_pluginBaseUrl . '/js/jquery/ui/jquery.ui.widget.min.js',
                    array('jquery-ui-core'), '1.8.16');
                wp_enqueue_script('jquery-ui-progressbar',
                    self::$_pluginBaseUrl . '/js/jquery/ui/jquery.ui.progressbar.min.js',
                    array('jquery-ui-core', 'jquery-ui-widget'), '1.8.16');

            case $pageSlug . '_page_' . self::SLUG . '-options':
                // Options page
                wp_enqueue_style(self::PREFIX . '_admin',
                    self::$_pluginBaseUrl . '/css/admin.css', array(),
                    self::VERSION);
                wp_enqueue_script(self::PREFIX . '_admin',
                    self::$_pluginBaseUrl . '/js/admin.js', array(),
                    self::VERSION);
                break;

            case 'post.php':
            case 'post-new.php':
                // Post editor
                wp_enqueue_style(self::PREFIX . '_metabox',
                    self::$_pluginBaseUrl . '/css/metabox.css', array(),
                    self::VERSION);
                wp_enqueue_script(self::PREFIX . '_metabox',
                    self::$_pluginBaseUrl . '/js/metabox.js', array(),
                    self::VERSION);
                break;

            default:
                // Do not load common stuff for other pages
                return;
        }

        // Common stuff
        wp_enqueue_style(self::PREFIX . '_help',
            self::$_pluginBaseUrl . '/css/help.css', array(),
            self::VERSION);
        wp_enqueue_script(self::PREFIX . '_help',
            self::$_pluginBaseUrl . '/js/help.js', array(),
            self::VERSION);
    }

    /**
    * Misc Helpers
    */

    /**
    * Returns array of available post types
    */
    static protected function getPostTypes()
    {
        return function_exists('get_post_types')
            ? get_post_types('','names')
            : array('post', 'page');
    }

    /**
    * Singleton pattern
    *
    * @return Ocs_WpPlugin Singleton instance
    */
    static public function getInstance()
    {
        if (!self::$_instance) {
            self::$_instance = new self;
        }

        return self::$_instance;
    }

    /**
    * Static accessors
    */

    static public function getPluginBaseUrl()
    {
        return self::$_pluginBaseUrl;
    }

    static public function getPluginPath()
    {
        return self::$_pluginPath;
    }

    static public function getCurrentPostId()
    {
        $result = get_the_ID();
        if (!$result) {
            global $wp_query;
            if (isset($wp_query->post->ID)) {
                $result = $wp_query->post->ID;
            }
        }

        return $result? $result : NULL;
    }

//------------------------------------------------------------------------------
// Object members
//------------------------------------------------------------------------------
    /**
    * Register plug-in admin pages and menus
    */
    /**
    * Register plug-in admin pages and menus
    */
    protected function _registerAdminPages()
    {
        add_menu_page(self::TITLE, self::MENU_LABEL,
            self::CAPABILITY, self::SLUG,
            array($this, 'dispatchAdmin'),
            self::getPluginBaseUrl() . '/images/icon.png'
        );

        add_submenu_page(self::SLUG, self::TITLE,
            'Admin', self::CAPABILITY,
            self::SLUG,
            array($this, 'dispatchAdmin')
        );

        add_submenu_page(self::SLUG, self::TITLE,
            'Options', self::CAPABILITY,
            self::SLUG . '-options',
            array($this, 'dispatch__admin__options')
        );

        return $this;
    }

    /**
    * Register plug-in admin metaboxes
    */
    protected function _registerMetaBoxes()
    {
        foreach(self::getPostTypes() as $type) {
            add_meta_box(self::PREFIX . '_main', self::TITLE, array($this, 'dispatch__metabox__index'), $type, 'side', 'high' );
        }

        return $this;
    }

    /**
    * Intercepts WP requests
    */
    public function __call($method, $arguments)
    {
        if ('dispatch__' == substr($method, 0, 10)) {
            $parts = explode('__', $method);
            $controller = @$parts[1];
            $action = @$parts[2];

            $controller = $this->_prepareMvcName($controller);
            $action = $this->_prepareMvcName($action);

            return $this->dispatch($controller, $action, $arguments);
        }
        else {
            throw new ErrorException("Method '{$method}' not found.");
        }
    }

    /**
    * Dispatches user request to WP Module
    *
    * @param string $controller Controller name
    * @param string $action Action name
    */
    public function dispatch($controller='index', $action='index',
        $arguments = NULL)
    {
        self::loadApplication();

        return Ocs_Application::dispatch($controller, $action, $arguments);
    }

    public function dispatchAdmin()
    {
        $controller = @$_GET['controller'];
        if (empty($controller)) $controller = 'admin';
        $action = @$_GET['action'];
        if (empty($action)) $action = 'index';

        $this->dispatch($controller, $action);
    }

    public function dispatchAjax()
    {
       /* $controller = @$_POST['controller'];
        if (empty($controller)) $controller = 'index';
        $action = @$_POST['ajaxAction'];
        if (empty($action)) $action = 'index';

        $this->dispatch($controller, $action);
        exit;*/

        $controller = @$_POST['controller'];
        if (empty($controller)) {
            $controller = 'index';
        }
        $action = @$_POST['controller_action'];
        if (empty($action)) {
            $action = 'index';
        }

        $this->dispatch($controller, $action);

        exit();
    }

    protected function _prepareMvcName($name)
    {
        return str_replace('_', '-', $name);
    }
}