<?php
/**
 * Wiki farm manager
 *
 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author   Etienne MELEARD <etienne.meleard@cru.fr>
 * @desc     Process and renders SOAP server config related requests
 */

// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
if(!defined('DOKU_FARM_PLUGIN')) define('DOKU_FARM_PLUGIN', DOKU_PLUGIN.'farm/');

if(!defined('DOKU_FARMPLUGINLOADED')) define('DOKU_FARMPLUGINLOADED', true);

class dokuwiki_farm_soapconfig {
	var $data = array();
	var $manager = null;
	var $errors = array();
	
	/**
	 * @param $manager object that must handle error(), success(), nicesize(), getLang() ... calls
	 */
	function __construct($manager) {
		$this->manager = & $manager;
	}
	
	/**
	 * Builds a link inside SOAP config tab
	 * @param $opts farm option array
	 * @return url string
	 */
	function wl($opts = array()) {
		return $this->manager->wl('soapconfig', $opts);
	}
	
	/**
	 * Process requests
	 */
	function process() {
		// Parameters check
		if(
			!(isset($this->manager->opt['app']) && isset($this->manager->opt['save'])) &&
			!(isset($this->manager->opt['app']) && isset($this->manager->opt['delete'])) &&
			!(isset($this->manager->opt['new']) && isset($this->manager->opt['save'])) &&
			!isset($this->manager->opt['advancedsave'])
		) return;
		
		// Security check
		if(!checkSecurityToken()) {
			$this->manager->error('system_errors', 'system_badtoken_failure');
			return; // any changes done by post
		}
		
		// Text mode save request
		if(isset($this->manager->opt['advancedsave'])) {
			if(!isset($_POST['soap_config_advanced']) || empty($_POST['soap_config_advanced'])) {
				$this->manager->error('system_errors', 'postparametermissing_failure');
				return;
			}
			if($fp = fopen(DOKU_FARM_PLUGIN.'trusted_apps.php', 'w')) {
				fwrite($fp, '<?php exit(); ?>'."\n".$_POST['soap_config_advanced']);
				fclose($fp);
			}else $this->manager->error('soapconfig_errors', 'soapconfig_save_failure');
			return;
		}
		
		// Trusted application deletion request
		if(isset($this->manager->opt['delete']) && isset($this->manager->opt['app'])) {
			$out = array();
			foreach(explode("\n\n", @file_get_contents(DOKU_FARM_PLUGIN.'trusted_apps.php')) as $p) {
				$appname = '';
				foreach(explode("\n", trim($p)) as $f) if(preg_match('`^name\s*=\s*([^#]+)(#.*)?$`i', trim($f), $m)) $appname = trim($m[1]);
				if($appname != $this->manager->opt['app']) $out[] = $p;
			}
			if($fp = fopen(DOKU_FARM_PLUGIN.'trusted_apps.php', 'w')) {
				fwrite($fp, $out);
				fclose($fp);
				unset($this->manager->opt['app']);
				$this->manager->success('soapconfig_save_success');
			}else $this->manager->error('soapconfig_errors', 'soapconfig_save_failure');
			return;
		}
		
		// Parameters check
		if(
			!isset($_POST['soap_remoteapp_name']) || empty($_POST['soap_remoteapp_name']) || !preg_match('`^[a-z0-9_-]+$`i', $_POST['soap_remoteapp_name']) ||
			!isset($_POST['soap_remoteapp_namecomment']) ||
			!isset($_POST['soap_remoteapp_pwd']) ||
			!isset($_POST['soap_remoteapp_pwdcomment']) ||
			!isset($_POST['soap_remoteapp_serviceallowed']) ||
			!isset($_POST['soap_remoteapp_serviceimposedargs']) ||
			!isset($_POST['soap_remoteapp_servicescomment'])
		) {
			$this->manager->error('system_errors', 'postparametermissing_failure');
			return;
		}
		
		// Basic mode save / add
		
		$name = $_POST['soap_remoteapp_name'];
		$namecomment = str_replace(array("\n", "\r"), '', $_POST['soap_remoteapp_namecomment']);
		
		$pwdh = function_exists('md5') ? md5($_POST['soap_remoteapp_pwd']) : (function_exists('mhash') ? bin2hex(mhash(1, $_POST['soap_remoteapp_pwd'])) : null);
		$pwd = preg_match('`^[0-9abcdef]{32}$`i', $_POST['soap_remoteapp_pwd']) ? strtolower($_POST['soap_remoteapp_pwd']) : $pwdh;
		$pwdclear = str_replace(array("\n", "\r"), '', $_POST['soap_remoteapp_pwd']);
		$pwdcomment = str_replace(array("\n", "\r"), '', $_POST['soap_remoteapp_pwdcomment']);
		
		$allowedcomment = str_replace(array("\n", "\r"), '', $_POST['soap_remoteapp_serviceallowedcomment']);
		
		$block = '# '.$name.' app'."\n";
		$block .= 'name = '.$name.($namecomment != '' ? ' # '.$namecomment : '')."\n";
		$block .= 'pwd = '.$pwd.' '.(($pwdclear != $pwd) || ($pwdcomment != '') ? '# '.($pwd != $pwdclear ? '('.$pwdclear.') ' : '').$pwdcomment : '')."\n";
		$allowed = array();
		include 'soapserver.php';
		foreach(get_class_methods('farmSOAP') as $m) {
			if(strpos($m, 'service_') !== 0) continue;
			$m = substr($m, 8);
			if(isset($_POST['soap_remoteapp_serviceallowed'][$m])) $allowed[] = $m.((isset($_POST['soap_remoteapp_serviceimposedargs'][$m]) && !empty($_POST['soap_remoteapp_serviceimposedargs'][$m])) ? '('.$_POST['soap_remoteapp_serviceimposedargs'][$m].')' : '');
		}
		$block .= 'allowed = '.implode(', ', $allowed).($allowedcomment != '' ? ' # '.$allowedcomment : '');
		
		if(isset($this->manager->opt['new']) && isset($this->manager->opt['save'])) {
			$app = '';
			$file = @file_get_contents(DOKU_FARM_PLUGIN.'trusted_apps.php');
			if(!$file) {
				$this->manager->error('soapconfig_errors', 'soapconfig_corruptedfile_failure');
				return;
			}
			
			if($fp = fopen(DOKU_FARM_PLUGIN.'trusted_apps.php', 'w')) {
				fwrite($fp, $file."\n\n".$block);
				fclose($fp);
				$this->manager->opt['app'] = $name;
				$this->manager->success('soapconfig_save_success');
				return;
			}else $this->manager->error('soapconfig_errors', 'soapconfig_save_failure');
			
		}
		
		if(isset($this->manager->opt['save']) && isset($this->manager->opt['app'])) {
			$out = array();
			foreach(explode("\n\n", @file_get_contents(DOKU_FARM_PLUGIN.'trusted_apps.php')) as $p) {
				$appname = '';
				foreach(explode("\n", trim($p)) as $f) if(preg_match('`^name\s*=\s*([^#]+)(#.*)?$`i', trim($f), $m)) $appname = trim($m[1]);
				if($appname != $this->manager->opt['app']) {
					$out[] = $p;
				}else $out[] = $block;
			}
			if($fp = fopen(DOKU_FARM_PLUGIN.'trusted_apps.php', 'w')) {
				fwrite($fp, implode("\n\n", $out));
				fclose($fp);
				$this->manager->opt['app'] = $name;
				$this->manager->success('soapconfig_save_success');
			}else $this->manager->error('soapconfig_errors', 'soapconfig_save_failure');
			return;
		}
	}
	
	/**
	 * Renders
	 */
	function html() {
		global $ID;
		ptln('<div class="farm_cmd_title">'.$this->manager->getLang('soapconfig_title').'</div>');
		ptln('<div class="farm_cmd_info">'.$this->manager->getLang('soapconfig_info').'</div>');
		ptln($this->manager->getLang('soapconfig_wsdlurl', array($this->manager->conf['farmwebroot'].'farm.wsdl')));
		ptln('<div class="soapapp soapapp_new">');
		ptln('	<a href="'.$this->wl(array('new' => 1)).'" title="'.$this->manager->getLang('soapconfig_newapp').'"><img src="'.DOKU_FARM_WWW.'images/add.png" alt="add remote app" /> '.$this->manager->getLang('soapconfig_newapp').'</a>');
		ptln('	<a href="'.$this->wl(array('advanced' => 1)).'" title="'.$this->manager->getLang('soapconfig_advancededit').'"><img src="'.DOKU_FARM_WWW.'images/txt.png" alt="advanced edit mode" /> '.$this->manager->getLang('soapconfig_advancededit').'</a>');
		ptln('</div>');
		
		if(isset($this->manager->opt['advanced'])) {
				$this->manager->formHead(array('farm_cmd' => 'soapconfig'));
				ptln('<fieldset>');
				ptln('	<textarea rows="20" style="width:100%;overflow:auto;" name="soap_config_advanced">'.preg_replace('`<\?php\s*exit\(\);\s*\?>`', '', @file_get_contents(DOKU_FARM_PLUGIN.'trusted_apps.php')).'</textarea>');
				ptln('</fieldset>');
				ptln('	<fieldset class="save">');
				ptln('		<input type="submit" class="button" name="farm_opt[advancedsave]" value="'.$this->manager->getLang('btn_save').'" />');
				ptln('	</fieldset>');
				ptln('</form>');
		}else{
			$apps = $this->getApps();
			foreach($apps as $a) {
				ptln('<div class="soapapp">');
				ptln('	'.$this->manager->getLang('soapconfig_field_name_name').' : '.$a['name']['value'].' '.($a['name']['comment'] != '' ? ' <i>('.$this->manager->getLang('soapconfig_comment').' : '.$a['name']['comment'].')</i>' : '').'<br />');
				ptln('	'.$this->manager->getLang('soapconfig_field_name_passwordhash').' : '.$a['pwd']['value'].($a['pwd']['comment'] != '' ? ' <i>('.$this->manager->getLang('soapconfig_comment').' : '.$a['pwd']['comment'].')</i>' : '').'<br />');
				ptln('	'.$this->manager->getLang('soapconfig_field_name_allowedservices').' : '.implode(', ', array_keys($a['allowed']['value'])).''.($a['allowed']['comment'] != '' ? ' <i>('.$this->manager->getLang('soapconfig_comment').' : '.$a['allowed']['comment'].')</i>' : '').'<br />');
				ptln('	<a href="'.$this->wl(array('app' => $a['name']['value'])).'#remoteapp_edit" title="'.$this->manager->getLang('soapconfig_editapp').'"><img src="'.DOKU_FARM_WWW.'images/edit.png" alt="edit remote app" /></a>');
				ptln('</div>');
			}
			
			if(isset($this->manager->opt['app']) || isset($this->manager->opt['new'])) {
				ptln('<div class="soapedit">'.$this->manager->getLang('soapconfig_'.(isset($this->manager->opt['app']) ? 'edit' : 'new').'app').'</div>');
				$hf = array('farm_cmd' => 'soapconfig');
				if(isset($this->manager->opt['app'])) $hf['farm_opt[app]'] = $this->manager->opt['app'];
				else $hf['farm_opt[new]'] = 1;
				$a = isset($this->manager->opt['app']) ? $apps[$this->manager->opt['app']] : null;
				ptln('<a name="remoteapp_edit"></a>');
				$this->manager->formHead($hf);
				ptln('<fieldset>');
				ptln('	'.$this->manager->getLang('soapconfig_field_name_name').' : <input type="text" name="soap_remoteapp_name" value="'.($a ? $a['name']['value'] : '').'" /><br />');
				ptln('	'.$this->manager->getLang('soapconfig_comment').' : <input type="text" name="soap_remoteapp_namecomment" value="'.($a ? $a['name']['comment'] : '').'" /><br />');
				ptln('	<small>'.$this->manager->getLang('soapconfig_field_desc_name').'</small>');
				ptln('</fieldset>');
				
				ptln('<fieldset>');
				ptln('	'.$this->manager->getLang('soapconfig_field_name_passwordhash').' : <input type="text" name="soap_remoteapp_pwd" value="'.($a ? $a['pwd']['value'] : '').'" /><br />');
				ptln('	'.$this->manager->getLang('soapconfig_comment').' : <input type="text" name="soap_remoteapp_pwdcomment" value="'.($a ? $a['pwd']['comment'] : '').'" /><br />');
				ptln('	<small>'.$this->manager->getLang('soapconfig_field_desc_passwordhash').'</small><br />');
				if(function_exists('md5') || function_exists('mhash')) {
					ptln('	'.$this->manager->getLang('soapconfig_field_name_passwordhash_change'));
				}else{
					ptln('	'.$this->manager->getLang('soapconfig_field_name_passwordhash_change_nohashfunc'));
				}
				ptln('</fieldset>');
				
				ptln('<fieldset>');
				ptln('	'.$this->manager->getLang('soapconfig_field_name_allowedservices').' : <br />');
				ptln('		<small>'.$this->manager->getLang('soapconfig_field_desc_allowedservice_imposedargs').'</small><br />');
				if(!class_exists('farmSOAP')) include 'soapserver.php';
				foreach(get_class_methods('farmSOAP') as $m) {
					if(strpos($m, 'service_') !== 0) continue;
					$m = substr($m, 8);
					ptln('	<div class="soapapp_service">');
					ptln('		<input type="checkbox" name="soap_remoteapp_serviceallowed['.$m.']" '.($a ? (isset($a['allowed']['value'][$m]) ? 'checked="checked"' : '') : '').' /> '.$m.' :<br />');
					ptln('		<small>'.$this->manager->getLang('soapconfig_field_desc_allowedservice_'.$m).'</small><br />');
					$imp = '';
					if($a) {
						if(isset($a['allowed']['value'][$m])) {
							$imp = $a['allowed']['value'][$m];
							foreach($imp as $k => $v) $imp[$k] = $k.'='.$v;
							$imp = implode(', ', $imp);
						}
					}
					ptln('		'.$this->manager->getLang('soapconfig_field_name_allowedservice_imposedargs').' : <input type="text" name="soap_remoteapp_serviceimposedargs['.$m.']" value="'.$imp.'" /><br />');
					ptln('	</div>');
				}
				ptln('	'.$this->manager->getLang('soapconfig_comment').' : <input type="text" name="soap_remoteapp_servicescomment" value="'.($a ? $a['allowed']['comment'] : '').'" /><br />');
				ptln('	<small>'.$this->manager->getLang('soapconfig_field_desc_allowedservices').'</small>');
				ptln('</fieldset>');
				
				ptln('	<fieldset class="save">');
				ptln('		<input type="submit" class="button" name="farm_opt[save]" value="'.$this->manager->getLang('btn_save').'" />');
				if($a) ptln('		<input type="button" class="button" onclick="if(this.type==\'submit\')return true;if(confirm(\''.$this->manager->getLang('soapconfig_appdelete_sure', $a['name']['value'], 'js').'\')){this.type=\'submit\';this.click();}" name="farm_opt[delete]" value="'.$this->manager->getLang('btn_delete').'" />');
				ptln('	</fieldset>');
				ptln('</form>');
			}
		}
	}
	
	/**
	 * Returns the list of trusted applications
	 * @return array of application descriptors
	 */
	function getApps() {
		$apps = array();
		foreach(explode("\n\n", preg_replace('`<\?php[^?]+\?>\s*`i', '', @file_get_contents(DOKU_FARM_PLUGIN.'trusted_apps.php'))) as $a) {
			$app = array();
			foreach(explode("\n", trim($a)) as $f) {
				if(preg_match('`^(name|pwd)\s*=\s*([^#]+)(#(.*))?$`i', trim($f), $m)) {
					$app[strtolower($m[1])] = array(
						'value' => trim($m[2]),
						'comment' => isset($m[4]) ? trim($m[4]) : ''
					);
				}
				if(preg_match('`^allowed\s*=\s*([^#]+)(#(.*))?$`i', trim($f), $m)) {
					$list = trim($m[1]);
					$app['allowed'] = array(
						'value' => array(),
						'comment' => isset($m[3]) ? trim($m[3]) : ''
					);
					while(preg_match('`^([^(,]+)(\(([^)]+)\))?(\s*,\s*(.*))?$`', $list, $m)) {
						$sname = trim($m[1]);
						$ovr = trim($m[3]);
						$list = isset($m[5]) ? trim($m[5]) : '';
						
						$app['allowed']['value'][$sname] = array();
						if($ovr != '') {
							foreach(array_map('trim', explode(',', $ovr)) as $o) {
								$o = array_map('trim', explode('=', $o));
								if(count($o) == 1) {
									$app['allowed']['value'][$sname][$o[0]] = true;
								}elseif(count($o) == 2) {
									if(preg_match('`^\[[^]]+\]$`', $o[1])) $o[1] = array_map('trim', explode(',', substr($o[1], 1, -1)));
									elseif(preg_match('`^[0-9]+$`', $o[1])) $o[1] = (int)$o[1];
									elseif(preg_match('`^[0-9]+\.[0-9]+$`', $o[1])) $o[1] = (float)$o[1];
									elseif(preg_match('`^(true|false)$`i', $o[1])) $o[1] = (strtolower($o[1]) == 'true');
									elseif(preg_match('`^null$`i', $o[1])) $o[1] = null;
									$app['allowed']['value'][$sname][$o[0]] = $o[1];
								}
							}
						}
					}
				}
			}
			if(isset($app['name']['value']) && $app['name']['value'] != '') $apps[$app['name']['value']] = $app;
		}
		return $apps;
	}
}
?>
