<?php
#-------------------------------------------------------------------------
# Module: Captcha - Enables CAPTCHA support for use in other modules.
#-------------------------------------------------------------------------
# Version 1.0
# maintained by Fernando Morgado AKA Jo Morg since 2017
#-------------------------------------------------------------------------
# Original Author: Dick Ittmann
#-------------------------------------------------------------------------
#
# Captcha is a CMS Made Simple module that enables the web developer to create
# multiple lists throughout a site. It can be duplicated and given friendly
# names for easier client maintenance.
#
#-------------------------------------------------------------------------
# BEGIN_LICENSE
#-------------------------------------------------------------------------
#
# This file is part of Captcha
# Captcha 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.
#
# Captcha 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
#-------------------------------------------------------------------------
class Captcha extends CMSModule
{
  /**
  * @var array   Messages to be shown to the user
  */
  var $messages;

  /**
  * @var array   Error messages to be shown to the user
  */
  var $errors;

  /**
  * The currently active Captcha library
  * @var object The active Captcha library
  */
  var $active_lib;
  
//  /**
//  * @var object  Can hold a reference to a Admin object
//  */
//  var $admin;
  
  protected function _initialize()
  {
      $loader_file = cms_join_path( 
                                    dirname(__FILE__),
                                    'lib',
                                    'class.autoloader.php'
                                  );
                              
      if( !is_readable($loader_file) )
      {
        echo '<p><div class="red"><strong>ERROR:</strong> The module Captcha could not be initialized.</div></p>';
        return;
      }
      
      require_once($loader_file);
      
      $loader = new \Captcha\autoloader() ;
      $loader->register();
      
      # register lib    
      $tmpLibsPath = cms_join_path( 
                                    dirname(__FILE__),
                                    'lib'
                                  );
                                  
      $loader->registerFilePrefix('class', array($tmpLibsPath) );
      $namespacepaths[] = $tmpLibsPath;
      $loader->registerNamespaceFallback($tmpLibsPath);
  }
  
  /**
  * Module class constructor
  */
  function __construct() 
  {
    $this->_initialize();
    parent::__construct();
    $this->setMessages(array());
    $this->setErrors(array());
    $this->captchalibs = array();
  }
  
  /**
  * @param array
  */
  function setMessages($array)
  {
    $this->messages = $array;
  }
  
  /**
  * @return array
  */
  function getMessages()
  {
    return $this->messages;
  }
  
  /**
  * @param array
  */
  function setErrors($array)
  {
    $this->errors = $array;
  }
  
  /**
  * @return array
  */
  function getErrors()
  {
    return $this->errors;
  }
  
  /**
  * @param string
  */
  function addMessage($msg)
  {
    $this->messages[] = $msg;
  }
  
  /**
  * @param string
  */
  function addError($error_msg)
  {
    $this->errors[] = $error_msg;
  }
  
  /**
  * @return array
  */
  function getCaptchaLibs()
  {
    $this->loadLibs();
    return  \Captcha\plugins_ops::get_loaded();
  }
  
  /**
  * @param object
  */
  function setActiveLib($lib)
  {
    $this->active_lib = $lib;
    $this->setPreference('active_lib', $lib->getName());
  }
  
  /**
  * @return object
  */
  function getActiveLib()
  {
    if( !is_object($this->active_lib) )
    {
      $pref = $this->GetPreference('active_lib');
      \Captcha\plugins_ops::load_plugin($pref);
      $loaded = \Captcha\plugins_ops::get_loaded();
      
      # in some rare situations we can come up empty mainly with beta or rc releases
      # so we revert to the fresh install default
      if( !isset($loaded[$pref]) )
      {
        $this->SetPreference('active_lib', 'hn_captcha');
        $pref = $this->GetPreference('active_lib');
        \Captcha\plugins_ops::load_plugin($pref);
      }
      
      $this->active_lib = $loaded[$pref]; 
    }
      
    return $this->active_lib;
  }
  
  
  /**
  * Loads the Captchalib objects
  */
  function loadLibs()
  {
    $found = \Captcha\plugins_ops::list_plugins();
 
    foreach ($found as $libname => $fn)
    {
      \Captcha\plugins_ops::load_plugin($libname);      
    }
        
    $loaded = \Captcha\plugins_ops::get_loaded();
    $pref = $this->getPreference('active_lib');
    
    ### this is to be removed later - Start ###
    if (count($found) == 1)
    {
      $this->setActiveLib( current($loaded) );
    }
    elseif(\Captcha\plugins_ops::is_loaded($pref) )
    {
      $this->setActiveLib( $loaded[$pref] );
    } 
    // set the captchalibs property
    //$this->setCaptchaLibs($loaded);
    ### this is to be removed later - End ###
    #! $this->captchalibs is not availabe before this point
  } 
  
  private function _load_active_lib()
  {
    if( !isset($this->active_lib) ) 
    {
      $found = \Captcha\plugins_ops::list_plugins();
      
      $pref = $this->getPreference('active_lib');
      
      if( !empty($pref) )
      {
        \Captcha\plugins_ops::load_plugin($pref);
        $loaded = \Captcha\plugins_ops::get_loaded();
        $this->setActiveLib( $loaded[$pref] );
      }
    }
  } 
  
  /**
  * @return string
  */
  function getClassName()
  {
    return 'CaptchaModule';
  }
  
  /**
  * @return string
  */
  function GetName()
  {
    return 'Captcha';
  }
  
  /*---------------------------------------------------------
     GetFriendlyName()
     This can return any string, preferably a localized name
     of the module. This is the name that's shown in the
     Admin Menus and section pages (if the module has an admin
     component).
  
     See the note on localization at the top of this file.
    ---------------------------------------------------------*/
  function GetFriendlyName()
  {
    return $this->Lang('friendlyname');
  }
  
  
  /*---------------------------------------------------------
     GetVersion()
     This can return any string, preferably a number or
     something that makes sense for designating a version.
     The CMS will use this to identify whether or not
     the installed version of the module is current, and
     the module will use it to figure out how to upgrade
     itself if requested.     
    ---------------------------------------------------------*/
  function GetVersion()
  {
    return '1.0';
  }

  /*---------------------------------------------------------
     GetHelp()
     This returns HTML information on the module.
     Typically, you'll want to include information on how to
     use the module.
     
     See the note on localization at the top of this file.
    ---------------------------------------------------------*/
  function GetHelp()
  {
    return $this->ProcessTemplate('help.tpl');
  }

  /*---------------------------------------------------------
     GetAuthor()
     This returns a string that is presented in the Module
     Admin if you click on the "About" link.
    ---------------------------------------------------------*/
  function GetAuthor()
  {
    return 'Jo Morg';
  }

  /*---------------------------------------------------------
     GetAuthorEmail()
     This returns a string that is presented in the Module
     Admin if you click on the "About" link. It helps users
     of your module get in touch with you to send bug reports,
     questions, cases of beer, and/or large sums of money.
    ---------------------------------------------------------*/
  function GetAuthorEmail()
  {
    return 'jomorg.morg@gmail.com';
  }

  /*---------------------------------------------------------
     GetChangeLog()
     This returns a string that is presented in the module
     Admin if you click on the About link. It helps users
     figure out what's changed between releases.
     See the note on localization at the top of this file.
    ---------------------------------------------------------*/
  function GetChangeLog()
  {
    return $this->ProcessTemplate("changelog.tpl");
  }

  /*---------------------------------------------------------
     IsPluginModule()
     This function returns true or false, depending upon
     whether users can include the module in a page or
     template using a smarty tag of the form
     {cms_module module='Captcha' param1=val param2=val...}
     
     If your module does not get included in pages or
     templates, return "false" here.
    ---------------------------------------------------------*/
  function IsPluginModule()
  {
    return false;
  }

  /*---------------------------------------------------------
     HasAdmin()
     This function returns a boolean value, depending on
     whether your module adds anything to the Admin area of
     the site. For the rest of these comments, I'll be calling
     the admin part of your module the "Admin Panel" for
     want of a better term.
    ---------------------------------------------------------*/
  function HasAdmin()
  {
      return TRUE;
  }
  
  function IsAdminOnly() 
  {
      return FALSE;
  }


  /*---------------------------------------------------------
     GetAdminSection()
     If your module has an Admin Panel, you can specify
     which Admin Section (or top-level Admin Menu) it shows
     up in. This method returns a string to specify that
     section. Valid return values are:

     main        - the Main menu tab.
     content     - the Content menu
     layout      - the Layout menu
     usersgroups - the Users and Groups menu
     extensions  - the Extensions menu (this is the default)
     siteadmin   - the Site Admin menu
     viewsite    - the View Site menu tab
     logout      - the Logout menu tab
     
     Note that if you place your module in the main,
     viewsite, or logout sections, it will show up in the
     menus, but will not be visible in any top-level
     section pages.
    ---------------------------------------------------------*/
  function GetAdminSection()
  {
    return 'extensions';
  }


  /*---------------------------------------------------------
     GetAdminDescription()
     If your module does have an Admin Panel, you
     can have it return a description string that gets shown
     in the Admin Section page that contains the module.
    ---------------------------------------------------------*/
  function GetAdminDescription()
  {
    return $this->Lang('admindescription');
  }


  /*---------------------------------------------------------
     VisibleToAdminUser()
     If your module does have an Admin Panel, you
     can control whether or not it's displayed by the boolean
     that is returned by this method. This is primarily used
     to hide modules from admins who lack permission to use
     them.
     
     Typically, you'll use some permission to set this
     (e.g., $this->CheckPermission('Some Permission'); )
    ---------------------------------------------------------*/
  function VisibleToAdminUser()
  {
    return $this->CheckPermission('Modify Site Preferences');
  }
  

  /*---------------------------------------------------------
  CheckAccess()
  This wrapper function will check against the specified permission,
  and display an error page if the user doesn't have adequate permissions.
  ---------------------------------------------------------*/
  function CheckAccess($id, $params, $returnid, $perm = 'Change Captcha Settings')
  {
    if ( !$this->CheckPermission($perm) )
    {
      $this->DisplayErrorPage($id, $params, $returnid,
                  $this->Lang('accessdenied')); 
      return false;
    }
    
    return true;
  }
  
  /*---------------------------------------------------------
     DisplayErrorPage()
     This is a simple function for generating error pages.
    ---------------------------------------------------------*/
  function DisplayErrorPage($id, &$params, $return_id, $message='')
  {
    if( \Captcha\utils::is_CMS2() )
    {
      # get the $smarty object assigned to the action if possible 
      $smarty = $this->GetActionTemplateObject(); 
      # otherwise get the one created by the cms app even if it creates scope issues
      if( !is_object($smarty) ) $smarty = cmsms()->GetSmarty();      
    }
    # or if we are in cmsms 1.x leave it as it was
    else $smarty = cmsms()->GetSmarty();
    
    $smarty->assign('title_error', $this->Lang('error'));
    $smarty->assign('message', $message);
    
    // Display the populated template
    echo $this->ProcessTemplate('error.tpl');
  }
  


  /*---------------------------------------------------------
     GetDependencies()
     Your module may need another module to already be installed
     before you can install it.
     This method returns a list of those dependencies and
     minimum version numbers that this module requires.
     
     It should return an hash, eg.
     return array('somemodule'=>'1.0', 'othermodule'=>'1.1');
    ---------------------------------------------------------*/
  function GetDependencies()
  {
    return array();
  }

  function HasCapability($capability,$params=array())
  {
    $cap = strtolower($capability);
    if( $cap == 'captcha' ) return true;
    return false;
  }


  /*---------------------------------------------------------
     MinimumCMSVersion()
     Your module may require functions or objects from
     a specific version of CMS Made Simple.
     Ever since version 0.11, you can specify which minimum
     CMS MS version is required for your module, which will
     prevent it from being installed by a version of CMS that
     can't run it.
     
     This method returns a string representing the
     minimum version that this module requires.
     ---------------------------------------------------------*/
  function MinimumCMSVersion()
  {
    return "1.12";
  }

  /*---------------------------------------------------------
     InstallPostMessage()
     After installation, there may be things you want to
     communicate to your admin. This function returns a
     string which will be displayed.
    ---------------------------------------------------------*/
  function InstallPostMessage()
  {
    return $this->Lang('postinstall');
  }

  /*---------------------------------------------------------
     UninstallPostMessage()
     After removing a module, there may be things you want to
     communicate to your admin. This function returns a
     string which will be displayed.
    ---------------------------------------------------------*/
  function UninstallPostMessage()
  {
    return $this->Lang('postuninstall');
  }


  /**
   * UninstallPreMessage()
   * This allows you to display a message along with a Yes/No dialog box. If the user responds
   * in the affirmative to your message, the uninstall will proceed. If they respond in the
   * negative, the uninstall will be canceled. Thus, your message should be of the form
   * "All module data will be deleted. Are you sure you want to uninstall this module?"
   *
   * If you don't want the dialog, have this method return a FALSE, which will cause the
   * module to uninstall immediately if the user clicks the "uninstall" link.
   */
  function UninstallPreMessage()
  {
    return $this->Lang('really_uninstall');
  }
  
  
  /**
  * Returns current CapchaLibName
  * 
  * @since 0.4.7
  * 
  * @author Jo Morg
  */
  function getCaptchaName()
  {
    $this->LoadLibs();
    return $this->getActiveLib()->name;
  }
  
  
  /**
  * checks if the current used Captcha Lib
  * requires an input field
  * @since 0.4.7
  * 
  * @author Jo Morg
  */
  function NeedsInputField()
  {
    return $this->getActiveLib()->NeedsInputField();
  }
  
  function getCaptcha($formid = '')
  {
    $this->_load_active_lib();
    $lib = $this->getActiveLib();

    $options = array();
    foreach ($lib->options as $option)
    {
      $options[$option->getName()] = $this->GetPreference('captchalib_' . $lib->getName() . '_' . $option->getName(), $option->getDefault());
    }

    return $lib->getCaptcha($options);
  }
  
  function CheckCaptcha($input)
  {
    $this->LoadLibs();
    $lib = $this->getActiveLib();
    $options = array();
    foreach ($lib->options as $option)
    {
      $options[$option->getName()] = $this->GetPreference('captchalib_' . $lib->getName() . '_' . $option->getName(), $option->getDefault());
    }
    return $lib->checkCaptcha($input, $options);
  }
  
  function &getAvailableLibs()
  {
    $available = array();
    $libs = $this->getCaptchaLibs(); 

    foreach ($libs as $shortname => $lib) 
    {
      if ($lib->isAvailable())
      {
        $available[$shortname] = $lib;
      }
    }
    
    return $available;
  }
    
  /**
  * Returns a reference to a library object by name
  * @return object
  */
  function getLibByName($name)
  {
    $lib = $this->captchalibs[$name];
    return $lib;
  }
  
  function getNumberOfCachedImages($cache_dir)
  {
    return count($this->getImageFilenames($cache_dir));
  }
  
  /**
  * Returns an array with image filenames inside a given dir
  * @return array
  */
  function getImageFilenames($dir)
  {
    $filenames = array();
    if ($handle = opendir($dir)) 
    {

      /* Loop over the directory. */
      while( ( $file = readdir($handle) ) !== false) 
      {
        if( \Captcha\utils::isImageFilename($file) )
        {
          $filenames[] = $file;
        }
      }
      closedir($handle);
    }
    return ($filenames);
  }
   
} // end of class Captcha
?>