<?PHP

final class SyncHelper
{
    private $DBLink;
    private $InCwis;
    private $ErrorStatus;

    const STATUS_OK = 0;
    const STATUS_NODB = 1;

    // DB abstraction
    // When we're in Drupal, we just want to use the mysql_...() calls
    // but if we're in CWIS, we want to take advantage of Axis--Database
    private function Query($String)
    {        
        $rc = array();

        $is_select = preg_match("/^select/i", $String) === 1;
        if ($this->InCwis)
        {
            $this->DBLink->Query($String);
            if ($is_select)
            {
                while ($Row = $this->DBLink->FetchRow()){ $rc []= $Row; }
            }
        }
        else
        {
            $rs = mysql_query($String, $this->DBLink);

            if ($is_select && is_resource($rs) )
            {
                while ($Row = mysql_fetch_array($rs)){ $rc []= $Row; }
            }
        }
        return $rc;
    }

    // Fetch a list of users who need something done to them
    private function UsersTo($Command)
    {
        $TableName =
            ($this->InCwis?"DrupalToCwis":"CwisToDrupal")."UserSync";
        return $this->Query("SELECT * FROM ".$TableName
                            ." WHERE Command='".$Command."'");
    }

    // Indicate that pending actions have been completed,
    // purging the request off of the queue
    private function CmdComplete($UserName,$Command)
    {
        $TableName =
            ($this->InCwis?"DrupalToCwis":"CwisToDrupal")."UserSync";
        $this->Query("DELETE FROM ".$TableName." WHERE "
                     ."Command='".$Command."' "
                     ."AND UserName='".addslashes($UserName)."'");
    }

    // Take a raw username, and convert it into a format that
    // will make CWIS happy.  Drupal is pretty liberal about what
    // characters they accept in a username, whereas CWIS is a
    // bit more strict
    private function NormalizeUserName($UserName)
    {
        $rc = "";
        if (! preg_match("/^[a-zA-Z0-9]{2,24}$/", $UserName))
        {
            for ($i=0;
                 $i<strlen($UserName) && strlen($rc) <= 24;
                 $i++)
            {
                if (preg_match("/^[a-zA-Z0-9]$/", $UserName[$i]))
                {
                    $rc .= $UserName[$i];
                }
            }
        }
        else
        {
            $rc = $UserName;
        }
        return $rc;
    }

    // Look up the current user's equivalent login on the other site
    private function RemoteName($LocalName)
    {
        $rc = "";

        $Tgt = ($this->InCwis ? "Drupal" : "Cwis"  )."Name";
        $Key = ($this->InCwis ? "Cwis"   : "Drupal")."Name";

        $Row = array_pop(
            $this->Query("SELECT ".$Tgt." FROM UserNameMap "
                         ."WHERE ".$Key."='".addslashes($LocalName)."'"));

        if (isset($Row[$Tgt]) )
        {
            $rc = $Row[$Tgt];
        }

        return $rc;
    }

    // Create a user name mapping from this site to the remote
    // Also useful to check if a parallel user needs to be created
    private function MapUserName($DrupalName, $CwisName)
    {
        $ExistingMaps = $this->Query(
            "SELECT * FROM UserNameMap WHERE "
            ."DrupalName='".addslashes($DrupalName)."' OR "
            ."CwisName='".addslashes($CwisName)."'");

        if ( count($ExistingMaps) == 0 )
        {
            $this->Query("INSERT INTO UserNameMap "
                         ."(DrupalName, CwisName) VALUES "
                         ."('".addslashes($DrupalName)."',"
                         ."'".addslashes($CwisName)."')");
        }
    }

    // Constructor
    function SyncHelper($DBInfo=NULL)
    {
        $this->Status = SyncHelper::STATUS_OK;

        if ($DBInfo !== NULL)
        {
            $this->InCwis = FALSE;
            if (strlen($DBInfo["server"])>0)
            {
                $this->DBLink = mysql_connect($DBInfo["server"],
                                              $DBInfo["user"],
                                              $DBInfo["pass"],
                                              TRUE);
                mysql_select_db( $DBInfo["database"], $this->DBLink );
            }
            else
            {
                $this->Status = SyncHelper::STATUS_NODB;
            }
        }
        else
        {
            $this->InCwis = TRUE;
            $this->DBLink = new Database();
        }
    }

    function GetStatus() { return $this->Status; }

    function __destruct()
    {
        if ( $this->InCwis==FALSE &&
             $this->Status==SyncHelper::STATUS_OK)
        {
            mysql_close($this->DBLink);
        }
    }

    // Signal a command, via both a cookie and a record in the DB
    //
    function SignalCommand($Command, $UserName, $Password=NULL, $Email=NULL)
    {
        $TableName =
            ($this->InCwis?"CwisToDrupal":"DrupalToCwis")."UserSync";

        if ($Command=="Create")
        {
            // Check to see if we've already made a mapping for this
            // user
            $RemName = $this->RemoteName($UserName);

            if (strlen($RemName)==0)
            {
                if ($this->InCwis)
                {
                    $DrupalName = $UserName;
                    $CwisName   = $UserName;
                }
                else
                {
                    $DrupalName = $UserName;
                    $CwisName   = $this->NormalizeUserName($UserName);
                }

                $this->MapUserName($DrupalName, $CwisName);
                $RemName = $this->RemoteName($UserName);

                $this->Query(
                    "INSERT INTO ".$TableName." "
                    ."(Command, UserName, Password, Email) VALUES "
                    ."('Create', "
                    ."'".addslashes($RemName)."', "
                    ."'".addslashes($Password)."', "
                    ."'".addslashes($Email)."')");
            }
        }
        elseif ($Command == "Delete")
        {
            $RemName = $this->RemoteName($UserName);
            if (strlen($RemName)>0 )
            {
                $this->Query(
                    "INSERT INTO ".$TableName." "
                    ."(Command, UserName) VALUES "
                    ."('Delete','".addslashes($RemName)."')");
            }
        }
        elseif ($Command == "Update")
        {
            $RemName = $this->RemoteName($UserName);
            if (strlen($RemName)>0)
            {
                $this->Query(
                    "DELETE FROM ".$TableName." WHERE "
                    ."Command='Update' AND "
                    ."UserName='".addslashes($RemName)."'");
                $this->Query(
                    "INSERT INTO ".$TableName." "
                    ."(Command, UserName, Password, Email) VALUES "
                    ."('Update', "
                    ."'".addslashes($RemName)."', "
                    ."'".addslashes($Password)."', "
                    ."'".addslashes($Email)."')");
            }
            else
            {
                $this->SignalCommand(
                    "Create", $UserName, $Password, $Email);
            }
        }
        elseif ($Command == "Login" || $Command == "Logout" )
        {

            if ($Command=="Login")
            {
                $this->SignalCommand(
                    "Create", $UserName, $Password, $Email);
            }

            $RemName = $this->RemoteName($UserName);

            if ( strlen($RemName)>0 )
            {
                $this->Query(
                    "DELETE FROM ".$TableName." WHERE "
                    ."UserName='".addslashes($RemName)."' "
                    ."AND ( Command='Logout' OR Command='Login' )");

                $CookieName =
                    ($this->InCwis?"Drupal":"Cwis")."LoginToken";

                mt_srand((double)microtime() * 1000000);
                $Token = mt_rand(0, 2147483647);

                $CookieData = array(
                    "Version" => 1,
                    "UserName" => $RemName,
                    "Token" => $Token
                    );

                $EncData = urlencode(
                    base64_encode(gzcompress(serialize($CookieData))));
                setcookie($CookieName, $EncData, time()+3600*24, "/");

                $this->Query(
                    "INSERT INTO ".$TableName." "
                    ."(Command, UserName, Password, Token) VALUES "
                    ."('".$Command."',"
                    ."'".addslashes($RemName)."',"
                    ."'".addslashes($Password)."',"
                    .$Token.")");
            }
        }
    }

    function CheckForCommand($CurrentUserName)
    {
        $rc = array("Command" => NULL, "User"=>NULL);

        $CookieName = ($this->InCwis ?"Cwis":"Drupal")."LoginToken";
        $TableName =
            ($this->InCwis?"DrupalToCwis":"CwisToDrupal")."UserSync";

        // Look for a login cookie
        if (isset($_COOKIE[$CookieName]))
        {
            // Extract the cookie data
            $CookieData = unserialize(
                gzuncompress(base64_decode(urldecode($_COOKIE[$CookieName]))));

            // Versioned cookies to allow adding features later
            if ($CookieData["Version"] == 1)
            {
                $UserName = $CookieData["UserName"];
                $Token    = $CookieData["Token"];
                $QueryString =
                        "SELECT * FROM ".$TableName." WHERE "
                        ."UserName='".addslashes($UserName)."' AND "
                        ."Token=".$Token;

                $Data=array_pop($this->Query($QueryString));

                if ( $Data !== NULL ) {
                    // Process the requested command:
                    if ($Data["Command"] == "Login" &&
                        $CurrentUserName === NULL )
                    {
                        $rc = array("Command"  => "Login",
                                    "UserName" => $UserName,
                                    "Password" => $Data["Password"]);
                    }
                    elseif ($Data["Command"] == "Logout" &&
                            $CurrentUserName == $UserName )
                    {
                        $rc = array("Command"  => "Logout",
                                    "UserName" => $UserName);
                    }
                } // if Data was in DB
            } // if Cookie was version 1
        } // if Cookie was set

        return $rc;
    }

    // Functions to retrieve lists of pending actions
    function UsersToCreate() { return $this->UsersTo("Create"); }
    function UsersToDelete() { return $this->UsersTo("Delete"); }
    function UsersToUpdate() { return $this->UsersTo("Update"); }

    // Functions to indicate that actions are completed.
    function UserCreated($Name)   { $this->CmdComplete($Name, "Create"); }
    function UserUpdated($Name)   { $this->CmdComplete($Name, "Update"); }

    function UserDeleted($Name){
        $this->CmdComplete($Name, "Delete");
        $this->Query("DELETE FROM UserNameMap WHERE "
                     .($this->InCwis?"Cwis":"Drupal")
                     ."Name='".addslashes($Name)."'");
    }

    function UserLoggedIn($Name){
        setcookie( ($this->InCwis?"Cwis":"Drupal")."LoginToken","",
                   time()-3600);
        $this->CmdComplete($Name, "Login");
    }

    function UserLoggedOut($Name) {
        setcookie( ($this->InCwis?"Cwis":"Drupal")."LoginToken","",
                   time()-3600);
        $this->CmdComplete($Name, "Logout");
    }
};
?>
