<?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
#-------------------------------------------------------------------------
/**
* 
* AutoLoader
* @version 1.2
* @package CMSMS
* @subpackage Captcha
* 
* based on: UniversalClassLoader by Fabien Potencier <fabien@symfony.com>
* UniversalClassLoader implements a "universal" autoloader for PHP 5.3.
*
* It is able to load classes that use either:
*
*  * The technical interoperability standards for PHP 5.3 namespaces and
*    class names (https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md);
*
*  * The PEAR naming convention for classes (http://pear.php.net/).
*
* Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be
* looked for in a list of locations to ease the vendoring of a sub-set of
* classes for large projects.
*
* Example usage:
*
*     $loader = new UniversalClassLoader();
*
*     // register classes with namespaces
*     $loader->registerNamespaces(array(
*         'Symfony\Component' => __DIR__.'/component',
*         'Symfony'           => __DIR__.'/framework',
*         'Sensio'            => array(__DIR__.'/src', __DIR__.'/vendor'),
*     ));
*
*     // register a library using the PEAR naming convention
*     $loader->registerPrefixes(array(
*         'Swift_' => __DIR__.'/Swift',
*     ));
*
*
*     // to enable searching the include path (e.g. for PEAR packages)
*     $loader->useIncludePath(true);
*
*     // activate the autoloader
*     $loader->register();
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* heavily modified version for Captcha
* @author JoMorg
*
* @api
*/

namespace Captcha;

final class autoloader
{
  private $namespaces             = array();
  private $prefixes               = array();
  private $namespaceFallbacks     = array();
  private $prefixFallbacks        = array();
  private $sufixFallbacks         = array();
  private $file_prefixes          = array();
  private $file_sufixes           = array();
  private $routes                 = array();
  private $useIncludePath         = false;
  
  # deal with dir_filename to dir/filename by splitting to $route and $className
  # $route will hold dir_a/dir_b/dir_c etc... or empty   
  private function _split_PEAR_filesnames($class)
  {
    if (false !== $pos = strrpos($class, '_'))
    {
      $route = substr($class, 0, $pos) . DIRECTORY_SEPARATOR;
      $route = str_replace('_', DIRECTORY_SEPARATOR, $route);
      $className = substr($class, $pos + 1);
    }
    else
    {
      $className = $class; 
      $route = ''; 
    } 
    
    return array($route, $className);
  }
  
  private function _split_class_name_components($class)
  {

    list($namespace, $className) = $this->_split_class_filename($class);
    list($route, $className) = $this->_split_PEAR_filesnames($className); 
    return array($route, $namespace, $className);
  }
  
  /**
  * 
  * @param mixed $class
  */
  private function _get_valid_filenames($class)
  {
    $ret = array();
    
    $ret[] = $class;
    
    # prefixes 
    foreach ($this->file_prefixes as $prefix) 
    {
      $ret[] = $prefix . '.' . $class;
    }
    
    # sufixes    
    foreach ($this->file_sufixes as $sufix)
    {
      $ret[] = $class . '.' . $sufix;
    }
    
    return $ret;
  }
  
  private function _split_class_filename($class)
  {
    $pos = strrpos($class, '\\'); 
    $namespace = substr($class, 0, $pos);
    $className = substr($class, $pos + 1);
    return array($namespace, $className);
  }

  /**
  * Turns on searching the include for class files. Allows easy loading
  * of installed PEAR packages
  *
  * @param Boolean $useIncludePath
  */
  public function useIncludePath($useIncludePath)
  {
    $this->useIncludePath = $useIncludePath;
  }

  /**
  * Can be used to check if the autoloader uses the include path to check
  * for classes.
  *
  * @return Boolean
  */
  public function getUseIncludePath()
  {
    return $this->useIncludePath;
  }

  /**
  * Gets the configured namespaces.
  *
  * @return array A hash with namespaces as keys and directories as values
  */
  public function getNamespaces()
  {
    return $this->namespaces;
  }

  /**
  * Gets the configured class prefixes.
  *
  * @return array A hash with class prefixes as keys and directories as values
  */
  public function getPrefixes()
  {
    return $this->prefixes;
  }
    
  /**
  * Gets the configured class sufixes.
  *
  * @return array A hash with class sufixes as keys and directories as values
  */
  public function getSufixes()
  {
    return $this->sufixes;
  }

  /**
  * Gets the directory(ies) to use as a fallback for namespaces.
  *
  * @return array An array of directories
  */
  public function getNamespaceFallbacks()
  {
    return $this->namespaceFallbacks;
  }

  /**
  * Gets the directory(ies) to use as a fallback for class prefixes.
  *
  * @return array An array of directories
  */
  public function getPrefixFallbacks()
  {
    return $this->prefixFallbacks;
  }

  /**
  * Registers the directory to use as a fallback for namespaces.
  *
  * @param array $dirs An array of directories
  *
  * @api
  */
  public function registerNamespaceFallbacks(array $dirs)
  {
    $this->namespaceFallbacks = $dirs;
  }

  /**
  * Registers a directory to use as a fallback for namespaces.
  *
  * @param string $dir A directory
  */
  public function registerNamespaceFallback($dir)
  {
    $this->namespaceFallbacks[] = $dir;
  }

  /**
  * Registers directories to use as a fallback for class prefixes.
  *
  * @param array $dirs An array of directories
  *
  * @api
  */
  public function registerPrefixFallbacks(array $dirs)
  {
    $this->prefixFallbacks = $dirs;
  }

  /**
  * Registers a directory to use as a fallback for class prefixes.
  *
  * @param string $dir A directory
  */
  public function registerPrefixFallback($dir)
  {
    $this->prefixFallbacks[] = $dir;
  }  
  
  /**
  * Registers an array of namespaces
  *
  * @param array $namespaces An array of namespaces (namespaces as keys and locations as values)
  *
  * @api
  */
  public function registerNamespaces(array $namespaces)
  {
    foreach ($namespaces as $namespace => $locations) 
    {
      $this->namespaces[$namespace] = (array) $locations;
    }
  }

  /**
  * Registers a namespace.
  *
  * @param string       $namespace The namespace
  * @param array|string $paths     The location(s) of the namespace
  *
  * @api
  */
  public function registerNamespace($namespace, $paths)
  {
      $this->namespaces[$namespace] = (array) $paths;
  }

  /**
  * Registers an array of classes using the PEAR naming convention.
  *
  * @param array $classes An array of classes (prefixes as keys and locations as values)
  *
  * @api
  */
  public function registerPrefixes(array $classes)
  {
    foreach ($classes as $prefix => $locations) 
    {
      $this->prefixes[$prefix] = (array) $locations;
    }
  }

  /**
  * Registers a set of classes using the PEAR naming convention.
  *
  * @param string       $prefix The classes prefix
  * @param array|string $paths  The location(s) of the classes
  *
  * @api
  */
  public function registerPrefix($prefix, $paths)
  {
    $this->prefixes[$prefix] = (array) $paths;
  }
    
  ### NEW #######################################################################
  
  /**
  * Registers an array of prefixes to file classes like <prefix>.<myclassname>.php.
  *
  * @param array $classes An array of prefixes (prefixes as keys and locations as values)
  *
  * @api
  */
  public function registerFilePrefixes(array $prefixes)
  {
    foreach ($prefixes as $prefix => $locations) 
    {
      if (!in_array($prefix, $this->file_prefixes) )
      {
        $this->file_prefixes[] = $prefix;
      }
      
      foreach ($locations as $location)
      {
         if (!in_array($location, $this->routes) )
         {
           $this->routes[] = $location;  
         }
      }
    }
  }

  /**
  * Registers a set of prefixes to file classes like <prefix>.<myclassname>.php.
  *
  * @param string       $prefix The files prefix
  * @param array|string $paths  The location(s) of the classes
  *
  * @api
  */
  public function registerFilePrefix($prefix, array $paths)
  {
    if (!in_array($prefix, $this->file_prefixes) )
    {
      $this->file_prefixes[] = $prefix;
    }
    
    foreach ($paths as $path)
    {
       if (!in_array($path, $this->routes) )
       {
         $this->routes[] = $path;  
       }
    }
    
  }
     
  /* Registers an array of sufixes to file classes like <myclassname>.<sufix>.php.
  *
  * @param array $classes An array of sufixes (sufixes as keys and locations as values)
  *
  * @api
  */
  public function registerFileSufixes(array $sufixes)
  {
      if (!in_array($sufix, $this->file_sufixes) )
      {
        $this->file_sufixes[] = $sufix;
      }
      
      foreach ($locations as $location)
      {
         if (!in_array($location, $this->routes) )
         {
           $this->routes[] = $location;  
         }
      }
  }

  /**
  * Registers a set of sufixes to file classes like <myclassname>.<sufix>.php.
  *
  * @param string       $sufix The files prefix
  * @param array|string $paths  The location(s) of the classes
  *
  * @api
  */
  public function registerFileSufix($sufix, array $paths)
  {
    if (!in_array($sufix, $this->file_sufixes) )
    {
      $this->file_sufixes[] = $sufix;
    }
    
    foreach ($paths as $path)
    {
       if (!in_array($path, $this->routes) )
       {
         $this->routes[] = $path;  
       }
    }
  }
    
      
  /* Registers an array of routes to file classes.
  *
  * @param $path
  *
  * @api
  */
  public function registerRoutes(array $routes)
  {
    foreach ($sufixes as $sufix => $locations) 
    {
      $this->file_sufixes[$sufix] = (array) $locations;
    }
  }

  /**
  * Registers an array of routes to file classes.
  *
  * @param string $path  The location of the classes
  *
  * @api
  */
  public function registerFileRoute($path)
  {
    $this->routes[] = $path;
  }
    
  ### END of NEW ################################################################################

  /**
  * Registers this instance as an autoloader.
  *
  * @param Boolean $prepend Whether to prepend the autoloader or not
  *
  * @api
  */
  public function register($prepend = false)
  {
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
  }

  /**
  * Loads the given class or interface.
  *
  * @param string $class The name of the class
  *
  * @return Boolean|null True, if loaded
  */
  public function loadClass($class)
  {
    if ($file = $this->findFile($class)) 
    {
      require_once $file;
      //require $file;

      return true;
    }
  }

  /**
  * Finds the path to the file where the class is defined.
  *
  * @param string $class The name of the class
  *
  * @return string|null The path, if found
  */
  public function findFile($class)
  {
    
    if (false !== $pos = strrpos($class, '\\')) 
    {
      # get route, namespace and className from class
      list($route, $namespace, $className) = $this->_split_class_name_components($class);
      
      # get all the valid sufix/prefix combinations
      $classNames = $this->_get_valid_filenames($className);
      
      foreach ($this->namespaces as $ns => $dirs) 
      {
        if (0 !== strpos($namespace, $ns)) 
        {
          continue;
        }

        foreach ($dirs as $dir) 
        {
          foreach ($classNames as $Class)
          {            
            $file = $dir . DIRECTORY_SEPARATOR . $Class . '.php';
     
            if (is_file($file)) 
            {
              return $file;
            }
            
            $file = $dir . DIRECTORY_SEPARATOR . $route . $Class .'.php';
           
            if (is_file($file)) 
            {
              return $file;
            }
            
          }
          
        }
      }
      

      foreach ($this->namespaceFallbacks as $dir) 
      {
        foreach ($classNames as $Class)
        { 
          $file = $dir . DIRECTORY_SEPARATOR . $Class . '.php';
          
          if (is_file($file)) 
          {
            return $file;
          }          
        
          $file = $dir . DIRECTORY_SEPARATOR . $route . $Class . '.php';
          
          if (is_file($file)) 
          {
            return $file;
          }
        }
      }

    } 
    else 
    {
      // PEAR-like class name
      $normalizedClass = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
      
      foreach ($this->prefixes as $prefix => $dirs) 
      {
        if (0 !== strpos($class, $prefix)) 
        {
          continue;
        }

          foreach ($dirs as $dir) 
          {
            $file = $dir . DIRECTORY_SEPARATOR . $normalizedClass;
            
            if (is_file($file)) 
            {
              return $file;
            }
          }
      }
      
      foreach ($this->prefixFallbacks as $dir) 
      {
        $file = $dir . DIRECTORY_SEPARATOR . $normalizedClass;
        
        if (is_file($file)) 
        {
          return $file;
        }
      }
    }
      
    # not found yet let's try without splitting by the '_'
    list($namespace, $className) = $this->_split_class_filename($class);
    $normalizedClass = $className . '.php';
    $classNames = $this->_get_valid_filenames($className);

    # all else failed let's try some custom combos
    foreach ($this->prefixFallbacks as $dir) 
    {
      $file = $dir . DIRECTORY_SEPARATOR . $normalizedClass;
      
      if (is_file($file)) 
      {
        return $file;
      }
    }
    
    foreach ($this->routes as $dir)
    {
      
      foreach($classNames as $classname)
      { 
        $file = $dir . DIRECTORY_SEPARATOR . $classname . '.php';
                
        if (is_file($file)) 
        {
          return $file;
        }
          
      }
       
    }
    
    if ($this->useIncludePath && $file = stream_resolve_include_path($normalizedClass)) 
    {
      return $file;
    }
    
    foreach($classNames as $classname)
    {
      if ($this->useIncludePath && $file = stream_resolve_include_path($classname)) 
      {
        return $file;
      }
    }
          
  }
}
?>