<?php
// $Header: /cvsroot/phpldapadmin/phpldapadmin/hooks.php,v 1.6 2005/04/15 13:16:59 wurley Exp $

/**
 * Functions related to hooks management.
 *
 * @author Benjamin Drieu <benjamin.drieu@fr.alcove.com> and Alcôve
 * @package phpLDAPadmin
 *
 * 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.
 *
 * 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
 */

if ( ! isset($hooks) ) {
  /** This variable contains all hooks that are defined.  */
  $hooks = array();
}

/**
 * Compares two arrays by numerically comparing their 'prority'
 * value.  Standard `cmp-like' function.
 *
 * @param a First element to compare.
 * @param b Second element to compare.
 *
 * @return -1 if priority of first element is smaller than second
 * element priority.  1 otherwise.
 */
function sort_array_by_priority ( $a, $b ) {
  return ( ( $a['priority'] < $b['priority'] ) ? -1 : 1 );
}

/**
 * Runs procedures attached to a hook.
 *
 * @param hook_name	Name of hook to run.
 * @param args		Array of optional arguments set by
 *			phpldapadmin.  It is normally in a form known
 *			by call_user_func_array() :
 * <pre>[ 'server_id' => 0,
 *   'dn' => 'uid=epoussa,ou=tech,o=corp,o=fr' ]</pre>
 *
 * @return true if all procedures returned true, false otherwise.
 */
function run_hook ( $hook_name, $args ) {
  global $hooks;

  $debug = 0;
	syslog_debug ( "Running hook $hook_name." );

  if ( ! array_key_exists ( $hook_name, $hooks ) ) {
      syslog_notice ( "Hook '$hook_name' not defined !\n" );
      return true;
  }

  unset ( $rollbacks );
  $rollbacks = array ();
  reset ( $hooks[$hook_name] );

  /** Execution of procedures attached is done using a numeric order
   * since all procedures have been attached to the hook with a
   * numerical weight. */
  while ( list ( $key, $hook ) = each ( $hooks[$hook_name] ) ) {
      array_push ( $rollbacks, $hook['rollback_function'] );
	syslog_debug ( "Calling " . $hook['hook_function'] . "\n" );
      $result = call_user_func_array ( $hook['hook_function'], $args );
      syslog_notice ( "Called " . $hook['hook_function'] . "\n" );

      /** If a procedure fails, its optional rollback is executed with
       * the same arguments.  After that, all rollbacks from
       * previously executed procedures are executed in the reverse
       * order.  */
      if ( $result != true ) {
		syslog_debug ( "Function " . $hook['hook_function'] . " returned $result\n" );

	  while ( $rollbacks ) {
	      $rollback = array_pop ( $rollbacks );

	      if ( $rollback != false ) {
			syslog_debug ( "Executing rollback $rollback\n" );
		  call_user_func_array ( $rollback, $args );
		}
	    }

	  return false;
      }
  }

  return true;
}

/**
 * Adds a procedure to a hook for later execution.
 *
 * @param hook_name		Name of the hook.
 * @param priority		Numeric priority.  Lowest means
 *				procedure will be executed before.
 * @param hook_function		Name of the php function called upon
 *				hook trigger.
 * @param rollback_function	Name of the php rollback function
 *				called upon failure.
 */
function add_hook ( $hook_name, $priority, $hook_function, $rollback_function ) {
  global $hooks;

  if ( ! array_key_exists ( $hook_name, $hooks ) ) {
      $hooks[$hook_name] = array();
  }

  array_push ( $hooks[$hook_name], array ( 'priority' => $priority, 'hook_function' => $hook_function,
       'rollback_function' => $rollback_function ) );

  uasort ( $hooks[$hook_name], "sort_array_by_priority" );
}

/**
 * Removes a procedure from a hook, based on a filter.
 *
 * @param hook_name		Name of the hook.
 * @param priority		Numeric priority.  If set, all
 *				procedures of that priority will be
 *				removed.
 * @param hook_function		Name of the procedure function.  If
 *				set, all procedures that call this
 *				function will be removed.
 * @param rollback_function	Name of the php rollback function
 *				called upon failure.  If set, all
 *				procedures that call this function
 *				as a rollback will be removed.
 */
function remove_hook ( $hook_name, $priority, $hook_function, $rollback_function ) {
  global $hooks;

  if ( array_key_exists ( $hook_name, $hooks ) ) {
      reset ( $hooks[$hook_name] );

      while ( list ( $key, $hook ) = each ( $hooks[$hook_name] ) ) {
	  if ( ( $priority >= 0 && $priority == $hook['priority'] ) ||
	       ( $hook_function && $hook_function == $hook['hook_function'] ) ||
	       ( $rollback_function && $rollback_function == $hook['rollback_function'] ) ) {
	      unset ( $hooks[$hook_name][$key] );
	  }

      }
  }
}

/**
 * Removes all procedures from a hook.
 *
 * @param hook_name	Name of hook to clear.
 */
function clear_hooks ( $hook_name )      {
  global $hooks;

  if ( array_key_exists ( $hook_name, $hooks ) ) {
      unset ( $hooks[$hook_name] );
  }
}

/* Evaluating user-made hooks */
if ( is_dir('hooks') ) {
  $dir = dir ('hooks');

  while (false !== ($entry = $dir -> read() ) ) {
      if ( is_file ("hooks/$entry") and eregi ('php[0-9]?$', $entry) ) {
	  require_once "hooks/$entry";
      }
  }

  $dir -> close();
}

?>
