<?php
#BEGIN_LICENSE
#-------------------------------------------------------------------------
# Module: MAMS (c) 2020-2021 by CMS Made Simple Foundation
#  An add-on module for CMS Made Simple to provide useful functions
#  and commonly used gui capabilities to other modules.
#-------------------------------------------------------------------------
# A fork of:
#
# Module: FrontEndUsers (c) 2008-2014 by Robert Campbell
#         (calguy1000@cmsmadesimple.org)
#
#-------------------------------------------------------------------------
#
# CMSMS - CMS Made Simple is (c) 2006 - 2021 by CMS Made Simple Foundation
# CMSMS - CMS Made Simple is (c) 2005 by Ted Kulp (wishy@cmsmadesimple.org)
# Visit the CMSMS Homepage at: http://www.cmsmadesimple.org
#
#-------------------------------------------------------------------------
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# However, as a special exception to the GPL, this software is distributed
# as an addon module to CMS Made Simple.  You may not use this software
# in any Non GPL version of CMS Made simple, or in any version of CMS
# Made simple that does not indicate clearly and obviously in its admin
# section that the site was built with CMS Made simple.
#
# 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 General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Or read it online: http://www.gnu.org/licenses/licenses.html#GPL
#
#-------------------------------------------------------------------------
#END_LICENSE

namespace MAMS;


class DbException extends \Exception {}
class DbDropException extends \Exception {}
class DbRollBackException extends \Exception {}

class feu_importer
{
  private static $_initialized   = FALSE;
  private static $_feu           = FALSE;
  private static $_feu_installed = FALSE;
  private static $_mod           = FALSE;
  private static $_db            = FALSE;
  
  private static $_tables        = [
                                      'users',
                                      'groups',
                                      'loggedin',
                                      'belongs',
                                      'dropdowns',
                                      'grouppropmap',
                                      'properties',
                                      'propdefn',
                                      'tempcode',
                                      'history'
                                    ];
  
  private static $_indexes_tables = [
                                      ['belongs', 'idx_belongs'],
                                      ['users', 'idx_username'],
                                      ['users', 'idx_expires'],
                                      ['history', 'idx_refdate'],
                                      ['properties', 'idx_propusertitle'],
                                      ['properties', 'idx_proptitle']
                                    ];
                                    
  private static $_indexes = [
                                'idx_belongs'       => ['groupid'],
                                'idx_username'      => ['username'],
                                'idx_expires'       => ['expires'],
                                'idx_refdate'       => ['userid', 'refdate', 'action'],
                                'idx_propusertitle' => ['userid', 'title'],
                                'idx_proptitle'     => ['title']
                              ];
  
  private static $_prefs         = [
                                      'secure_field_color',
                                      'secure_field_marker',
                                      'login_after_verify',
                                      'signin_button',
                                      'allow_duplicate_reminders',
                                      'support_lostpw',
                                      'password_requiredchars',
                                      'allow_changeusername',
                                      'support_lostun',
                                      'use_usersalt',
                                      'expireage_months',
                                      'notification_subject',
                                      'allowed_image_extensions',
                                      'image_destination_path',
                                      'allow_repeated_logins',
                                      'passwordfldlength',
                                      'usernamefldlength',
                                      'username_is_email',
                                      'allow_duplicate_emails',
                                      'pageid_afterverify',
                                      'pageid_logout',
                                      'pageid_login',
                                      'pageid_changesettings',
                                      'hidden_field_color',
                                      'pageid_forgotpasswd',
                                      'hidden_field_marker',
                                      'required_field_color',
                                      'require_onegroup',
                                      'default_group',
                                      'required_field_marker',
                                      'cookiename',
                                      'cookie_keepalive',
                                      'user_session_expires',
                                      'max_usernamelength',
                                      'min_usernamelength',
                                      'pwrequiredchars',
                                      'enhanced_password',
                                      'min_passwordlength',
                                      'max_passwordlength',
                                      'pageidforgotpasswd',
                                      'pageid_onverify',
                                      'pageid_afterchangesettings',
                                      'auto_create_unknown',
                                      'auth_module',
                                      'clearhistory_age',
                                      'use_randomusername',
  //                                    'mams_specific_permissions',
                                      'forcelogout_times',
                                      'forcelogout_sessionage',
                                      'expireusers_interval',
                                      'pwsalt'
                                    ];
  
  
  private static function _initialize()
  {
    if(!self::$_initialized)
    {
      self::$_feu           = \cms_utils::get_module('FrontEndUsers');
      self::$_mod           = \cms_utils::get_module('MAMS');
      self::$_db            = \cms_utils::get_db();
      $mods                 = \ModuleOperations::get_instance()->GetInstalledModules();
      self::$_feu_installed = in_array('FrontEndUsers', $mods);
      self::$_initialized   = TRUE;
    }
  }
  
  static private function _is_feu_active()
  {
    return \ModuleOperations::get_instance()->IsModuleActive('FrontEndUsers');
  }
  
  static private function _is_feu_installed()
  {
    self::_initialize();
    return self::$_feu_installed;
  }
  
  static private function _have_feu()
  {
    return (self::_is_feu_installed() && self::_is_feu_active() && is_object(self::$_feu) );
  }
  
  /**
   * backs up MAMS tables
   * @throws \MAMS\DbException
   */
  static private function _CopyMamsTables2bkup()
  {
    $prefix_f = \cms_db_prefix() . 'module_mams_';
    $prefix_t = \cms_db_prefix() . 'module_mams__tmp__';
  
    foreach(self::$_tables as $one)
    {
      $f = $prefix_f . $one;
      $t = $prefix_t . $one;
    
      \audit(80085, 'feu2mams', 'processing table ' . $t);
      
      $q = 'DROP TABLE IF EXISTS ' . $t;
      $r = self::$_db->Execute($q);
    
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      \audit(80085, 'feu2mams', 'creating table ' . $t);
  
      $q = 'CREATE TABLE IF NOT EXISTS ' . $t . ' LIKE ' . $f;
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
      
      \audit(80085, 'feu2mams', 'created , copying...');
      
      $q = 'INSERT ' . $t . ' SELECT * FROM ' . $f;
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      \audit(80085, 'feu2mams', 'copyed!');
    }
  }
  
  /**
   * copies tables from FEU 2 MAMS
   * @throws \MAMS\DbException
   */
  static private function _CopyFeuTables2Mams()
  {
    $prefix_f = \cms_db_prefix() . 'module_feusers_';
    $prefix_t = \cms_db_prefix() . 'module_mams_';
  
    $q = 'SET foreign_key_checks = 0';
    $r = self::$_db->Execute($q);
  
    if(!$r) throw new DbException(
      self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
      self::$_db->ErrorNo() );
    
    foreach(self::$_tables as $one)
    {
      $f = $prefix_f . $one;
      $t = $prefix_t . $one;
      
      $q = 'DROP TABLE IF EXISTS ' . $t;
      $r = self::$_db->Execute($q);
      
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      $q = 'CREATE TABLE IF NOT EXISTS ' . $t . ' LIKE ' . $f;
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      $q = 'INSERT ' . $t . ' SELECT * FROM ' . $f;
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
    }
    $q = 'SET foreign_key_checks = 1';
    $r = self::$_db->Execute($q);
  
    if(!$r) throw new DbException(
      self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
      self::$_db->ErrorNo() );
    
  }
  
  /**
   * Rolls back i.e. gets original tables from backup
   * @throws \MAMS\DbRollBackException
   */
  static private function _RollBack()
  {
    $prefix_f = \cms_db_prefix() . 'module_mams__tmp__';
    $prefix_t = \cms_db_prefix() . 'module_mams_';
  
    $q = 'SET foreign_key_checks = 0';
    $r = self::$_db->Execute($q);
  
    if(!$r) throw new DbRollBackException(
      self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
      self::$_db->ErrorNo() );
    
    foreach(self::$_tables as $one)
    {      $f = $prefix_f . $one;
      
      $t = $prefix_t . $one;
      
      $q = 'DROP TABLE IF EXISTS ' . $t;
      $r = self::$_db->Execute($q);
      
      if(!$r) throw new DbRollBackException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      $q = 'CREATE TABLE IF NOT EXISTS ' . $t . ' LIKE ' . $f;
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbRollBackException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      $q = 'INSERT ' . $t . ' SELECT * FROM ' . $f;
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbRollBackException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
    }
  
    $q = 'SET foreign_key_checks = 1';
    $r = self::$_db->Execute($q);
  
    if(!$r) throw new DbRollBackException(
      self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
      self::$_db->ErrorNo() );
  }
  
  /**
   * drops temporary backup tables
   * @throws \MAMS\DbDropException
   */
  static private function _DropTemps()
  {
    $prefix = \cms_db_prefix() . 'module_mams__tmp__';
    
    # try reversing the order to prevent foreign key constraints fails
    # $tables = array_reverse(self::$_tables);
    
    $q = 'SET foreign_key_checks = 0';
    $r = self::$_db->Execute($q);
  
    if(!$r) throw new DbDropException(
      self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
      self::$_db->ErrorNo() );
    
    foreach(self::$_tables as $one)
    {
      $t = $prefix . $one;
  
      \audit(80085, 'feu2mams', 'dropping table ' . $t);
      
      $q = 'DROP TABLE IF EXISTS ' . $t;
      $r = self::$_db->Execute($q);
      
      if(!$r) throw new DbDropException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
  
      \audit(80085, 'feu2mams', 'dropped!');
    }
  
    $q = 'SET foreign_key_checks = 1';
    $r = self::$_db->Execute($q);
  
    if(!$r) throw new DbDropException(
      self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
      self::$_db->ErrorNo() );
  }
  
  /**
   * @throws \MAMS\DbException
   */
  static private function _RenameIndexes()
  {
    $prefix = \cms_db_prefix() . 'module_mams_';
    
    foreach(self::$_indexes_tables as $one)
    {
      list($table, $index_name) = $one;
      $tablename = $prefix . $table;
      $drop      = 'cms_feu_' . $index_name;
      $add       = 'cms_mams_' . $index_name;
      $fields    = implode(',', self::$_indexes[$index_name]);
      
      $q = 'ALTER TABLE ' . $tablename
           . ' DROP INDEX ' . $drop
           . ', ADD INDEX ' . $add . ' (' . $fields . ') USING BTREE';
  
      $r = self::$_db->Execute($q);
  
      if(!$r) throw new DbException(
        self::$_db->ErrorNo() . ' - ' . self::$_db->ErrorMsg() ,
        self::$_db->ErrorNo() );
    }
    
  }
  

  static private function _CopyPreferences()
  {
    foreach(self::$_prefs as $one)
    {
      self::$_mod->SetPreference( $one, self::$_feu->GetPreference($one) );
    }
    
    # only exception found is this one
  
    self::$_mod->SetPreference( 'mams_specific_permissions', self::$_feu->GetPreference('feu_specific_permissions') );
  }
  
  /**
   * public methods
   */
  
  static function run()
  {
    self::_initialize();
    $code = 0;
    $msg = 'OK';
    $err = [$code, $msg];
    $fail = FALSE;
    
    if( !self::_is_feu_installed() )
    {
      $code = 1;
      $msg = 'no_feu_install';
      $err = [$code, $msg];
      return [FALSE, $err];
    }
    
    
    # backup MAMS tables
    try
    {
      \audit(880085, 'feu2mams', 'Start making backups');
      self::_CopyMamsTables2bkup();
    }
    catch( DbException $e)
    {
      $code = $e->getCode();
      $msg = $e->getMessage();
      
      try
      {
        # something went wrong remove the backups anyway
        \audit(880085, 'feu2mams', 'Something went wrong creating backups of MAMS tables... going to drop temp tables!');
        self::_DropTemps();
        \audit(880085, 'feu2mams', 'Backup tables dropped successfully!');
      }
      catch( DbDropException $e)
      {
        $code = $e->getCode();
        $msg = $e->getMessage();
        \audit(880085, 'feu2mams', 'Error dropping MAMS temp tables...');
      }
      finally
      {
        \audit(880085, 'feu2mams', 'Copying tables from FEU failed! Import aborted and some cleaning up was attempted.');
        # whether backup failed or both backup and drop temps
        # we return the error code -- there is only so much we can do
        $err = [$code, $msg];
        return [FALSE, $err];
      }
    }
    
    # copy FEU 2 MAMS
    try
    {
      \audit(880085, 'feu2mams', 'Start copying tables from FEU');
      self::_CopyFeuTables2Mams();
    }
    catch( DbException $e)
    {
      $code = $e->getCode();
      $msg = $e->getMessage();
      
      try
      {
        # something went wrong
        \audit(880085, 'feu2mams', 'Something went wrong copying tables from FEU... going to rollback!');
        # roll back
        self::_RollBack();
        # remove the backups anyway
        \audit(880085, 'feu2mams', 'Rolled back... removing temp backup tables');
        self::_DropTemps();
        \audit(880085, 'feu2mams', 'Backup tables dropped successfully!');
      }
      catch( DbRollBackException $e)
      {
        $code = $e->getCode();
        $msg = $e->getMessage();
        \audit(880085, 'feu2mams', 'Error rolling back...');
      }
      catch( DbDropException $e)
      {
        $code = $e->getCode();
        $msg = $e->getMessage();
        \audit(880085, 'feu2mams', 'Error dropping MAMS temp tables...');
      }
      finally
      {
        # whether backup failed or both backup and drop temps
        # we return the error code -- there is only so much we can do
        \audit(880085, 'feu2mams', 'Copying tables from FEU failed! Import aborted and some cleaning up was attempted.');
        $err = [$code, $msg];
        return [FALSE, $err];
      }
    }
    
    try
    {
      self::_DropTemps();
      \audit(880085, 'feu2mams', 'Backup tables dropped successfully!');
    }
    catch( DbDropException $e)
    {
      $code = $e->getCode();
      $msg = $e->getMessage();
      $fail = TRUE;
      \audit(880085, 'feu2mams', 'Error dropping MAMS temp tables...');
    }
    finally
    {
      if($fail)
      {
        # whether backup failed or both backup and drop temps
        # we return the error code -- there is only so much we can do
        \audit(880085, 'feu2mams', 'Copying tables from FEU seems to have worked!But apparently there are some leftover temp tables.');
        $err = [$code, $msg];
        return [FALSE, $err];
      }
    }
    
    try
    {
      self::_RenameIndexes();
    }
    catch(DbException $e)
    {
      $code = $e->getCode();
      $msg = $e->getMessage();
      $err = [$code, $msg];
      //return [FALSE, $err]; it doesn't warrant a rollback
    }
    
    //    try
    //    {
    //      self::_CopyTemplates();
    //    }
    //    catch(\Exception $e)
    //    {
    //      $err = [$e->getCode(), $e->getMessage()];
    //      return [FALSE, $err];
    //    }
    
    try
    {
      self::_CopyPreferences();
    }
    catch(\Exception $e)
    {
      $code = $e->getCode();
      $msg = $e->getMessage();
      $err = [$code, $msg];
      return [FALSE, $err];
    }
    
    return [TRUE, $err];
  }
  
  
}

?>