<?php
/**
 * The ServiceBrowser class can generate a listing of class in the services folder 
 * and generate actionscript along with a listing of methods. 
 * It is therefore more complete than 
 * Flash's service browser (although the latter is still supported)
 * 
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 * @copyright (c) 2003 amfphp.org
 * @access private 
 * @package flashservices
 * @subpackage util
 * @author John Cowen
 * @version $Id: ServiceBrowser.php,v 1.23 2005/07/25 01:33:19 pmineault Exp $
 */
 
include_once(AMFPHP_BASE . 'util/Compat.php');

class ServiceBrowser {
	/**
	 * The location of the class to be browsed.
	 * 
	 * @access private 
	 * @var string 
	 */
	var $_classpath;
	/**
	 * The name of the class to be browsed.
	 * 
	 * @access private 
	 * @var string 
	 */
	var $_classname;
	/**
	 * The method to be tested.
	 * 
	 * @access private 
	 * @var string 
	 */
	var $_methodname;
	/**
	 * An instance of the class being browsed.
	 * 
	 * @access private 
	 * @var object 
	 */
	var $_classConstruct;
	/**
	 * Arguments used when tesing a method.
	 * 
	 * @access private 
	 * @var array 
	 */
	var $_arguments;
	/**
	 * Internal array of loaded classes
	 * This needed because of issues with loading multiple files in different folders with
	 * the same class name
	 *
	 * @access private
	 * @var string
	 */
	var $_classes;
	/**
	 * Path to the classes
	 *
	 * @access private
	 * @var string
	 */
	var $_path;
	
	var $phpToAsTypes = array(
		"string" => "String",
		"int" => "Number",
		"float" => "Number",
		"double" => "Number",
		"number" => "Number",
		"boolean" => "Boolean",
		"null" => "null",
		"void" => "Void",
		"undefined" => "null",
		"date" => "Date",
		"array" => "Array",
		"object" => "Object",
		"xml" => "XML");

	/**
	 * Constructor method for the Service Browser class.
	 * We do not use a unified constructor for PHP4 compatibility. (CH)
	 * 
	 * @param $path The path to the services folder.
	 */
	function ServiceBrowser($path, $omit = array()) {
		define("PRODUCTION_SERVER", false);
		$this->_path = $path;
		$this->_omit = $omit;

		if (isset($_GET['methodname'])) {
			$this->_methodname = $_GET['methodname'];
		}
		if (isset($_POST['arguments'])) {
			$this->_arguments = $_POST['arguments'];
		}
		$this->_basedir = getcwd();
	}


	/**
	 * Retrieves the list of services in a folder
	 *
	 * @param string $location The location of the folder containing services
	 *
	 */
	function listServices($dir = "", $suffix = "")
	{
		
		if($dir == "")
		{
			$dir = $this->_path;
		}
		$services = array();
		if(in_array($suffix, $this->_omit)){ return; }
		if ($handle = opendir($dir . $suffix))
		{
			while (false !== ($file = readdir($handle))) 
			{
				chdir(dirname(__FILE__));
				if ($file != "." && $file != "..") 
				{
					if(is_file($dir . $suffix . $file))
					{
						if(strpos($file, '.methodTable') !== FALSE)
						{
							continue;
						}
						$index = strrpos($file, '.');
						$before = substr($file, 0, $index);
						$after = substr($file, $index + 1);
						
						if($after == 'php')
						{
							$loc = "zzz_default";
							if($suffix != "")
							{
								$loc = str_replace('/','.', substr($suffix, 0, -1));
							}
							
							if($services[$loc] == NULL)
							{
								$services[$loc] = array();
							}
							$services[$loc][] = array($before, $suffix);
							//array_push($this->_classes, $before);
						}
						
					}
					elseif(is_dir($dir . $suffix . $file))
					{
						$insideDir = $this->listServices($dir, $suffix . $file . "/");
						if(is_array($insideDir))
						{
							$services = $services + $insideDir;
						}
					}
				}
			}
		}else{
			echo("error");
		}
		closedir($handle);
		return $services;
	}
	 
	/**
	 * Sets the service to be browsed.
	 * 
	 * The classname can be passed or the class filename i.e MyClass or MyClass.php
	 * 
	 * @param string $class The location of the service class file
	 */
	function setService($class) {
		
		$this->_classpath = $class;
		// get classname
		$dot = strrpos($this->_classpath, ".");
		if ($dot === false) {
			// class name was passed
			$trunced = $this->_classpath;
			$this->_classpath .= ".php";
		} else {
			// class filename was passed
			$trunced = substr($this->_classpath, 0, $dot);
		} 
		//echo($trunced);
		$this->_classname = substr(strrchr('/' . $trunced, "/"), 1);
		$path = substr('/' . $trunced, 1 ,strrpos('/' . $trunced, "/"));
		chdir($this->_path . $path);
		ob_start();
		include_once($this->_classname . '.php');
		ob_end_clean();
		
		if (class_exists($this->_classname)) {
			$this->_classConstruct = new $this->_classname(NULL, NULL, NULL, NULL, NULL, NULL, NULL);
			if(isset($this->_classConstruct->methodTable))
			{
				
				return true;
			}
			else
			{
				return false;
			}
		} else {
			return false;
		}
	} 
	function setMethodName($methodName)
	{
		$this->_methodname = $methodName;
	}
	
	/**
	 * Prints out the headers and footers of the method list page and 
	 * prints out the properties of each method through _printMethod.
	 */
	function listMethods() {
		$methods = array();
		$i = 0;
		if(is_array($this->_classConstruct->methodTable))
		{
			foreach ($this->_classConstruct->methodTable as $name => $methodproperties) {
				$methods[$name] = $this->_printMethod($name, $methodproperties, $i);
				$i++;
			}
		}
		return $methods;
	}
	
	/**
	 * Prints out the headers and footers of the method list page and 
	 * prints out the properties of each method through _printMethod.
	 */
	function listMethodsShort() {
		$remoteMethods = array();
		$privateMethods = array();
		if(is_array($this->_classConstruct->methodTable))
		{
			$i = 1;
			$j = count($this->_classConstruct->methodTable);
			foreach ($this->_classConstruct->methodTable as $name => $methodproperties) {
				if($methodproperties['access'] != 'remote')
				{
					$privateMethods[].= "<a href='javascript:testMethod($i,$j)' title='" . $methodproperties['description'] . "'>" . $name. "</a>";
				}
				else
				{
					$remoteMethods[].= "<a href='javascript:testMethod($i,$j)' title='" . $methodproperties['description'] . "'>" . $name. "</a>";
				}
				$i++;
			}
		}
		$ret = "";
		if(count($remoteMethods) > 0)
		{
			$ret .= "<p><b>Remote</b>: " . implode($remoteMethods, ' | ') . "</p>";
		}
		if(count($privateMethods) > 0)
		{
			$ret .= "<p><b>Private</b>: " . implode($privateMethods, ' | ') . "</p>";
		}
		return $ret;
	} 
	
	function generateCode()
	{
		$info = $this->gatherInfo();
		chdir($this->_basedir);
		
		$templates = array();
		if ($handle = opendir('templates'))
		{
			while (false !== ($file = readdir($handle))) 
			{
				chdir(dirname(__FILE__));
				if ($file != "." && $file != "..") 
				{
					if(is_file($this->_basedir . '/templates/' . $file))
					{
						$index = strrpos($file, '.');
						$before = substr($file, 0, $index);
						$after = substr($file, $index + 1);
						
						if($after == 'php')
						{
							include_once($this->_basedir . '/templates/' . $file);
							$templates[$before] = new $before;
						}
					}
				}
			}
		}
		
		function cmp ($a, $b)
		{ 
			 if ($a->priority == $b->priority) return 0;
			 return ($a->priority < $b->priority) ? 1 : -1;
		}
		# the index is the second element of
		# each row
		
		usort($templates, "cmp");
		
		
		
		$section = array();
		foreach($templates as $key => $tpl)
		{
			$key = get_class($tpl);
			$code = $tpl->format($info);
			$text = ('<p>' . "<span style='float:right'><a href='javascript:selectCode(\"$key\");'>Select text</a></span>" . $tpl->description . "</p>");
			$text .= "<textarea class='codex' id='ascode_$key' name='ascode_$key'>" . $code . '</textarea>';
			$section[$key] = array('code' => $text, 'description' => $tpl->description);
		}
		
		return $section;
	}
	
	function gatherInfo()
	{
		if(!isset($this->info))
		{
			$info = array();
			$info['package'] = str_replace("/" , "." , dirname($this->_classpath));
			if($info['package'] == '.')
			{
				$info['package'] = "";
			}
			else
			{
				$info['package'] .= ".";
			}
			$info['class'] = $this->_classname;
			$info['server'] = $_SERVER['SERVER_NAME'];
			
			$auth = false;
			$methods = array();
			
			require_once(AMFPHP_BASE . 'util/MethodTable.php');
			$mt = MethodTable::create(substr($_GET['class'], strrpos('/' . $_GET['class'], '/')) . '.php');
			
			foreach ($this->_classConstruct->methodTable as $name => $props) 
			{
				if($props['access'] == 'remote')
				{
					$typedArgs = "";
					$untypedArgs = "";
					
					if(!isset($props['arguments']))
					{
						$props['arguments'] = $mt[$name]['arguments'];
					}
					foreach($props['arguments'] as $key => $value) {
						if(!is_array($value))
						{
							$arg = ', ' . substr($value . ' -', 0, strpos($value . ' -','-') - 1);
							$typedArgs .= $arg;
							$untypedArgs .= $arg;
						}
						else
						{
							$untypedArgs .= ', ' . $key ;
							if(isset($value['type']))
							{
								if(!isset($this->phpToAsTypes[strtolower($value['type'])]))
								{
									$typedArgs .= ', ' . $key . ':' . $value['type'];
								}
								else
								{
									$typedArgs .= ', ' . $key . ':' . $this->phpToAsTypes[strtolower($value['type'])];
								}
							}
							else
							{
								$typedArgs .= ', ' . $value;
							}
							
						}
					} 

					$typedArgs = substr($typedArgs, 2);
					$untypedArgs = substr($untypedArgs, 2);
					
					$methods[] = array(
						"description" => $props['description'],
						"methodName" => $name,
						"args" => $untypedArgs,
						"typedArgs" => $typedArgs,
					);
					
					if(isset($props['roles']))
					{
						//At least one method requires auth
						$auth = true;
					}
				}
			}
			$info['methods'] = $methods;
			$info['auth'] = $auth;
			$info['gatewayUrl'] = 'http://' . $_SERVER['HTTP_HOST'] . str_replace('//', '/', str_replace("%2F","/",rawurlencode (str_replace('\\', '/', dirname(dirname($_SERVER['PHP_SELF']))))) . '/gateway.php');
			
			$this->info = $info;
			return $info;
		}
		else
		{
			return $this->info;
		}
	}
	
	/**
	 * Prints out the headers and footers of the method testing page and
	 * either prints a form (through _printForm) for the user to enter arguments or, if arguments
	 * are provided, prints the result of the method (through printResult).
	 */
	function testMethod($name) {
		if(isset($this->_classConstruct->methodTable[$name]['arguments']))
		{
			return $this->_classConstruct->methodTable[$name]['arguments'];
		}
		else
		{
			require_once(AMFPHP_BASE . 'util/MethodTable.php');
			$mt = MethodTable::create(substr($_GET['class'], strrpos('/' . $_GET['class'], '/')) . '.php');
			return $mt[$name]['arguments'];
		}
	} 

	/**
	 * Prints out a table containing the method name and its
	 * properties.
	 * 
	 * @param string $name The name of the method.
	 * @param array $methodproperties The method properties, the information in the method table.
	 */
	function _printMethod($name, $methodproperties, $selected) {
		// header - Method name and link
		$method = '<div id="methods">';
		$method .= '<table class="methodTable" style="width:100%" cellspacing="2" cellpadding="3">';
		$method .= '<tr class="header"><td class="header" colspan=2>';
		$method .= $name . ' - ' . substr($methodproperties['description'], 0, 80) . '</td>';
		$method .= '</td></tr>';
		foreach($methodproperties as $property => $value) {
			if (!is_array($value)) {
				$method .= $this->_printMethodProp($property, $value);
			} else {
				$method .= $this->_printArgs($property, $value);
			}
		}
		
		$methodName = $name;
		
		if($methodproperties['access'] == 'remote')
		{
			$arguments = $this->testMethod($name);
			
			$method .= '<form action="' . $_SERVER['PHP_SELF'] . '?class=' . substr($this->_classpath, 0, -4) . '&method=' . $name . '&action=exec" method="POST" id="form">' ."\n";
			$method .= '<tr><td class="formcaption" colspan="2">Arguments</td></tr>'."\n";
			$key = 0;
			foreach($arguments as $key => $name) {
				$method .= '<tr class="caption"><td class="key">' . $name . '</td><td><input class="inputbox" type="text" name="' . $methodName . '_arguments[]" maxlength="65535" value="' . htmlspecialchars(strip($_POST[$methodName . "_arguments"][$key])) . '"/></td></tr>'."\n";
			}
			//$method .= '<tr><td class="formcaption" colspan="2">Optional arguments</td></tr>';
			//for($i = 0; $i < 5; $i++) {
			//    $method .= '<tr class="inputrow"><td class="key">arg' . ($i + 1) . '</td><td><input class="inputbox" type="text" name="' . $methodName . '_arguments[]" maxlength="65535" value="' . htmlspecialchars(strip($_POST[$methodName . "_arguments"][$i + $key + 1])) . '"/></td></tr>'."\n";
			//}
			$method .= '<tr><td class="formcaption" colspan="2">Authentication</td></tr>'."\n";
			$method .='<tr class="inputrow"><td class="key">Username</td><td><input class="inputbox" type="text" name="username" maxlength="65535" value="' . htmlspecialchars(strip($_POST['username'])) . '"/></td></tr>'."\n";
			$method .='<tr class="inputrow"><td class="key">Password</td><td><input class="inputbox" type="text" name="password" maxlength="65535" value="' . htmlspecialchars(strip($_POST['password'])) . '"/></td></tr>'."\n";
			$method .= '<tr class="inputrow"><td colspan="2" class="submitbtn"><input type="submit">'."\n";
			$method .= '</td></tr></form>'."\n";
			
			
		}
		$method .= '</table></div>';
		return $method;
	}
	/**
	 * Prints a row of a table containing the a property of the method table
	 * This method copes with the description, access, roles, instance and
	 * alias entries in the method table. Arguments are printed by _printArgs.
	 * 
	 * @param string $property The name of the property.
	 * @param string $value The value of the property.
	 */
	function _printMethodProp($property, $value) {
		return '<tr class="methodrow"><td class="key">' . nl2br($property) . '</td><td class="value">' . nl2br($value) . '</td></tr>';
	} 
	/**
	 * Prints a row of a table containing the an meta information of
	 * an argument (taken from the method table).
	 * The meta information is printed out in a similar format to the
	 * Flash MX Service Browser.
	 * 
	 * @param string $property The name of the property.
	 * @param string $value The value of the property.
	 */
	function _printArgs($property, $value) {
		$args = '<tr class="argrow"><td class="key">' . $property . '</td><td class="value">';
		if (count($value) == 0) {
			$args .= '<span>[none]</span>';
		} else {
			foreach($value as $subproperty => $subvalue) {
				if(!is_array($subvalue))
				{
					$args .= $subvalue . '<br />';
				}
				else
				{
					$args .= '<pre>' . $subproperty . " => ";
					ob_start();
					print_r($subvalue);
					$args .= ob_get_clean();
					$args .= '</pre>';
				}
			} 
		}
		return $args . '</td></tr>';
	} 
	
	/**
	 * Prints the final result of a tested method
	 *
	 * @param mixed $result The result of the execution of the method.
	 */
	function _printResult($result) {
		echo '<div id="results">';
		echo '<table class="resultstable">';
		echo '<caption class="resultscaption">Output of: ' . $this->_classpath . '</caption>';
		echo '<tr class="resultsrow">';
		if (is_object($result) || is_array($result) || is_resource($result)) {
			echo '<td>';
			echo '<code>';
			print_r($result);
			echo '</code>';
			echo '</td>';
		} else {
			echo '<td><code class="resultstext">' . $result . '</code></td>';
		} 
		echo '</tr>';
		echo '</table>';
		echo '</div>';
	}
	
	function saveCode($type, $location, $overwrite)
	{
		$info = $this->gatherInfo();
		$result = include_once($this->_basedir . '/templates/' . str_replace('..', '', $type) . '.php');
		if(!$result)
		{
			die('<p>Could not include template type</p>');
		}
		
		global $cfg;
		
		$template = new $type;
		$result = $template->save($info, $cfg['CodePath'] . '/' . str_replace('..', '', $location), $overwrite);
		return $result;
	}
}

function makeDirs($strPath) //creates directory tree recursively
{
   return is_dir($strPath) or ( makeDirs(dirname($strPath)) and mkdir($strPath) );
}

function strip($val)
{
	if(get_magic_quotes_gpc())
	{
		return stripslashes($val);
	}
	else
	{
		return $val;
	}
}

?>
