<?PHP

class Captcha extends Plugin
{
    function Register()
    {
        $this->Name = "CAPTCHA Anti-Spam Support";
        $this->Version = "1.0.6";
        $this->Description = "Adds <a href=\"http://captcha.net\" "
            ."target=\"_blank\">CAPTCHA</a> "
            ."support to protect against attacks by spammers using bots. ";
        $this->Author = "Internet Scout";
        $this->Url = "http://scout.wisc.edu/cwis/";
        $this->Email = "scout@scout.wisc.edu";
        $this->Requires = array("CWISCore" => "2.0.3");
        $this->CfgSetup["Method"] =
          array(
                "Type" => "Option",
                "Label" => "CAPTCHA Method",
                "Help" =>
                  "Select what manner of CAPTCHA you wish to display.",
                "Options" =>
                array("Securimage" => "SecurImage CAPTCHA")
                );
        $this->CfgSetup["Width"] =
          array( "Type" => "Number",
                 "MaxVal" => 1024,
                 "Label" => "Width",
                 "Help" => "Width of the captcha image");
        $this->CfgSetup["Height"] =
          array( "Type" => "Number",
                 "MaxVal" => 1024,
                 "Label" => "Height",
                 "Help" => "Height of the captcha image");
        $this->CfgSetup["DisplayIfLoggedIn"] =
            array(
                "Type" => "Flag",
                "Label" => "Display CAPTCHA for logged-in users",
                "Help" => "",
                "OnLabel" => "Yes",
                "OffLabel" => "No",
                );
        $this->CfgSetup["CaptchaCommentPost"] =
            array(
                "Type" => "Flag",
                "Label" => "Display CAPTCHA for comment posting",
                "Help" => "",
                "OnLabel" => "Yes",
                "OffLabel" => "No",
                );
        $this->CfgSetup["CaptchaMessagePost"] =
            array(
                "Type" => "Flag",
                "Label" => "Display CAPTCHA for message posting",
                "Help" => "",
                "OnLabel" => "Yes",
                "OffLabel" => "No",
                );
        $this->CfgSetup["CaptchaFeedback"] =
            array(
                "Type" => "Flag",
                "Label" => "Display CAPTCHA for sending feedback",
                "Help" => "",
                "OnLabel" => "Yes",
                "OffLabel" => "No",
                );
        $this->CfgSetup["CaptchaSignup"] =
            array(
                "Type" => "Flag",
                "Label" => "Display CAPTCHA for account signup",
                "Help" => "",
                "OnLabel" => "Yes",
                "OffLabel" => "No",
                );
    }

    function Install()
    {
        $DB = new Database();

        // make the installation process fault tolerant
        $DB->SetQueryErrorsToIgnore(array(
            '/CREATE\s+TABLE\s+[^\s]+\s+\([^)]+\)/i'
              => '/Table\s+[^\s]+\s+already\s+exists/i'));

        # create IP log table
        if (FALSE === $DB->Query("
            CREATE TABLE Captcha_IpLog (
                ClientIp VARCHAR(15) UNIQUE,
                Views INT,
                Successes INT,
                Failures INT,
                LastSeen TIMESTAMP
            );"))
        { return "Could not create the IP log table"; }

        # create user log table
        if (FALSE === $DB->Query("
            CREATE TABLE Captcha_UserLog (
                UserName VARCHAR(15) UNIQUE,
                Views INT,
                Successes INT,
                Failures INT,
                LastSeen TIMESTAMP
            );"))
        { return "Could not create the user log table"; }

        $this->ConfigSetting("Method", "Securimage");
        $this->ConfigSetting("Width", 115);
        $this->ConfigSetting("Height", 40);
        $this->ConfigSetting("DisplayIfLoggedIn",  TRUE);
        $this->ConfigSetting("CaptchaCommentPost", TRUE);
        $this->ConfigSetting("CaptchaMessagePost", TRUE);
        $this->ConfigSetting("CaptchaFeedback",    TRUE);
        $this->ConfigSetting("CaptchaSignup",      TRUE);

        return NULL;
    }

    /**
     * Uninstall the plugin.
     * @return NULL|string NULL if successful or an error message otherwise
     */
    public function Uninstall()
    {
        $DB = new Database();

        foreach( array("Captcha_IpLog" => "IP log",
                       "Captcha_UserLog" => "user log") as $Table => $Desc)
        {
            if (FALSE === $DB->Query("DROP TABLE ".$Table) )
            { return "Could not remove the ".$Desc." table"; }
        }
    }

    function Upgrade($PreviousVersion)
    {
        $DB = new Database();

        # ugprade from versions < 1.0.1 to 2.0.0
        if (version_compare($PreviousVersion, "1.0.1", "<"))
        {
            $Method = $DB->Query("
                SELECT V FROM CaptchaPrefs
                WHERE K='Method'", "Method");

            # migrate existing plugin configuration from the database
            $this->ConfigSetting("Method", $Method);

            # new image dimensions configuration
            $this->ConfigSetting("Width", 115);
            $this->ConfigSetting("Height", 40);
        }

        # upgrade from version 1.0.1 to 1.0.2
        if (version_compare($PreviousVersion, "1.0.2", "<"))
        {
            $Method = $this->ConfigSetting("Method");

            # disabling from the preferences is no longer an option since it's
            # assumed when enabling/disabling the plugin
            if (is_null($Method) || $Method == "None" || $Method == "Disabled")
            {
                $this->ConfigSetting("Method", "Securimage");
            }
        }

        if (version_compare($PreviousVersion, "1.0.3", "<"))
        {
            $this->ConfigSetting("CaptchaCommentPost", TRUE);
            $this->ConfigSetting("CaptchaMessagePost", TRUE);
            $this->ConfigSetting("CaptchaFeedback",    TRUE);
            $this->ConfigSetting("CaptchaSignup",      TRUE);
        }

        if (version_compare($PreviousVersion, "1.0.4", "<"))
        {
            $DB->Query("ALTER TABLE CaptchaIpLog "
                ."RENAME TO Captcha_IpLog");
            $DB->Query("ALTER TABLE CaptchaUserLog "
                ."RENAME TO Captcha_UserLog");
        }

        if (version_compare($PreviousVersion, "1.0.5", "<"))
        {
            $this->ConfigSetting("DisplayIfLoggedIn", TRUE);
        }
    }

    function HookEvents()
    {
        return array(
            "EVENT_SYSTEM_ADMINISTRATION_MENU" => "SysAdminMenu",
            "EVENT_PAGE_LOAD" => "ManageState",
            "EVENT_APPEND_HTML_TO_FORM" => "AppendHtmlToForm",
            "EVENT_VALIDATE_USER_COMMENT" => "VerifyComment",
            "EVENT_USER_LOGIN" => "ResetState",
            "EVENT_USER_LOGOUT" => "ResetState",
            "EVENT_USER_SIGNUP_VERIFY" => "VerifySignup",
            "EVENT_USER_SIGNUP_EXTEND_ERROR_LIST" => "ExtendSignupErrorList",
            );
    }

    function SysAdminMenu()
    {
        return array(
            "Log" => "View Captcha Logs",
            );
    }

    function AppendHtmlToForm($PageName, $FormName, $Labels, $InputElements, $Notes)
    {
        global $User;

        # check if the CAPTCHA should be displayed for logged in users
        if ($User->IsLoggedIn() && !$this->ConfigSetting("DisplayIfLoggedIn"))
        {
            return array("PageName"      => $PageName,
                         "FormName"      => $FormName,
                         "Labels"        => $Labels,
                         "InputElements" => $InputElements,
                         "Notes"         => $Notes);
        }

        if ($this->ConfigSetting("CaptchaCommentPost"))
        {
            $PlacesToDisplay["AddResourceComment"]["CommentForm" ] = TRUE;
            $PlacesToDisplay["PreviewComment"    ]["CommentForm" ] = TRUE;
        }

        if ($this->ConfigSetting("CaptchaMessagePost"))
        {
            $PlacesToDisplay["PostMessage"       ]["CommentForm" ] = TRUE;
            $PlacesToDisplay["PreviewMessage"    ]["CommentForm" ] = TRUE;
            $PlacesToDisplay["AddTopic"          ]["CommentForm" ] = TRUE;
        }

        if ($this->ConfigSetting("CaptchaFeedback"))
        {
            $PlacesToDisplay["LeaveFeedback"     ]["FeedbackForm"] = TRUE;
        }

        if ($this->ConfigSetting("CaptchaSignup"))
        {
            $PlacesToDisplay["RequestAccount"]["NewAccountForm"] = TRUE;
        }

        if ( $this->AlreadySolved !== TRUE &&
             isset($PlacesToDisplay[$PageName][$FormName]) )
        {
            $MyUrl = './plugins/Captcha/';
            $UpdateDB = FALSE;

            $Method = $this->ConfigSetting("Method");
            if ($Method == "Securimage")
            {
                #- For the securimage method:
                # (the SID is not used by securimage_show, but is passed to prevent
                #  browsers from caching the content of that page).
                $Labels []= "Verification Code:";

                $InputElements []=
                    '<table>'
                    .'<tr><td rowspan=2><img id="captcha" '
                    .'src="index.php?P=P_Captcha_DisplayImage&sid='.md5(time()).'" '
                    .'width="'.$this->ConfigSetting("Width").'" height="'.$this->ConfigSetting("Height").'" '
                    .'alt="CAPTCHA Image" /></td>'
                    .'<td><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '
                    .'codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" '
                    .'width="19" height="19" id="SecurImage_as3" align="middle">'
                    .'<param name="allowScriptAccess" value="sameDomain" />'
                    .'<param name="allowFullScreen" value="false" />'
                    .'<param name="movie" value="securimage_play.swf?audio='
                    .'index.php&#63;P&#61;P_Captcha_Sound&bgColor1=#777'
                    .'&bgColor2=#fff&iconColor=#000&roundedCorner=5" />'
                    .'<param name="quality" value="high" />'
                    .'<param name="bgcolor" value="#ffffff" />'
                    .'<embed src="'.$MyUrl.'securimage/securimage_play.swf?audio='
                    .'index.php&#63;P&#61;P_Captcha_Sound&bgColor1=#777'
                    .'&bgColor2=#fff&iconColor=#000&roundedCorner=5" quality="high" '
                    .'bgcolor="#ffffff" width="19" height="19" name="SecurImage_as3" '
                    .'align="middle" allowScriptAccess="sameDomain" allowFullScreen="false" '
                    .'type="application/x-shockwave-flash" '
                    .'pluginspage="http://www.macromedia.com/go/getflashplayer" />'
                    .'</object></td>'
                    .'</tr><tr>'
                    .'<td><a tabindex="-1" style="border-style: none" href="#" title="Refresh Image" '
                    .'onclick="document.getElementById(\'captcha\').src = \''
                    .'index.php?P=P_Captcha_DisplayImage&sid=\' + Math.random(); return false">'
                    .'<img src="'.$MyUrl.'securimage/images/refresh.gif" alt="Reload Image" border="0" '
                    .'onclick="this.blur()" align="bottom" /></a></td>'
                    .'</tr></table><br>'
                    .'<input type="text" name="captcha_code" '
                    .'size="10" maxlength="6" />';

                $Notes []=
                "Please enter the above code to verify that you're not a spam robot.";

                $UpdateDB = TRUE;
            }

            # Now that the CAPTCHA has been displayed, log a view
            # for this user/ip, creating DB entries where required
            # if this is the first time we've seen this user.

            if ($UpdateDB)
            {
                $DB = new Database();
                $DB->Query("INSERT IGNORE INTO "
                           ."Captcha_IpLog (ClientIp,Views,Successes,Failures) "
                           ."VALUES "
                           ."('".$_SERVER["REMOTE_ADDR"]."',0,0,0)");
                $DB->Query("UPDATE Captcha_IpLog "
                           ."SET Views=Views+1, LastSeen=NOW() "
                           ."WHERE ClientIp='".$_SERVER["REMOTE_ADDR"]."'");

                global $G_User;
                if ($G_User->IsLoggedIn())
                {
                    $DB->Query("INSERT IGNORE INTO "
                               ."Captcha_UserLog (UserName,Views,Successes,Failures) "
                               ."VALUES "
                               ."('".$G_User->Name()."',0,0,0)");
                    $DB->Query("UPDATE Captcha_UserLog "
                               ."SET Views=Views+1, LastSeen=NOW() "
                               ."WHERE UserName='".$G_User->Name()."'");
                }
            }
        }

        return array("PageName"      => $PageName,
                     "FormName"      => $FormName,
                     "Labels"        => $Labels,
                     "InputElements" => $InputElements,
                     "Notes"         => $Notes);
    }

    # Verify the Captcha code.  Returns a tri-state:
    #  NULL  -- unable to display captcha
    #  TRUE  -- Captcha displayed and successfully solved
    #  FALSE -- Captcha displayed but solved incorrectly
    function VerifyCaptcha()
    {
        if ($this->AlreadySolved !== TRUE)
        {
            $valid = NULL;

            # Attempt to validate the captcha, using whatever backend
            # is appropriate for our selected method
            $Method = $this->ConfigSetting("Method");
            if ($Method == "Securimage")
            {
                # For the securimage method:
                if( isset($_POST['captcha_code'])) {
                    include("securimage/securimage.php");
                    $img = new Securimage();
                    $valid = $img->check($_POST['captcha_code']);
                }
            }

            # If we were able to run the validation, we then want
            # to update the IP and User logs to indicate success or failure.
            if ($valid !== NULL)
            {
                $UpdateString = ($valid) ?
                    "SET Successes=Successes+1 ":
                    "SET Failures=Failures+1 ";

                $DB = new Database();
                $DB->Query("UPDATE Captcha_IpLog "
                           .$UpdateString
                           ."WHERE ClientIp='".$_SERVER["REMOTE_ADDR"]."'");

                global $G_User;
                if ($G_User->IsLoggedIn())
                {
                    $DB->Query("UPDATE Captcha_UserLog "
                               .$UpdateString.", LastSeen=NOW() "
                               ."WHERE UserName='".$G_User->Name()."'");
                }
            }

            # And if the validation succeeded, we want to stash that.
            if ($valid === TRUE)
            {
                $_SESSION['CaptchaSolved'] = TRUE;
            }
        }
        else
        {
            $valid = TRUE;
        }

        return $valid;
    }

    function VerifyComment($Subject,$Body){
        $CaptchaStatus = $this->VerifyCaptcha();

        $ErrorMessages = ($CaptchaStatus===FALSE)?
            "Verification Code not correctly entered": "";

        return array($CaptchaStatus, $ErrorMessages);
    }


    function VerifySignup($UserName, $Password, $EMail, $Status)
    {
        $CaptchaStatus = $this->VerifyCaptcha();

        if ($CaptchaStatus === FALSE )
        {
            $Status = "U_ERROR_CAPTCHA_FAILURE";
        }

        return array("UserName" => $UserName,
                     "Password" => $Password,
                     "EMail"    => $EMail,
                     "Status"   => $Status);
    }

    function ExtendSignupErrorList($ErrorList)
    {
        $ErrorList ["U_ERROR_CAPTCHA_FAILURE"] = "Verification Code not correctly entered";
        return array("ErrorList" => $ErrorList);
    }

    function ManageState($PageName)
    {
        if (isset($_SESSION['CaptchaSolved']) && $_SESSION['CaptchaSolved']===TRUE)

        {
            $this->AlreadySolved = TRUE;
            $_SESSION['CaptchaSolved'] = TRUE;
        }
        else
        {
            $this->AlreadySolved = FALSE;
        }
    }

    function ResetState()
    {
        if (isset($_SESSION['CaptchaSolved']) )
            unset($_SESSION['CaptchaSolved']);
    }

    private $AlreadySolved;
}
