1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8require_once 'lib/ointegratelib.php';
9require_once 'soap/soaplib.php';
10require_once 'soap/wsdllib.php';
11
12/**
13 * Tiki_Webservice
14 *
15 */
16class Tiki_Webservice
17{
18	private $name;
19	public $url;
20	public $body;
21	public $operation;
22	public $wstype;
23	public $schemaVersion;
24	public $schemaDocumentation;
25	public $allowCookies;
26
27	private $templates = [];
28	private $all = false;
29
30	/**
31	 * @param $name
32	 * @return Tiki_Webservice
33	 */
34	public static function create($name)
35	{
36		if (! ctype_alpha($name) || self::getService($name)) {
37			return null;
38		}
39
40		$ws = new self;
41		$ws->name = strtolower($name);
42
43		return $ws;
44	}
45
46	/**
47	 * @param $name
48	 * @return Tiki_Webservice
49	 */
50	public static function getService($name)
51	{
52		$name = strtolower($name);
53
54		global $tikilib;
55		$result = $tikilib->query(
56			"SELECT url, operation, wstype, body, schema_version, schema_documentation FROM tiki_webservice WHERE service = ?",
57			[ $name ]
58		);
59
60		while ($row = $result->fetchRow()) {
61			$service = new self;
62
63			$service->name = $name;
64			$service->url = $row['url'];
65			$service->body = $row['body'];
66			$service->operation = $row['operation'];
67			$service->wstype = $row['wstype'];
68			$service->schemaVersion = $row['schema_version'];
69			$service->schemaDocumentation = $row['schema_documentation'];
70
71			return $service;
72		}
73	}
74
75	/**
76	 * @return array
77	 */
78	public static function getTypes()
79	{
80		return ['REST', 'SOAP'];
81	}
82
83	/**
84	 * @return array
85	 */
86	public static function getList()
87	{
88		global $tikilib;
89
90		$result = $tikilib->query("SELECT service FROM tiki_webservice ORDER BY service");
91		$list = [];
92
93		while ($row = $result->fetchRow()) {
94			$list[] = $row['service'];
95		}
96
97		return $list;
98	}
99
100	function save()
101	{
102		global $tikilib;
103		$tikilib->query("DELETE FROM tiki_webservice WHERE service = ?", [ $this->name ]);
104
105		$tikilib->query(
106			"INSERT INTO tiki_webservice (service, url, operation, wstype, body, schema_version, schema_documentation) VALUES(?,?,?,?,?,?,?)",
107			[
108				$this->name,
109				$this->url,
110				$this->operation,
111				$this->wstype,
112				$this->body,
113				$this->schemaVersion,
114				$this->schemaDocumentation,
115			]
116		);
117	}
118
119	function delete()
120	{
121		global $tikilib;
122		$tikilib->query("DELETE FROM tiki_webservice WHERE service = ?", [ $this->name ]);
123		$tikilib->query("DELETE FROM tiki_webservice_template WHERE service = ?", [ $this->name ]);
124	}
125
126	/**
127	 * @param $newName
128	 * @return $this|null
129	 */
130	function rename($newName)
131	{
132		$tiki_webservice = TikiDb::get()->table('tiki_webservice');
133		if (ctype_alpha($newName) && $tiki_webservice->fetchCount(['service' => $newName]) == 0) {
134			TikiDb::get()->table('tiki_webservice_template')->updateMultiple(
135				['service' => $newName,],
136				['service' => $this->name]
137			);
138			$tiki_webservice->update(['service' => $newName,], ['service' => $this->name]);
139			$this->name = $newName;
140			return $this;
141		} else {
142			return null;
143		}
144	}
145
146	/**
147	 * @return array
148	 */
149	function getParameters()
150	{
151		global $wsdllib;
152
153		switch ($this->wstype) {
154			case 'SOAP':
155				return $wsdllib->getParametersNames($this->url, $this->operation);
156
157			case 'REST':
158			default:
159				if (preg_match_all("/%(\w+)%/", $this->url . ' ' . $this->body, $matches, PREG_PATTERN_ORDER)) {
160					return array_diff($matches[1], [ 'service', 'template' ]);
161				} else {
162					return [];
163				}
164		}
165	}
166
167	/**
168	 * @param $params
169	 * @return array
170	 */
171	function getParameterMap($params)
172	{
173		$parameters = [];
174
175		foreach ($this->getParameters() as $key => $name) {
176			if (isset($params[$name])) {
177				$parameters[$name] = $params[$name];
178			} else {
179				$parameters[$name] = '';
180			}
181		}
182
183		return $parameters;
184	}
185
186	/*
187	*	If fullResponse = true, "out" parameters from .NET calls are included in the response.
188	*	If false, only the <request>Response part of the reply is included.
189	*	fullResponse has no effect for REST calls
190	*/
191	/**
192	 * @param $params
193	 * @param bool $fullReponse
194	 * @return bool|OIntegrate_Response
195	 */
196	function performRequest($params, $fullReponse = false, $clearCache = false)
197	{
198		global $soaplib, $prefs;
199
200		$built = $this->url;
201		$builtBody = $this->body;
202
203		$map = $this->getParameterMap($params);
204
205		if ($built) {
206			switch ($this->wstype) {
207				case 'SOAP':
208					if (! empty($this->operation)) {
209						$options = [ 'encoding' => 'UTF-8' ];
210
211						if ($prefs['use_proxy'] == 'y' && ! strpos($built, 'localhost')) {
212							$options['proxy_host'] = $prefs['proxy_host'];
213							$options['proxy_port'] = $prefs['proxy_port'];
214						}
215
216						$response = new OIntegrate_Response();
217						$soaplib->allowCookies = $this->allowCookies;
218						try {
219							$response->data = $soaplib->performRequest($built, $this->operation, $map, $options, $fullReponse);
220						} catch (Exception $e) {
221							Feedback::error(tr('Webservice error on %0 request "%1"', $this->wstype, $this->url)
222								. '<br>' . $e->getMessage());
223						}
224
225						return $response;
226					}
227
228					return false;
229
230				case 'REST':
231				default:
232					foreach ($map as $name => $value) {
233						$built = str_replace("%$name%", urlencode($value), $built);
234						$builtBody = str_replace("%$name%", urlencode($value), $builtBody);
235					}
236
237					$ointegrate = new OIntegrate;
238					$ointegrate->addAcceptTemplate('smarty', 'tikiwiki');
239					$ointegrate->addAcceptTemplate('smarty', 'html');
240					$ointegrate->addAcceptTemplate('javascript', 'html');
241
242					if ($this->schemaVersion) {
243						$ointegrate->addSchemaVersion($this->schemaVersion);
244					}
245
246					try {
247						$response = $ointegrate->performRequest($built, $builtBody, $clearCache);
248					} catch (Exception $e) {
249						Feedback::error(tr('Webservice error on %0 request "%1"', $this->wstype, $this->url)
250						. '<br>' . $e->getMessage());
251					}
252
253					return $response;
254			}
255		}
256	}
257
258	/**
259	 * @param $name
260	 * @return Tiki_Webservice_Template
261	 */
262	function addTemplate($name)
263	{
264		if (! ctype_alpha($name) || empty($name)) {
265			return;
266		}
267
268		$template = new Tiki_Webservice_Template;
269		$template->webservice = $this;
270		$template->name = strtolower($name);
271
272		$this->templates[$name] = $template;
273
274		return $template;
275	}
276
277	/**
278	 * @param $name
279	 */
280	function removeTemplate($name)
281	{
282		global $tikilib;
283
284		$tikilib->query("DELETE FROM tiki_webservice_template WHERE service = ? AND template = ?", [ $this->name, $name ]);
285	}
286
287	/**
288	 * @return array
289	 */
290	function getTemplates()
291	{
292		if ($this->all) {
293			return $this->templates;
294		}
295
296		global $tikilib;
297		$result = $tikilib->query(
298			"SELECT template, last_modif, engine, output, content FROM tiki_webservice_template WHERE service = ?",
299			[ $this->name ]
300		);
301
302		while ($row = $result->fetchRow()) {
303			$template = new Tiki_Webservice_Template;
304			$template->webservice = $this;
305			$template->name = $row['template'];
306			$template->lastModif = $row['last_modif'];
307			$template->engine = $row['engine'];
308			$template->output = $row['output'];
309			$template->content = $row['content'];
310
311			$this->templates[$template->name] = $template;
312		}
313
314		$this->all = true;
315		return $this->templates;
316	}
317
318	/**
319	 * @param $name
320	 * @return Tiki_Webservice_Template
321	 */
322	function getTemplate($name)
323	{
324		if (isset($this->templates[$name])) {
325			return $this->templates[$name];
326		}
327
328		global $tikilib;
329
330		$result = $tikilib->query(
331			"SELECT last_modif, engine, output, content FROM tiki_webservice_template WHERE service = ? AND template = ?",
332			[ $this->name, $name ]
333		);
334
335		while ($row = $result->fetchRow()) {
336			$template = new Tiki_Webservice_Template;
337			$template->webservice = $this;
338			$template->name = $name;
339			$template->lastModif = $row['last_modif'];
340			$template->engine = $row['engine'];
341			$template->output = $row['output'];
342			$template->content = $row['content'];
343
344			$this->templates[$name] = $template;
345			return $template;
346		}
347	}
348
349	function getName()
350	{
351		return $this->name;
352	}
353}
354
355
356/**
357 * Tiki_Webservice_Template
358 *
359 */
360class Tiki_Webservice_Template
361{
362	public $webservice;
363	public $name;
364	public $engine;
365	public $output;
366	public $content;
367	public $lastModif;
368
369	function save()
370	{
371		global $tikilib;
372
373		$tikilib->query(
374			"DELETE FROM tiki_webservice_template WHERE service = ? AND template = ?",
375			[ $this->webservice->getName(), $this->name ]
376		);
377
378		$tikilib->query(
379			"INSERT INTO tiki_webservice_template (service, template, engine, output, content, last_modif) VALUES(?,?,?,?,?,?)",
380			[
381				$this->webservice->getName(),
382				$this->name,
383				$this->engine,
384				$this->output,
385				$this->content,
386				time(),
387			]
388		);
389
390		if ($this->engine === 'index') {
391			if ($this->output === 'mindex') {
392				Feedback::warning(tra('You will need to rebuild the search index to see these changes'));
393			}
394
395			require_once 'lib/search/refresh-functions.php';
396			refresh_index('webservice', $this->name);
397		}
398	}
399
400	/**
401	 * @return string
402	 */
403	function getTemplateFile()
404	{
405		$token = sprintf("%s_%s", $this->webservice->getName(), $this->name);
406		$file = "temp/cache/" . md5($token) . '.tpl';
407
408		if (! file_exists($file) || $this->lastModif > filemtime($file)) {
409			file_put_contents($file, $this->content);
410		}
411
412		return realpath($file);
413	}
414
415	/**
416	 * @param OIntegrate_Response $response
417	 * @param $outputContext
418	 * @return mixed|string
419	 */
420	function render(OIntegrate_Response $response, $outputContext)
421	{
422		return $response->render($this->engine, $this->output, $outputContext, $this->getTemplateFile());
423	}
424}
425