Current File : /home/aventura/www/site/wp-content/plugins/victheme_core/wordpress/models/hooks.php
<?php
/**
 * Models for creating factory for hookable
 * system.
 *
 * @author jason.xie@victheme.com
 */
class VTCore_Wordpress_Models_Hooks
implements VTCore_Wordpress_Interfaces_Factory {


  protected $classes = array();
  protected $registry = array();
  protected $overloaderPrefix = array();
  protected $loaded = array();
  protected $hookFunction;
  protected $updateCache = false;
  protected $classMap = array();
  protected $activePrefix = false;
  protected $activeHookBlock = array();

  protected $hooks = array();


  /**
   * Construct method
   */
  public function __construct() {

    // Check if we should bypass cache
    $this->maybeByPassCache();

    // Load cache
    $this->loadCache();

  }



  /**
   * Method for checking if class should bypass cache
   * @return VTCore_Wordpress_Factory_Layout
   */
  public function maybeByPassCache() {
    // Wordpress on debug mode
    if ((defined('WP_DEBUG') && WP_DEBUG)
        || (defined('VTCORE_CLEAR_CACHE') && VTCORE_CLEAR_CACHE)) {

      $this->clearCache();
    }

    return $this;
  }


  /**
   * Method for loading from cache
   * @return VTCore_Wordpress_Factory_Layout
   */
  public function loadCache() {
    $this->classes = get_transient('vtcore_hook_classes_registry_' . $this->hookFunction);
    $this->classMap = get_transient('vtcore_autoloader_maps');
    return $this;
  }


  /**
   * Method for setting up class cache
   * @return $this
   */
  public function setCache() {
    set_transient('vtcore_hook_classes_registry_' . $this->hookFunction, $this->classes, VTCore_Wordpress_Init::getFactory('coreConfig')->get('cachetime') * HOUR_IN_SECONDS);
    return $this;
  }


  /**
   * Method for clearing cached elements
   * @return VTCore_Wordpress_Factory_Layout
   */
  public function clearCache() {
    delete_transient('vtcore_hook_classes_registry_' . $this->hookFunction);
    return $this;
  }


  /**
   * Use VTCore Stored PHP class map to determine if the
   * class string is a valid class
   *
   * @param $class
   * @return bool
   */
  public function maybeMapped($class) {
    return ((array) $this->classMap === $this->classMap && isset($this->classMap[$class]));
  }


  /**
   * Register the hook into registry
   * @param bool $resetActivePrefix
   */
  public function register($resetActivePrefix = true) {

    if (!empty($this->activePrefix)) {
      $this->registerFromActiveBlock();
    }

    else {
      $this->detectAndRegister();
    }

    if ($resetActivePrefix == true) {
      $this->activePrefix = false;
      $this->activeHookBlock = array();
    }

  }

  /**
   * Method for registering hook from activePrefix and activeBlock
   * instead of looping for all possible match.
   *
   * This can only works if either :
   *    - User register the prefix with $setActive = true
   *    - User register the hooks immediatelly
   *    - User invoke the register() imeediatelly
   *
   */
  protected function registerFromActiveBlock() {

    $this->updateCache = false;

    foreach ($this->activeHookBlock as $registered) {

      $name = $this->activePrefix . str_replace(array('_','-'), array('__', '__'), ucfirst($registered));
      $found = isset($this->classes[$name]) ? $this->classes[$name] : false;

      if (empty($found)) {

        if (class_exists($name, true)) {
          $object = new $name;
          $found = array(
            'hook' => $registered,
            'class' => $name,
            'weight' => $object->getWeight(),
            'argument' => $object->getArgument(),
          );
        }
        else {
          $this->classes[$name] = false;
        }

        $this->updateCache = true;
      }

      if ($found) {
        $this->classes[$name] = $found;
        $this->updateCache = true;
        $this->registerHook($found);
      }
    }

    if ($this->updateCache) {
      $this->setCache();
    }

    return $this;
  }

  /**
   * Method for detecting the correct class per prefix + hook name
   *
   * This is slow and will only be fired if user didnt define
   * active prefix when registering the prefix and / or via setActivePrefix();
   *
   */
  protected function detectAndRegister() {
    foreach ($this->registry as $registered) {
      foreach ($this->overloaderPrefix as $prefix) {

        // Stop too much looping early
        $class = $prefix . str_replace(array('_','-'), array('__', '__'), ucfirst($registered));

        $name = '';

        // Dont bother to proceed, cache already mark this class to non-existant
        if (isset($this->classes[$class])
            && $this->classes[$class] == false) {
          continue;
        }

        if (isset($this->classes[$class])) {
          if ($this->classes[$class]) {
            $name = $class;
          }
        }
        elseif ($this->maybeMapped($class)) {
          $name = $class;

          $object = new $name;
          $this->classes[$class] = array(
            'hook' => $registered,
            'class' => $name,
            'weight' => $object->getWeight(),
            'argument' => $object->getArgument(),
          );

          $this->updateCache = true;
        }
        else {

          if (class_exists($class)) {
            $name = $class;

            $object = new $name;
            $this->classes[$class] = array(
              'hook' => $registered,
              'class' => $name,
              'weight' => $object->getWeight(),
              'argument' => $object->getArgument(),
            );

          }
          else {
            $this->classes[$class] = false;
          }

          $this->updateCache = true;
        }

        // Prevent double loading
        if (empty($name)) {
          continue;
        }

        // Dont double load
        if (isset($this->loaded[$name])) {
          continue;
        }

        // Register the hook
        if (!empty($this->classes[$class])) {
          $this->registerHook($this->classes[$class]);
        }

      }
    }

    if ($this->updateCache) {
      $this->setCache();
    }

    return $this;
  }


  /**
   * Magic method, this object will call the magic
   * class name as method, we redirect them to the
   * correct class here.
   *
   * @param $class
   * @param $args
   * @return mixed|string
   */
  public function __call($class, $args) {

    $loaded = $this->getLoaded($class);
    $value = '';

    if (isset($args[1])) {
      $value = $args[1];
    }

    if ($loaded) {
      if (!isset($loaded['object'])) {
        $object = new $loaded['class'];
        $this->loaded[$class]['object'] = $object;
      }
      else {
        $object = $loaded['object'];
      }
      $value = call_user_func_array(array($object, 'hook'), $args);
    }

    return $value;
  }


  /**
   * Logic for promise the hook
   * @param $name
   * @param $registered
   * @return $this
   */
  protected function registerHook($arguments) {

    if (isset($arguments['class'])
        && isset($arguments['hook'])
        && isset($arguments['weight'])
        && isset($arguments['argument'])) {

      $function = $this->hookFunction;
      $function($arguments['hook'], array(
        $this,
        $arguments['class']
      ), $arguments['weight'], $arguments['argument']);

      $this->addLoaded($arguments['class'], $arguments);
    }

    return $this;
  }


  /**
   * Method for checking if the action is already
   * loaded into Wordpress or not.
   */
  public function checkLoaded($name) {
    return isset($this->loaded[$name]);
  }



  /**
   * Method for adding action class name
   * into loaded array database
   */
  protected function addLoaded($name, array $context) {
    if (!isset($this->loaded[$name])) {
      $this->loaded[$name] = $context;
    }

    return $this;
  }


  protected function getLoaded($name) {
    return isset($this->loaded[$name]) ? $this->loaded[$name] : false;
  }

  /**
   * Method for adding a new action into the
   * action array database.
   */
  public function addHook($hook) {

    if (!in_array($hook, $this->registry)) {
      $this->registry[] = $hook;
    }

    if (!empty($this->activePrefix)) {
      $this->activeHookBlock[] = $hook;
    }

    return $this;
  }


  /**
   * Registering multiple actions at once
   */
  public function addHooks(array $hooks) {
    $this->registry = array_merge($this->registry, $hooks);
    if (!empty($this->activePrefix)) {
      $this->activeHookBlock = $hooks;
    }
    return $this;
  }


  /**
   * Method for removing action
   * This method must be invoked after register()
   * and valid child action class name must be
   * passed as the argument
   *
   * @param string $name
   */
  public function removeHook($name) {
    if (isset($this->loaded[$name])) {

      $hook = $weight = $hash = $argument = '';
      extract($this->loaded[$name]);

      if (isset($GLOBALS['wp_filter'][$hook][$weight][$hash])) {
        unset($GLOBALS['wp_filter'][$hook][$weight][$hash]);

        if (empty($GLOBALS['wp_filter'][$hook][$weight])) {
          unset($GLOBALS['wp_filter'][$hook][$weight]);
        }

        if (empty($GLOBALS['wp_filter'][$hook])) {
          $GLOBALS['wp_filter'][$hook] = array();
        }

        unset($GLOBALS['merged_filters'][$hook]);
      }
    }

    return $this;
  }


  /**
   * Method for adding a new class prefix so
   * the autoloader can pickup and invoke the
   * class when this class is trying to add
   * the action into wordpress
   *
   * @performance
   * Use setActive = false to just register the prefix
   * without setting them to active, if after addPrefix
   * you invoke register() immediatelly use setActive = true
   * to minimize recursion and performance lost.
   */
  public function addPrefix($prefix, $setActive = true) {
    if (!in_array($prefix, $this->overloaderPrefix)) {
      $this->overloaderPrefix[] = $prefix;
    }

    if ($setActive) {
      $this->activePrefix = $prefix;
    }
    return $this;
  }
}