<?php
|
|
|
|
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
|
|
//
|
|
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
|
|
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
|
|
// $Id$
|
|
|
|
use Symfony\Component\Yaml\Yaml;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
interface OIntegrate_Converter
|
|
{
|
|
/**
|
|
* @param $content
|
|
* @return mixed
|
|
*/
|
|
public function convert($content);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
interface OIntegrate_Engine
|
|
{
|
|
/**
|
|
* @param $data
|
|
* @param $templateFile
|
|
* @return mixed
|
|
*/
|
|
public function process($data, $templateFile);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate
|
|
{
|
|
private $schemaVersion = [];
|
|
private $acceptTemplates = [];
|
|
|
|
/**
|
|
* @param $name
|
|
* @param $engineOutput
|
|
* @return OIntegrate_Engine_JavaScript|OIntegrate_Engine_Smarty|OIntegrate_Engine_Index
|
|
*/
|
|
public static function getEngine($name, $engineOutput)
|
|
{
|
|
switch ($name) {
|
|
case 'javascript':
|
|
return new OIntegrate_Engine_JavaScript();
|
|
case 'smarty':
|
|
return new OIntegrate_Engine_Smarty($engineOutput == 'tikiwiki');
|
|
case 'index':
|
|
return new OIntegrate_Engine_Index();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $from
|
|
* @param $to
|
|
* @return OIntegrate_Converter_Direct|OIntegrate_Converter_EncodeHtml|OIntegrate_Converter_HtmlToTiki|OIntegrate_Converter_TikiToHtml|OIntegrate_Converter_Indexer
|
|
*/
|
|
public static function getConverter($from, $to)
|
|
{
|
|
switch ($from) {
|
|
case 'html':
|
|
if ($to == 'tikiwiki') {
|
|
return new OIntegrate_Converter_HtmlToTiki();
|
|
} elseif ($to == 'html') {
|
|
return new OIntegrate_Converter_Direct();
|
|
}
|
|
break;
|
|
case 'tikiwiki':
|
|
if ($to == 'html') {
|
|
return new OIntegrate_Converter_TikiToHtml();
|
|
} elseif ($to == 'tikiwiki') {
|
|
return new OIntegrate_Converter_EncodeHtml();
|
|
}
|
|
break;
|
|
case 'index':
|
|
case 'mindex':
|
|
if ($to == 'index') {
|
|
return new OIntegrate_Converter_Indexer();
|
|
} elseif ($to == 'html') {
|
|
return new OIntegrate_Converter_Indexer('html');
|
|
} elseif ($to == 'tikiwiki') {
|
|
return new OIntegrate_Converter_Indexer('tikiwiki');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $url
|
|
* @param string $postBody url or json encoded post parameters
|
|
* @param bool $clearCache
|
|
* @return OIntegrate_Response
|
|
*/
|
|
public function performRequest($url, $postBody = null, $clearCache = false) // {{{
|
|
{
|
|
$cachelib = TikiLib::lib('cache');
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
$cacheKey = $url . $postBody;
|
|
|
|
if ($cache = $cachelib->getSerialized($cacheKey)) {
|
|
if (time() < $cache['expires'] && ! $clearCache) {
|
|
return $cache['data'];
|
|
}
|
|
|
|
$cachelib->invalidate($cacheKey);
|
|
}
|
|
|
|
$client = $tikilib->get_http_client($url);
|
|
$method = null;
|
|
|
|
if (empty($postBody)) {
|
|
$method = 'GET';
|
|
$http_headers = [
|
|
'Accept' => 'application/json,text/x-yaml',
|
|
'OIntegrate-Version' => '1.0',
|
|
];
|
|
} else {
|
|
$method = 'POST';
|
|
if (json_decode($postBody)) { // autodetect if the content type should be json
|
|
$requestContentType = 'application/json';
|
|
} else {
|
|
$requestContentType = 'application/x-www-form-urlencoded';
|
|
}
|
|
$http_headers = [
|
|
'Accept' => 'application/json,text/x-yaml',
|
|
'OIntegrate-Version' => '1.0',
|
|
'Content-Type' => $requestContentType,
|
|
];
|
|
$client->setRawBody($postBody);
|
|
}
|
|
|
|
if (count($this->schemaVersion)) {
|
|
$http_headers['OIntegrate-SchemaVersion'] = implode(', ', $this->schemaVersion);
|
|
}
|
|
if (count($this->acceptTemplates)) {
|
|
$http_headers['OIntegrate-AcceptTemplate'] = implode(', ', $this->acceptTemplates);
|
|
}
|
|
|
|
// merge with existing headers
|
|
$headers = $client->getRequest()->getHeaders();
|
|
$http_headers = array_merge($headers->toArray(), $http_headers);
|
|
|
|
$client->setHeaders($http_headers);
|
|
|
|
$client->setMethod($method);
|
|
$httpResponse = $client->send();
|
|
$content = $httpResponse->getBody();
|
|
|
|
$requestContentType = $httpResponse->getHeaders()->get('Content-Type');
|
|
$cacheControl = $httpResponse->getHeaders()->get('Cache-Control');
|
|
|
|
$response = new OIntegrate_Response();
|
|
$response->contentType = $requestContentType;
|
|
$response->cacheControl = $cacheControl;
|
|
if ($requestContentType) {
|
|
$mediaType = $requestContentType->getMediaType();
|
|
} else {
|
|
$mediaType = '';
|
|
}
|
|
$response->data = $this->unserialize($mediaType, $content);
|
|
|
|
$filter = new DeclFilter();
|
|
$filter->addCatchAllFilter('xss');
|
|
|
|
$response->data = $filter->filter($response->data);
|
|
$response->version = $httpResponse->getHeaders()->get('OIntegrate-Version');
|
|
$response->schemaVersion = $httpResponse->getHeaders()->get('OIntegrate-SchemaVersion');
|
|
if (! $response->schemaVersion && isset($response->data->_version)) {
|
|
$response->schemaVersion = $response->data->_version;
|
|
}
|
|
$response->schemaDocumentation = $httpResponse->getHeaders()->get('OIntegrate-SchemaDocumentation');
|
|
|
|
global $prefs;
|
|
if (empty($cacheControl)) {
|
|
$maxage = 0;
|
|
$nocache = false;
|
|
} else {
|
|
// Respect cache duration and no-cache asked for
|
|
$maxage = $cacheControl->getDirective('max-age');
|
|
$nocache = $cacheControl->getDirective('no-cache');
|
|
}
|
|
if ($maxage) {
|
|
$expiry = time() + $maxage;
|
|
|
|
$cachelib->cacheItem(
|
|
$cacheKey,
|
|
serialize(['expires' => $expiry, 'data' => $response])
|
|
);
|
|
// Unless service specifies not to cache result, apply a default cache
|
|
} elseif (empty($nocache) && $prefs['webservice_consume_defaultcache'] > 0) {
|
|
$expiry = time() + $prefs['webservice_consume_defaultcache'];
|
|
|
|
$cachelib->cacheItem($cacheKey, serialize(['expires' => $expiry, 'data' => $response]));
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @param string $type
|
|
* @param string $data
|
|
* @return array|mixed|null
|
|
*/
|
|
public function unserialize($type, $data)
|
|
{
|
|
|
|
if (empty($data)) {
|
|
return null;
|
|
}
|
|
|
|
switch ($type) {
|
|
case 'application/json':
|
|
case 'text/javascript':
|
|
if ($out = json_decode($data, true)) {
|
|
return $out;
|
|
}
|
|
|
|
// Handle invalid JSON too...
|
|
$fixed = preg_replace('/(\w+):/', '"$1":', $data);
|
|
$out = json_decode($fixed, true);
|
|
return $out;
|
|
case 'text/x-yaml':
|
|
return Yaml::parse($data);
|
|
default:
|
|
// Attempt anything...
|
|
if ($out = $this->unserialize('application/json', $data)) {
|
|
return $out;
|
|
}
|
|
if ($out = $this->unserialize('text/x-yaml', $data)) {
|
|
return $out;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $version
|
|
*/
|
|
public function addSchemaVersion($version)
|
|
{
|
|
$this->schemaVersion[] = $version;
|
|
}
|
|
|
|
/**
|
|
* @param $engine
|
|
* @param $output
|
|
*/
|
|
public function addAcceptTemplate($engine, $output)
|
|
{
|
|
$this->acceptTemplate[] = "$engine/$output";
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Response
|
|
{
|
|
public $version = null;
|
|
public $schemaVersion = null;
|
|
public $schemaDocumentation = null;
|
|
public $contentType = null;
|
|
public $cacheControl = null;
|
|
public $data;
|
|
public $errors = [];
|
|
|
|
/**
|
|
* @param $data
|
|
* @param $schemaVersion
|
|
* @param int $cacheLength
|
|
* @return OIntegrate_Response
|
|
*/
|
|
public static function create($data, $schemaVersion, $cacheLength = 300)
|
|
{
|
|
$response = new self();
|
|
$response->version = '1.0';
|
|
$response->data = $data;
|
|
$response->schemaVersion = $schemaVersion;
|
|
|
|
if ($cacheLength > 0) {
|
|
$response->cacheControl = "max-age=$cacheLength";
|
|
} else {
|
|
$response->cacheControl = "no-cache";
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @param $engine
|
|
* @param $output
|
|
* @param $templateLocation
|
|
*/
|
|
public function addTemplate($engine, $output, $templateLocation)
|
|
{
|
|
if (! array_key_exists('_template', $this->data)) {
|
|
$this->data['_template'] = [];
|
|
}
|
|
if (! array_key_exists($engine, $this->data['_template'])) {
|
|
$this->data['_template'][$engine] = [];
|
|
}
|
|
if (! array_key_exists($output, $this->data['_template'][$engine])) {
|
|
$this->data['_template'][$engine][$output] = [];
|
|
}
|
|
|
|
if (0 !== strpos($templateLocation, 'http')) {
|
|
$host = $_SERVER['HTTP_HOST'];
|
|
$proto = 'http';
|
|
$path = dirname($_SERVER['SCRIPT_NAME']);
|
|
$templateLocation = ltrim($templateLocation, '/');
|
|
|
|
$templateLocation = "$proto://$host$path/$templateLocation";
|
|
}
|
|
|
|
$this->data['_template'][$engine][$output][] = $templateLocation;
|
|
}
|
|
|
|
public function send()
|
|
{
|
|
header('OIntegrate-Version: 1.0');
|
|
header('OIntegrate-SchemaVersion: ' . $this->schemaVersion);
|
|
if ($this->schemaDocumentation) {
|
|
header('OIntegrate-SchemaDocumentation: ' . $this->schemaDocumentation);
|
|
}
|
|
header('Cache-Control: ' . $this->cacheControl);
|
|
|
|
$data = $this->data;
|
|
$data['_version'] = $this->schemaVersion;
|
|
|
|
$access = TikiLib::lib('access');
|
|
$access->output_serialized($data);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* @param $engine
|
|
* @param $engineOutput
|
|
* @param $outputContext
|
|
* @param $templateFile
|
|
* @return mixed|string
|
|
*/
|
|
public function render($engine, $engineOutput, $outputContext, $templateFile)
|
|
{
|
|
$engine = OIntegrate::getEngine($engine, $engineOutput);
|
|
if (! $engine) {
|
|
$this->errors = [ 1000, tr('Engine "%0" not found.', $engineOutput) ];
|
|
return false;
|
|
}
|
|
|
|
if (! $output = OIntegrate::getConverter($engineOutput, $outputContext)) {
|
|
$this->errors = [ 1001, tr('Output converter "%0" not found.', $outputContext) ];
|
|
return false;
|
|
}
|
|
|
|
$raw = $engine->process($this->data, $templateFile);
|
|
return $output->convert($raw);
|
|
}
|
|
|
|
/**
|
|
* @param null $supportedPairs
|
|
* @return array
|
|
*/
|
|
public function getTemplates($supportedPairs = null)
|
|
{
|
|
if (! is_array($this->data) || ! isset($this->data['_template']) || ! is_array($this->data['_template'])) {
|
|
return [];
|
|
}
|
|
|
|
$templates = [];
|
|
|
|
foreach ($this->data['_template'] as $engine => $outputs) {
|
|
foreach ($outputs as $output => $files) {
|
|
if (is_array($supportedPairs) && ! in_array("$engine/$output", $supportedPairs)) {
|
|
continue;
|
|
}
|
|
|
|
$files = (array) $files;
|
|
|
|
foreach ($files as $file) {
|
|
$content = TikiLib::lib('tiki')->httprequest($file);
|
|
|
|
$templates[] = [
|
|
'engine' => $engine,
|
|
'output' => $output,
|
|
'content' => $content,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $templates;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Engine_JavaScript implements OIntegrate_Engine
|
|
{
|
|
/**
|
|
* @param $data
|
|
* @param $templateFile
|
|
* @return string
|
|
*/
|
|
public function process($data, $templateFile)
|
|
{
|
|
$json = json_encode($data);
|
|
|
|
return <<<EOC
|
|
<script type="text/javascript">
|
|
var response = $json;
|
|
</script>
|
|
EOC
|
|
. file_get_contents($templateFile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Engine_Smarty implements OIntegrate_Engine
|
|
{
|
|
private $changeDelimiters;
|
|
|
|
/**
|
|
* @param bool $changeDelimiters
|
|
*/
|
|
public function __construct($changeDelimiters = false)
|
|
{
|
|
$this->changeDelimiters = $changeDelimiters;
|
|
}
|
|
|
|
/**
|
|
* @param $data
|
|
* @param $templateFile
|
|
* @return mixed
|
|
*/
|
|
public function process($data, $templateFile)
|
|
{
|
|
/** @var Smarty_Tiki $smarty */
|
|
$smarty = new Smarty_Tiki();
|
|
$smarty->setTemplateDir(dirname($templateFile));
|
|
|
|
if ($this->changeDelimiters) {
|
|
$smarty->left_delimiter = '{{';
|
|
$smarty->right_delimiter = '}}';
|
|
}
|
|
|
|
$smarty->assign('response', $data);
|
|
return $smarty->fetch($templateFile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Engine to pass on raw data and mapping info from the template
|
|
*/
|
|
class OIntegrate_Engine_Index implements OIntegrate_Engine
|
|
{
|
|
/**
|
|
* @param array $data
|
|
* @param string $templateFile
|
|
* @return array
|
|
*/
|
|
public function process($data, $templateFile)
|
|
{
|
|
$mappingString = file_get_contents($templateFile);
|
|
$mapping = json_decode($mappingString, true);
|
|
|
|
if ($mappingString && ! $mapping) {
|
|
Feedback::error(tr('Could decode JSON webservice template for indexing, check the syntax'));
|
|
}
|
|
|
|
return [
|
|
'data' => $data,
|
|
'mapping' => $mapping,
|
|
];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Converter_Direct implements OIntegrate_Converter // {{{
|
|
{
|
|
/**
|
|
* @param $content
|
|
* @return mixed
|
|
*/
|
|
public function convert($content)
|
|
{
|
|
return $content;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Converter_EncodeHtml implements OIntegrate_Converter // {{{
|
|
{
|
|
/**
|
|
* @param $content
|
|
* @return string
|
|
*/
|
|
public function convert($content)
|
|
{
|
|
return htmlentities($content, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Converter_HtmlToTiki implements OIntegrate_Converter // {{{
|
|
{
|
|
/**
|
|
* @param $content
|
|
* @return string
|
|
*/
|
|
public function convert($content)
|
|
{
|
|
return '~np~' . $content . '~/np~';
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class OIntegrate_Converter_TikiToHtml implements OIntegrate_Converter // {{{
|
|
{
|
|
/**
|
|
* @param $content
|
|
* @return mixed|string
|
|
*/
|
|
public function convert($content)
|
|
{
|
|
return TikiLib::lib('parser')->parse_data(htmlentities($content, ENT_QUOTES, 'UTF-8'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt to index the result from the request
|
|
*/
|
|
class OIntegrate_Converter_Indexer implements OIntegrate_Converter
|
|
{
|
|
private $format;
|
|
|
|
public function __construct($format = 'none')
|
|
{
|
|
$this->format = $format;
|
|
}
|
|
|
|
/**
|
|
* @param $content
|
|
* @return mixed|string
|
|
*/
|
|
public function convert($content)
|
|
{
|
|
if ($this->format === 'html' || $this->format === 'tikiwiki') {
|
|
if (! empty($_REQUEST['nt_name'])) { // preview from admin/webservice page
|
|
$source = new Search_ContentSource_WebserviceSource();
|
|
$factory = new Search_Type_Factory_Direct();
|
|
|
|
if ($_REQUEST['nt_output'] === 'mindex') {
|
|
$documents = $source->getDocuments();
|
|
$data = [];
|
|
$count = 0;
|
|
foreach ($documents as $document) {
|
|
if (strpos($document, $_REQUEST['nt_name']) === 0) {
|
|
$data[$document] = $source->getDocument($document, $factory);
|
|
$count++;
|
|
if ($count > 100) { // enough for a preview?
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$data = $source->getDocument($_REQUEST['nt_name'], $factory);
|
|
}
|
|
|
|
$output = '<h3>' . tr('Parsed Data') . '</h3>';
|
|
$output .= '<pre style="max-height: 40em; overflow: auto; white-space: pre-wrap">';
|
|
$output .= htmlentities(
|
|
print_r($data, true),
|
|
ENT_QUOTES,
|
|
'UTF-8'
|
|
);
|
|
} else {
|
|
$output = '<h3>' . tr('Data') . '</h3>';
|
|
$output .= '<pre style="max-height: 20em; overflow: auto; white-space: pre-wrap">';
|
|
$output .= htmlentities(
|
|
json_encode($content['data'], JSON_PRETTY_PRINT),
|
|
ENT_QUOTES,
|
|
'UTF-8'
|
|
);
|
|
$output .= '</pre>';
|
|
|
|
if ($this->format === 'html') {
|
|
$output .= '<h3>' . tr('Mapping') . '</h3>';
|
|
$output .= '<pre style="max-height: 20em; overflow: auto; white-space: pre-wrap">';
|
|
$output .= htmlentities(
|
|
json_encode($content['mapping'], JSON_PRETTY_PRINT),
|
|
ENT_QUOTES,
|
|
'UTF-8'
|
|
);
|
|
$output .= '</pre>';
|
|
} else { // wiki mode from plugin
|
|
$output = "~np~{$output}~/np~";
|
|
}
|
|
}
|
|
|
|
return $output;
|
|
} else {
|
|
return $content;
|
|
}
|
|
}
|
|
}
|