<?php
require_once dirname(__FILE__) . '/MuCashSDK.inc';

define("MUCASH_WP_ITEMCODE_ID_BITS", 24);
define("MUCASH_WP_ITEMCODE_TYPE_BITS", 31 - MUCASH_WP_ITEMCODE_ID_BITS);

define("MUCASH_COOKIE_ADSFREE", "mucash_adsfree");

class MuCashWP
{
    const MUCASH_SID_COOKIE = "mucash_id";
    const COOKIE_EXP = 31536000; // One year
    const EXCERPT_REGEX = '/<!--more(.*?)?-->/'; // From wp-includes/post-template.php
    
    const IT_ARTICLE = 0;
    const IT_ADSFREE = 1;
    const IT_DONATE_COMMENT = 2;
    const IT_DONATE_BUTTON = 3;
    
    const S_SECTION_MAIN = "mucash_options";
    const S_MERCHANT_ID = "mucash_merchant_id";
    const S_API_KEY = "mucash_api_key";
    const S_DONATION_TYPE = "mucash_donation_type";
    
    const DT_COMMENT = "comment";
    const DT_BUTTON = "button";
    const DT_DISABLED = "disable";
    
    public $sdk;
    
    private $ok = true;
    private $page_handled = false;
    private $sid;
    private $donate_type;
    private $exception;
    
    public function __construct()
    {
        $this->setSid();
        
        try {
            $this->sdk = new MuCashSDK(
                get_option(self::S_MERCHANT_ID), 
                get_option(self::S_API_KEY),
                array("MuCashWP", "kvStore"),
                array("MuCashWP", "kvGet")
            );
        } catch(Exception $e) {
            $this->ok = false;
            $this->exception = $e;
            add_action("admin_notices", array($this, "noticeInitFailed"));
        }
        
        // Deal with MuCash specific form stuff    
        if (isset($_GET["mucash_callback"])) {
            $this->handleCallback();
    	}

    	if (is_admin()) {
            add_action("admin_menu", array($this, "addOptionsMenu"));
            add_action("admin_init", array($this, "adminInit"));
    	}
    	
        if ($this->ok) {
            wp_enqueue_script("mucash", MUCASH_URL . "/media/js/webint.js");
            wp_enqueue_style("mucash", plugins_url('mucash.css', __FILE__));

            add_action("save_post", array($this, "saveMeta"));
            add_action("the_posts", array($this, "thePosts"), 1);
            
            $this->donate_type = get_option(self::S_DONATION_TYPE, self::DT_COMMENT);
            if ($this->donate_type == self::DT_COMMENT) {
                add_action("comment_form_after_fields", array($this, "addDonate"));
                add_action("comment_form_logged_in_after", array($this, "addDonate"));
                add_action("comment_post", array($this, "saveComment"));
                add_filter("comment_text", array($this, "commentText"));   
            } 
        }
    }
    
    public function thePosts($posts)
    {
        if(is_admin()) {
            return $posts;
        }
        
        foreach($posts as $post) {
            $price = get_post_meta($post->ID, "mucash_price", true);
            
            if($price) {
                $this->addBuyArticleButton($post, $price);
            } else if ($this->donate_type == self::DT_BUTTON && !is_feed()) {
                $this->addDonateButton($post);
            }
        }
        
        return $posts;
    }

    private function addBuyArticleButton(&$post, $price)
    {
        $itemcode = MuCashWP::packItemcode(self::IT_ARTICLE, $post->ID);
        $cert = $this->getCert($itemcode);
        if (!$cert) {
            $price = new MuCashCurrency($price);
            $error = "";
            
            try {
                $quote = $this->sdk->generateArticleQuote(
                    $itemcode, $price, $post->post_title, get_permalink($post));
                $cburl = self::getCbUrl();

                if (preg_match(self::EXCERPT_REGEX, $post->post_content, $matches)) {
                    $parts = explode($matches[0], $post->post_content, 2);
                    $post->post_content = force_balance_tags($parts[0]);

                    if (is_feed()) {
                        $post->post_content .= makeDiv("This article costs $price via <a href=\"http://mucash.com\">MuCash</a>.  <a href=\"$url\">Click here</a> to purchase the full article.");
                    } else {
                        $post->post_content .= self::makeBuyButton($quote);
                    }
                } else {
                    $error = "WARNING: This article has a MuCash price but no more tag."; 
                }
            } catch (MuCashErrInvalidTitle $e) {
                $error .= "WARNING: You must set a title when locking an article.";
            } catch (Exception $e) {
                $error .= "Internal error.  Please contact support@mucash.com.";
            }
            
            if (current_user_can('edit_post', $post->ID) && !empty($error)) {
                $post->post_content .= self::makeDiv($error, "mucash_warning");
            }
        }              
    }
    
    private function addDonateButton(&$post)
    {
        $itemcode = MuCashWP::packItemcode(self::IT_DONATE_BUTTON, $post->ID);
        if ($cert = $this->getCert($itemcode)) {
            $post->post_content .= self::makeDiv(
            	"Thank you for your donation.", "mucash_thankyou"
            );
        } else {
            $min = MuCashCurrency::fromCents(1);
            $max = MuCashCurrency::fromCents(99);
            $quote = $this->sdk->generateDonateQuote($itemcode, $min, $max);
            $cburl = self::getCbUrl();
            $post->post_content .= self::makeBuyButton($quote);
        } 
    }
    
    public function addDonate()
    {
        $pid = get_the_ID();
        $itemcode = MuCashWP::packItemcode(self::IT_DONATE_COMMENT, $pid);
        $min = MuCashCurrency::fromCents(1);
        $max = MuCashCurrency::fromCents(99);
        $quote = $this->sdk->generateDonateQuote($itemcode, $min, $max);
        require dirname(__FILE__) . '/html/comment_donate_form.php';
    }
    
    public function adminInit()
    {
        register_setting(self::S_SECTION_MAIN, self::S_MERCHANT_ID);
        register_setting(self::S_SECTION_MAIN, self::S_API_KEY);
        register_setting(self::S_SECTION_MAIN, self::S_DONATION_TYPE);
        
        if ($this->isOk()) {
            add_meta_box("mucash_post_options", "MuCash Options", 
                    array($this, "addMetaBox"), "post", "normal", "high");
        }
    }
    
    public function handleCallback()
    {
        if (isset($_REQUEST["cert"])) {
            try {
                $cert = $this->sdk->checkCertificate($_REQUEST["cert"]);
                $this->addCert($cert);
            } catch (MuCashErrBadCert $e) {
                $error = "BAD_CERT";
            }
        } else {
            $error = "UNKNOWN_FUNCTION";
        }
        require dirname(__FILE__) . '/html/callback.php';
        $this->page_handled = true;
    }
    
    public function noticeInitFailed()
    {
        switch(get_class($this->exception)) {
            case "MuCashErrInvalidMerchantId":
            case "MuCashErrInvalidKey":
                $msg = "Please visit the MuCash settings page and set your " .
                	"Site ID and API Key to complete the plugin installation.";
                break;
            default:
                $msg = "We're sorry, the MuCash plugin has suffered an internal " .
                    "error.  Please contact us at support@mucash.com and we will " .
                    "help you resolve this as quickly as possible.";
                break;
        }
        
        echo self::makeDiv($msg, "updated fade");
    }
    
    public function addOptionsMenu()
    {
        add_options_page('MuCash Options', 'MuCash', 'manage_options', 
        	self::S_SECTION_MAIN, array($this, 'showAdminOptions'));
    }

    public function showAdminOptions()
    {
        include dirname(__FILE__) . '/html/options.php';
    }
    
    public function addMetaBox($data)
    {
        include dirname(__FILE__) . '/html/meta_box.php';
    }
    
    public function saveMeta($postid)
    {
        if (defined("DOING_AUTOSAVE") && DOING_AUTOSAVE) {
            return;
        }
        if (!wp_verify_nonce($_POST["mucash_meta_nonce"], "mucash_meta")) {
            return;
        }
        if(!current_user_can("edit_post", $postid)) {
            return;
        }
        
        $mucash_price = (int)$_POST["mucash_price"];
        if($mucash_price) {
            update_post_meta($postid, "mucash_price", $mucash_price);
        } else {
            delete_post_meta($postid, "mucash_price");
        }
    }
    
    public function saveComment($commentid)
    {
        $amt = $_POST["mucash_donate_amount"];
        if(empty($amt)) {
            return;
        }
        
        $comment = get_comment($commentid);
        $pid = $comment->comment_post_ID;
        $itemcode = MuCashWP::packItemcode(self::IT_DONATE_COMMENT, $pid);
        $cert = $this->getCert($itemcode);
        if(!$cert) {
            return;
        }
        update_comment_meta($commentid, "mucash_donation", $cert);
        $this->delCert($itemcode);
    }
    
    public function commentText($comment_text)
    {
        global $comment;
        if(!isset($comment)) {
            return $comment_text;
        }
        $cert = get_comment_meta($comment->comment_ID, "mucash_donation", true);
        if($cert) {
            $amt = new MuCashCurrency($cert->price);
            $comment_text .= '<p class="mucash_donation_notice">' . (string)$amt . ' donated via <a href="https://mucash.com">MuCash</a></p>';
        }
        return $comment_text;
    }
    
    static public function packItemcode($type, $id)
    {
        $type = (int)$type;
        $id = (int)$id;
        
        if ($id >= (1 << MUCASH_WP_ITEMCODE_ID_BITS)) {
            throw new MuCashWPErrItemIDTooLarge();
        }
        return ($type << MUCASH_WP_ITEMCODE_ID_BITS) | $id;
    }

    static public function getItemcodeId($itemcode)
    {
        $mask = (1 << MUCASH_WP_ITEMCODE_ID_BITS) - 1;
        return $itemcode & $mask;
    }
    
    static public function getItemcodeType($itemcode)
    {
        $mask = (1 << MUCASH_WP_ITEMCODE_TYPE_BITS) - 1; 
        return ($itemcode >> MUCASH_WP_ITEMCODE_ID_BITS) & $mask;
    }

    private function getItemKey($itemcode)
    {
        return implode('_', array("mucash", $this->sid, $itemcode));
    }
    
    public function addCert(MuCashPaymentCertificate $cert)
    {
        $old_cert = $this->getCert($cert->itemcode);
        if ($old_cert && $old_cert->timestamp >= $cert->timestamp) {
            return;
        }
        set_transient($this->getItemKey($cert->itemcode), $cert, 30 * 86400);
    }

    public function getCert($itemcode)
    {
        return get_transient($this->getItemKey($itemcode));
    }
    
    public function delCert($itemcode)
    {
        delete_transient($this->getItemKey($itemcode));
    }
    
    public function pageHandled()
    {
        return $this->page_handled;
    }
    
    public function isOk()
    {
        return $this->ok;
    }
    
    static public function kvStore($key, $value)
    {
        set_transient($key, $value, 30 * 24 * 3600);
    }
    
    static public function kvGet($key)
    {
        return get_transient($key);
    }
    
    static public function getCbUrl()
    {
        return site_url() . '?mucash_callback=1';
    }

    private function setSid()
    {
        if(isset($_COOKIE[self::MUCASH_SID_COOKIE])) {
            $this->sid = $_COOKIE[self::MUCASH_SID_COOKIE];
        } else {
            global $wp_hasher;

            if (empty($wp_hasher)) {
                require_once( ABSPATH . 'wp-includes/class-phpass.php');
                $ph = new PasswordHash(8, true);
            } else {
                $ph = $wp_hasher;
            }

            $this->sid = bin2hex($ph->get_random_bytes(8));
        }
        setcookie(self::MUCASH_SID_COOKIE, $this->sid, time() + self::COOKIE_EXP, 
            SITECOOKIEPATH, COOKIE_DOMAIN);
    }
    
    static protected function makeDiv($content, $class = "", $id = "")
    {
        if ($class != "") {
            $class = "class=\"$class\"";
        }
        if ($id != "") {
            $id = "id=\"$id\"";
        }
        return "<div $class $id>$content</div>";
    }
    
    static protected function makeBuyButton($quote)
    {
		return "<script type=\"text/javascript\">MUCASH.showBuyBtn(\"$quote\"" .
		    ", \"" . self::getCbUrl() . "\");</script>"; 
    }
}

class MuCashWPErrItemIDTooLarge extends Exception {}

?>