<?php
/**
 * Contao Open Source CMS
 * Copyright (C) 2005-2010 Leo Feyer
 *
 * Formerly known as TYPOlight Open Source CMS.
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, please visit the Free
 * Software Foundation website at <http://www.gnu.org/licenses/>.
 *
 * PHP version 5
 *
 * @copyright  2010 by e@sy Solutions IT <http://www.easySolutionsIT.de/>
 * @author     Patrick Froch <patrick.froch@easySolutionsIT.de/>
 * @package    Language
 * @license    LGPL
 * @filesource
 */

/**
 * Description:
 * Die Klasse resetPass erzeugt ein neues Contao-Passwort, setzt eine evtl. vorhandene Account-Sperrung zurueck
 * und mailt die neuen Zugangsdaten an den entsprechenden User.
 *
 * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
 * @copyright   Copyright 2011 by e@sy Solutions IT
 * @version     1.0.0
 * @since       19.05.2011
 * @category    Tools
 * @package     eS_Tools
 * @uses        Contao-Framework
 */
class resetPass extends Backend {


    /**
     * Id der zu bearbeitenden Person
     * @var int
     */
    private $intPersId = 0;


    /**
     * Name der Tabelle
     * @var string
     */
    private $strTab = '';


    /**
     * Zeichenvorrat fuer die Passwoerter
     * @var array
     */
    private $arrCharPool = array();


    /**
     * Login-Link
     * @var string
     */
    private $strLink = '';


    /**
     * Username
     * @var string
     */
    private $strUser = '';


    /**
     * Passwort
     * @var string
     */
    private $strPass = '';

    /**
     * Erstellt fuer alle definierten Personen neue Passwoerter.
     * Definiert heisst: user oder member; nur aktive oder alle.
     * @param $dc
     * @return string
     */
    public function runAll($dc){
        $this->import('Database');
        $strContent = "<h1>Alle Passwörter zurücksetzen</h1><ul style='margin-bottom: 20px;'>";

        if(!$GLOBALS['es_resetPassword']['settings']['runTests']){
            $strFilter  = $this->getValue('rp_peoplefilter');
            $strWhere   = ($strFilter == 'all') ? '1' : '`disable` != 1';
            $query      = "SELECT * FROM " . $dc->table . " WHERE $strWhere";
            $result     = $this->Database->query($query);

            if($result->numRows){
                while($result->next()){
                    $strPass = $this->run(null, $result->id, $dc->table);
                    $strContent .= '<li>Passwort für die Person mit der ID: ' . $result->id . ' erstellt.</li>';

                    $this->runHooks('rpSavePassword', array('id' => $result->id, 'table' => $dc->table, 'password' => $strPass));
                }
            }
        } else{
            $strContent .= $this->testPassGen($GLOBALS['es_resetPassword']['settings']['testscount']);
        }

        return $strContent . '</ul>';
    }


    /**
     * Description:
     * Die Methode run() wird aufgerufen, um die Aktionen auszuloesen. Sie fuehrt alle noetigen Operationen durch.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     */
    public function run($dc, $id = '', $tab = ''){
        $this->import('Database');
        $this->import('Environment');

        $id                 = ($dc->id != '' ) ? $dc->id : $id;
        $tab                = ($dc->table != '') ? $dc->table : $tab;

        if(is_array($tab) && array_key_exists('tables', $tab)) {
            $tab = $tab['tables'][0];
        }

        $act                = str_replace('tl_', '', $tab);
        $this->intPersId    = $id;
        $this->strTab       = $tab;
        $data               = $this->getData($id, $tab);
        $adr                = $data['email'];
        $usr                = $this->checkUsername($data['username'], $data);
        $strPass               = $this->makePass();

        $this->mailPass($adr, $strPass, $usr, $tab);
        $this->setPass($id, $strPass, $usr, $tab);
        $this->resetBlocking($id, $tab);
        $this->runHooks('rpSavePassword', array('id' => $id, 'table' => $tab, 'password' => $strPass));

        if($id == '' && $tab == ''){
            // direkter Aufruf
            $this->returnToList($act);
        } else {
            // Aufruf durch runAll()
            $this->returnToList($act);
        }
    }


    /**
     * Description:
     * Die Methode setPass() speichert das uebergebene Passwort und den Usernamen fuer den Benutzer mit der
     * uebergebenen Id in der uebergebenen Tabelle.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <integer>       $id     Die ID des Users, fuer den das Passwort und der Loginname eingetragen werden.
     * @param       <string>        $pass   Das Passwort (in klartext) fuer den Benutzer
     * @param       <integer>       $usr    Der Loginname fuer den Benutzer
     * @param       <string>        $tab    Name der Taberlle, in der die Zugangsdaten gespeichert werden ('tl_user', 'tl_member')
     */
    private function setPass($id, $pass, $usr, $tab){
        $cipherPass = $this->cipherPass($pass);

        if($tab == 'tl_member'){
            $login = ', `login` = 1';
        } else {
            $login = '';
        }

        $query = "UPDATE `$tab` SET `password` = '$cipherPass', `username` = '$usr', `resetPassword` = " . time() . " $login WHERE `id` = '$id'";
        $this->Database->execute($query);
    }


    /**
     * Description:
     * Die Methode checkUsername prueft, ob ein Username vorhanden ist, wenn nicht wird einer erstellt.
     * Ist ein Username in der DB, ist er in $usr abgelegt und wird zurueckgegeben. Wenn $usr leer ist,
     * Wird ein Username aus dem Namen der Benutzers erzeugt. Dazu werden etweder die Felder aus der tl_user oder
     * tl_member werwendet, je nach dem welche im $data-Array gefunden werden.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <string>    $usr    Username aus der DB, falls vorhanden, sonst leer
     * @param       <array>     $data   Ergebnis einer Datenbankabfrage
     * @return      <string>            Eingueltiger Username
     */
    private function checkUsername($usr, $data){
        if($usr != ''){
            return $usr;
        } else {
            if($data['name'] != ''){
                return str_replace(' ', '_', $data['name']);
            } else {
                return str_replace(' ', '_', $data['firstname']) . '.' . str_replace(' ', '_', $data['lastname']);
            }
        }
    }


    /**
     * Description:
     * Erzeugt eine Mail mit dem Passwort, dem Username und dem Link zum Login und sendet diese an den Benutzer.
     * Bei einem Forntend-Login wird nur ein Link zur Homepage mitgeschickt, da die Loginseite ueberall sein kann.
     * Bei einem Backend-Login wird ein Link zur Contao-Anmeldeseite mitgeschickt.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <string>    $adr    Mailadresse an die die Zugangsdaten gesendet werden.
     * @param       <string>    $pass   Das Passwort
     * @param       <string>    $usr    Der Username
     * @param       <string>    $tab    Die Tabelle, um zu entscheiden ob es sich um ein Forntend-Login (tl_member)
     *                                  oder ein Backend-Login (tl_user) handelt. Es wird ein entsprechender Link
     *                                  zum Login mitgeschickt.
     */
    private function mailPass($adr, $pass, $usr, $tab){
        $this->strUser  = $usr;
        $this->strPass  = $pass;
        $this->strLink  = $this->Environment->url . '/';
        $strSubject     = $this->getValue('rp_mailsubject');
        $strMailtext    = $this->getValue('rp_mailtext');

        if($tab == 'tl_user'){
            $this->strLink .= 'contao/';
        }

        $email          = new Email();
        $email->from    = $GLOBALS['TL_CONFIG']['adminEmail'];
        $email->fromName= 'Administrator - ' . $this->strLink;
        $email->subject = $this->replaceAllInsertTags($strSubject);
        $email->html    = $this->replaceAllInsertTags($strMailtext);
        $email->sendTo($adr);
    }


    /**
     * Description:
     * Nach dem Versenden der Zugangsdaten wird wirder auf die Uebersicht umgeleitet.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <string>    $act    Die aufgerufenen Aktion
     */
    private function returnToList($act){
        if(version_compare(VERSION . '.' . BUILD, '2.9.0', '<')) {
           $path = $this->Environment->base . 'typolight/main.php?do=' . $act;
        } else {
           $path = $this->Environment->base . 'contao/main.php?do=' . $act;
        }

        $this->redirect($path, 301);
    }


    /**
     * Description:
     * Die Methode getData liesst die vorhandenen Daten des Benutzers aus der DB.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <integer>   $id     Die Id des Benutzers
     * @param       <string>    $tab    Die Tabelle aus der die Daten ausgelesen werden (tl_user oder tl_member)
     * @return      <array>             Ein Array mit dem Namen, der Mailadresse und dem Usernamen
     */
    private function getData($id, $tab){
        if($tab == 'tl_user'){
            $name = '`name`';
        } else {
            $name = '`firstname`, `lastname`';
        }

        if($id != '' && !is_array($id) && $tab != '' && !is_array($tab)){
            $query = "SELECT `email`, `username`, $name FROM `$tab` WHERE `id` = '$id'";
            $result = $this->Database->execute($query);

            if($result->numRows){
                $row = $result->fetchAssoc();
                return $row;
            }
        }

        return false;
    }


    /**
     * Description:
     * Die Methode cipherPass verschluesselt das Passwort, damit es in die Contao-DB eingetragen werden kann.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <string>    $pass   Das Passwort
     * @return      <string>            Das verschluesselte Passwort
     */
    private function cipherPass($pass){
        $strSalt = substr(md5(uniqid(mt_rand(), true)), 0, 23);
        return sha1($strSalt . $pass) . ':' . $strSalt;
    }


    /**
     * Description:
     * Die Mathode resetBlocking() setzt eine evtl. vorhandene Login-Sperre zurueck.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <integer>   $id     Die Id des Benutzers, bei dem die Login-Sperre aufgehoben werden soll.
     * @param       <string>    $tab    Die Tabelle in die die Eintragung gemacht wird (tl_user oder tl_member)
     */
    private function resetBlocking($id, $tab){
        $query = "UPDATE `$tab` SET `loginCount` = 3, `locked` = 0, `disable` = '' WHERE `id` = '$id'";
        $this->Database->execute($query);
    }


    /**
     * Description:
     * Die Methode makePass() erstellt ein Passwaort.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       19.05.2011
     * @param       <integer>   $len    Die Laenge des zu erzeugenden Passwortes
     * @return      <string>            Das erzeugte Passwort
     */
    private function makePass(){
        $intLen     = $this->getValue('rp_charcount');
        $prefix     = $this->getValue('rp_prefix');
        $postfix    = $this->getValue('rp_postfix');
        $pool       = $this->getCharPool();
        $count      = (count($pool) > 0) ? count($pool) : 1;
        $pass       = '';

        srand((double)microtime()*1000000);

        for($i = 0; $i < $intLen; $i++){
            $intIndex   = (rand()%($count));
            $pass      .= $pool[$intIndex];
        }

        return str_replace(' ', '', $this->replaceAllInsertTags($prefix) . $pass . $this->replaceAllInsertTags($postfix));
    }


    /**
     * Erstellt den Zeichenpool.
     * @return array
     */
    private function getCharPool(){
        if(!count($this->arrCharPool)){
            $strPool    = '';
            $strExclude = $this->getValue('rp_excludechars');
            $strExclude = str_replace(' ', '', $strExclude);
            $strSpecial = $this->getValue('rp_includespecialchars');
            $arrClasses = $this->getValue('rp_charclasses');
            $arrClasses = @unserialize($arrClasses);
            $arrCharPool= array();

            // Zeichengruppen definieren
            $arrPool    = array(
                'upper'     => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                'lower'     => 'abcdefghijklmnopqrstuvwxyz',
                'digit'     => '0123456789',
                'special'   => html_entity_decode($strSpecial)  // Contao kodiert Sonderzeichen vor dem Speichern!
            );

            // Zeichen-Pool zusammenstellen
            if(is_array($arrClasses)){
                foreach($arrClasses as $strClass){
                    $strPool .= $arrPool[$strClass];
                }
            }

            // Exclude-Zeichen loeschen
            for($i = 0; $i < utf8_strlen($strExclude); $i++){
                $strPool = str_replace($strExclude[$i], '', $strPool);
            }

            // Zeichen-Array (multibyte-sicher) erstellen
            for ($i = 0; $i < utf8_strlen($strPool)-1; $i++) {
                $arrCharPool[] = utf8_substr($strPool, $i, 1);
            }

            $this->arrCharPool = $arrCharPool;
        }

        return $this->arrCharPool;
    }


    /**
     * Gibt einen Wert aus den Einstellungen zurueck.
     * Ist kein Wert gespeichert, wird der Vorgabewert zurueck gegeben.
     *
     * @param $strField
     * @return mixed
     */
    private function getValue($strField){
        return ($GLOBALS['TL_CONFIG'][$strField] != '') ? $GLOBALS['TL_CONFIG'][$strField] : $GLOBALS['es_resetPassword']['settings'][$strField];
    }


    /**
     * Ersetzt alle InsertTags, falls ein DB-Zugriff noetig ist, wird dies hier gemacht,
     * sonst wird nur replaceInsertTags() aufgerufen.
     * @param $strString
     * @return string
     */
    private function replaceAllInsertTags($strString){
        if(substr_count($strString, '{{database::') && is_int($this->intPersId)){
            $query  = "SELECT * FROM `" . $this->strTab . "` WHERE id = " . $this->intPersId;
            $result = $this->Database->execute($query);

            if($result->numRows){
                $arrFields = $this->Database->listFields($this->strTab);

                foreach($arrFields as $arrField){
                    $strTag     = '{{database::' . $arrField['name'] . '}}';
                    $strString  = str_replace($strTag, $result->$arrField['name'], $strString);
                }

            }
        }

        $strString  = str_replace('{{custom::username}}', $this->strUser, $strString);
        $strString  = str_replace('{{custom::password}}', $this->strPass, $strString);
        $strString  = str_replace('{{custom::loginlink}}', $this->strLink, $strString);
        return $this->replaceInsertTags($strString);
    }


    /* ============= */
    /* Hook-Methoden */
    /* ============= */

    /**
     * Ersetzt die InsertTags der Erweiterung.
     * @param $strTag
     * @return bool|string
     */
    public function myReplaceInsertTags($strTag){
        $arrTemp = explode('::', $strTag);
        $arrElem = explode('.', $arrTemp[1]);

        switch($arrElem[0]){
            case 'date':
                return date($arrElem[1]);
                break;

            default:
                return false;
                break;
        }
    }



    /* ================ */
    /* Hooks ausfuehren */
    /* ================ */

    /**
     * Ruft die Hooks auf.
     * @param $strHook
     * @param null $varParam
     * @return array|bool
     */
    public function runHooks($strHook, $varParam = null){
        if(array_key_exists($strHook, $GLOBALS['TL_HOOKS'])){
            $arrReturn = array();

            foreach($GLOBALS['TL_HOOKS'][$strHook] as $arrHook){
                if(is_array($arrHook) && count($arrHook) == 2){
                    $strClass   = $arrHook[0];
                    $strMethode = $arrHook[1];
                    $this->import($strClass);
                    $arrReturn[] = $this->$strClass->$strMethode($varParam);
                }
            }

            return $arrReturn;
        }

        return false;
    }


    /* ================= */
    /* Callback-Methoden */
    /* ================= */

    /**
     * Description:
     * Die Methode erzeugt ein ICon in abhaengigkeit vom letzten Passwortreset. Ist weniger als 15 Minuten seit
     * dem letzten Passwort-Reset vergangen, wird ein alternatives ICon angezeigt.
     *
     * @author      Patrick Froch (e@sy Solutions IT) <patrick.froch@easySolutionsIT.de>
     * @since       24.05.2011
     * @param       array       $arrRow                 the current row
     * @param       string      $href                   the url of the embedded link of the button
     * @param       string      $label                  label text for the button
     * @param       string      $title                  title value for the button
     * @param       string      $icon                   url of the image for the button
     * @param       array       $attributes             additional attributes for the button (fetched from the array key "attributes" in the DCA)
     * @param       string      $strTable               the name of the current table
     * @param                   $arrRootIds             array of the ids of the selected "page mounts" (only in tree view)
     * @param                   $arrChildRecordIds      ids of the childs of the current record (only in tree view)
     * @param       boolean     $blnCircularReference   determines if this record has a circular reference (used to prevent pasting of an cutted item from an tree into any of it's childs).
     * @param       string      $strPrevious            id of the previous entry on the same parent/child level. Used for move up/down buttons. Not for root entries in tree view.
     * @param       string      $strNext                id of the next entry on the same parent/child level. Used for move up/down buttons. Not for root entries in tree view.
     * @return      string                              Link for the List
     */
    public function makeIcon($arrRow, $href, $label, $title, $icon, $attributes, $strTable, $arrRootIds, $arrChildRecordIds, $blnCircularReference, $strPrevious, $strNext){
        $displyTime = time() - $GLOBALS['es_resetPassword']['settings']['timeForFeedback'];
        $href      .= '&id=' . $arrRow['id'];           // Die Id anfuegen

        if($arrRow['resetPassword'] > $displyTime){
            // Icon aendern, wenn nicht mehr als $displayTime seit dem letzen REset vergangen sind.
            $icon = 'system/modules/es_resetpassword/html/email_to_friend.png';
        }

        return '<a href="'.$this->addToUrl($href).'" title="'.specialchars($title).'"'.$attributes.'>'.$this->generateImage($icon, $label).'</a> ';
    }


    /**
     * Gibt den Default-Wert fuer die Einstellungen zurueck.
     * @param $varData
     * @param $dc
     * @return string
     */
    public function loadSettingValue($varData, $dc){
        if(version_compare(VERSION . '.' . BUILD, '3.0.0', '<')){
            // Contao 2.11.x
            $this->import('Config');
        }

        if($varData == '' || $varData == '0' || $varData == '0,00' || $dc->field == 'showheadline'){
            if(is_array($GLOBALS) && array_key_exists('es_resetPassword', $GLOBALS) && is_array($GLOBALS['es_resetPassword']) && array_key_exists('settings', $GLOBALS['es_resetPassword']) && is_array($GLOBALS['es_resetPassword']['settings']) && array_key_exists($dc->field, $GLOBALS['es_resetPassword']['settings'])){
                return $GLOBALS['es_resetPassword']['settings'][$dc->field];
            }
        }

        return $varData;
    }


    /* ============ */
    /* Testmethoden */
    /* ============ */

    private function testPassGen($intCount = 1000){
        $strContent     = '';
        $strOkayContent = '';
        $this->intPersId= 1;
        $this->strTab   = 'tl_member';
        $prefix         = $this->replaceAllInsertTags($this->getValue('rp_prefix'));
        $postfix        = $this->replaceAllInsertTags($this->getValue('rp_postfix'));
        $intLen         = $this->getValue('rp_charcount');
        $intLen         = $intLen + strlen($prefix) + strlen($postfix);

        for($i = 0; $i < $intCount; $i++){
            $strPass = $this->makePass(null, 1, 'tl_member');
            $bolOkay = true;

            if(!substr_count($strPass, $prefix)){
                $bolOkay = false;
                $strContent .= '<li>Prefix nicht gefunden - Prefix: ' . $prefix . ' - Passowrt: ' . $strPass . '</li>';
            }

            if(!substr_count($strPass, $postfix)){
                $bolOkay = false;
                $strContent .= '<li>Postfix nicht gefunden - Postfix: ' . $postfix . ' - Passowrt: ' . $strPass . '</li>';
            }

            if(mb_strlen($strPass) != $intLen){
                $bolOkay = false;
                $strContent .= '<li>Passwort hat die flasche Länge - gewünschte Länge: ' . $intLen . ' - gefundene Länge: '. strlen($strPass) . ' - Passowrt: ' . $strPass . '</li>';
            }

            $strTempEnc = $this->testEncryption($strPass);

            if(!$strTempEnc){
                $bolOkay = false;
                $strContent .= $strTempEnc;
            }

            if($bolOkay){
                if($i % 100 == 0){
                    if($i == 0 ) {
                        $strOkayContent .= "<br>00$i: .";
                    } else {
                        $strOkayContent .= "<br>$i: .";
                    }
                } else {
                    $strOkayContent .= '.';
                }
            }
        }

        return '<li>' . $strOkayContent . '</li>' . $strContent;
    }


    private function testEncryption($pass){
        $encPass = Encryption::encrypt($pass);
        $decPass = Encryption::decrypt($encPass);

        if($decPass == $pass){
            return true;
        } else {
            return '<li>Passwort hat die flasche Länge - verschlüsseltes Passwort: ' . $encPass . ' - entschlüsseltes Passwort: '. $decPass . ' - Passowrt: ' . $pass . '</li>';
        }
    }
}
?>
