getCached($fileIdentifier, 'diagram');
if (
! $content
&& $prefs['fgal_use_casperjs_to_export_images'] === 'y'
&& class_exists('CasperJsInstaller\Installer')
) {
$content = self::getDiagramAsImageUsingCasperJs('' . $diagramContent . '', $fileIdentifier);
}
if (! $content && $prefs['fgal_use_drawio_services_to_export_images'] === 'y') {
$content = self::getDiagramAsImageFromExternalService('' . $diagramContent . '');
}
if (! empty($content)) {
$cachelib->cacheItem($fileIdentifier, $content, 'diagram');
}
return $content;
}
/**
* Get an array of diagrams based on the XML content or file_id which will retrieve the File XML contents
* @param $identifier
* @param $page string Return specific page from the diagram
* @return array|bool
*/
public static function getDiagramsFromIdentifier($identifier, $page = '')
{
$rawXmlContent = $identifier;
if (is_int($identifier)) {
$file = File::id($identifier);
if (empty($file)) {
return false;
}
$rawXmlContent = $file->getContents();
}
$diagramRoot = simplexml_load_string($rawXmlContent);
if ($diagramRoot === false && ! empty($identifier)) {
Feedback::error(tr('The provided diagram XML is not valid. Please check and validate the diagram structure.'));
}
$diagrams = [];
foreach ($diagramRoot->diagram as $diagram) {
$diagramName = (string) $diagram->attributes()->name;
if (! empty($page) && $page != $diagramName) {
continue;
}
$diagrams[] = $diagram->asXML();
}
return $diagrams;
}
/**
* Check if file is a diagram
*
* @param $fileId
* @return bool
*/
public static function isDiagram($fileId)
{
$file = TikiFile::id($fileId);
$type = $file->getParam('filetype');
$data = trim($file->getContents());
if (in_array($type, ['text/plain', 'text/xml']) && (strpos($data, '\n", '', $diagram_xml->asXml());
return base64_encode(gzdeflate($headlessXML));
}
/**
* Parse Wiki Markup inside a diagram's cells
* @param $diagram_hash
* @return string
* @throws \Exception
*/
public static function parseDiagramWikiSyntax($rootDiagram): string
{
if (is_string($rootDiagram)) {
$decoded = self::inflate($rootDiagram);
$rootDiagram = simplexml_load_string(urldecode($decoded));
}
foreach ($rootDiagram as $root) {
$xpathToUnset = [];
$newCells = [];
foreach ($root as $mxCell) {
if (! isset($mxCell['value'])) {
continue;
}
$cellValue = (string) $mxCell['value'];
$plugins = \WikiParser_PluginMatcher::match($cellValue);
if ($plugins->count()) {
$plugin = $plugins->next();
if ($plugin->getName() === 'list') {
require_once "lib/wiki-plugins/wikiplugin_list.php";
$populatedListContent = [];
$callback = function ($result, $formatter) use (&$populatedListContent) {
$populatedListContent = $result;
};
$argumentParser = new WikiParser_PluginArgumentParser();
$listPluginArguments = $argumentParser->parse($plugin->getArguments());
if (! isset($listPluginArguments['diagram-repeat'])) {
$listPluginArguments['diagram-repeat'] = self::WIKI_SYNTAX_DEFAULT_DIAGRAM_MXCELL_REPEAT;
}
if (! isset($listPluginArguments['diagram-offset'])) {
$listPluginArguments['diagram-offset'] = self::WIKI_SYNTAX_DEFAULT_DIAGRAM_MXCELL_OFFSET;
}
wikiplugin_list($plugin->getBody(), array_merge($listPluginArguments, ['resultCallback' => $callback]));
$cellAttributes = current($mxCell->attributes());
$geometryAttributes = current($mxCell->mxGeometry->attributes());
$offsetAxis = $listPluginArguments['diagram-repeat'] == 'vertical' ? 'y' : 'x';
foreach ($populatedListContent as $listContent) {
$newCell = self::generateMxCell($cellAttributes, $geometryAttributes);
$newCell['value'] = htmlspecialchars_decode(TikiLib::lib('parser')->parse_data($listContent));
$newCells[] = $newCell;
$geometryAttributes[$offsetAxis] += $listPluginArguments['diagram-offset'];
}
$xpathToUnset[] = '//mxCell[@id="' . $cellAttributes['id'] . '"]';
} else {
$mxCell['value'] = htmlspecialchars_decode(TikiLib::lib('parser')->parse_data($cellValue));
}
} else {
$cellValue = str_replace('
', "\r\n", $cellValue);
$mxCell['value'] = htmlspecialchars_decode(TikiLib::lib('parser')->parse_data($cellValue, ['is_html' => 1]));
}
}
foreach ($xpathToUnset as $xpath) {
$toUnset = current($rootDiagram->xpath($xpath));
unset($toUnset[0]);
}
foreach ($newCells as $newCell) {
XMLHelper::appendElement($root, $newCell);
}
}
return self::deflate($rootDiagram);
}
/**
* Converts a diagram plugin inner content to md5 (useful when performing diffs)
* @param WikiParser_PluginMatcher $parser
* @return bool returns true if any content was changed
*/
public static function md5WikiPluginDiagramContent(WikiParser_PluginMatcher &$parser): bool
{
$hasChanges = false;
$parser->rewind();
while ($parser->valid()) {
$current = $parser->current();
if ($parser->current()->getName() !== self::WIKI_SYNTAX_DIAGRAM_PLUGIN || empty($parser->current()->getBody())) {
$parser->next();
continue;
}
$bodyHash = tr('diagram content hash - %0', md5($current->getBody()));
$current->replaceWithPlugin(self::WIKI_SYNTAX_DIAGRAM_PLUGIN, $current->getArguments(), $bodyHash);
$hasChanges = true;
$parser->next();
}
return $hasChanges;
}
/**
* Generate an mxCell based on specific attributes
* @param $cellAttributes
* @param $geometryAttributes
* @return \SimpleXMLElement
*/
private static function generateMxCell(array $cellAttributes, array $geometryAttributes): \SimpleXMLElement
{
$mxCell = simplexml_load_string("");
if (! empty($cellAttributes)) {
foreach ($cellAttributes as $key => $attribute) {
$mxCell[$key] = $attribute;
}
$mxCell['id'] = uniqid();
}
if (! empty($geometryAttributes)) {
foreach ($geometryAttributes as $key => $geometryAttribute) {
$mxCell->mxGeometry[$key] = $geometryAttribute;
}
}
return $mxCell;
}
/**
* Get diagram as PNG using CasperJs
* @param $rawXml
* @param $fileIdentifier
* @return bool|string
*/
private static function getDiagramAsImageUsingCasperJs($rawXml, $fileIdentifier)
{
$vendorPath = VendorHelper::getAvailableVendorPath('diagram', 'tikiwiki/diagram', false);
$casperBin = TIKI_PATH . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'casperjs';
$scriptPath = TIKI_PATH . DIRECTORY_SEPARATOR . 'lib/jquery_tiki/tiki-diagram.js';
$htmlFile = TIKI_PATH . DIRECTORY_SEPARATOR . 'lib/core/File/DiagramHelperExportCasperJS.html';
$jsfile = TIKI_PATH . DIRECTORY_SEPARATOR . 'temp/do_' . $fileIdentifier . '.js';
$imgFile = TIKI_PATH . DIRECTORY_SEPARATOR . 'temp/diagram_' . $fileIdentifier . '.png';
if (! empty($vendorPath) && file_exists($casperBin) && file_exists($scriptPath) && file_exists($htmlFile)) {
$jsContent = <<setTimeout($params['timeout']);
$process->setIdleTimeout($params['timeout']);
}
try {
$process->run();
} catch (ProcessTimedOutException $e) {
$e->getMessage();
unlink($jsfile);
}
if ($success = $process->isSuccessful() && file_exists($imgFile)) {
$imgData = file_get_contents($imgFile);
unlink($jsfile);
unlink($imgFile);
return base64_encode($imgData);
}
}
}
/**
* Get diagram as PNG from DRAWIO external service
* @param $rawXml
* @return bool|string
*/
private static function getDiagramAsImageFromExternalService($rawXml)
{
global $prefs;
$logsLib = TikiLib::lib('logs');
$serviceEndpoint = $prefs['fgal_drawio_service_endpoint'];
if (empty($serviceEndpoint) || filter_var($serviceEndpoint, FILTER_VALIDATE_URL) === false) {
$logsLib->add_log('diagram export', tr('Invalid value for fgal_drawio_service_endpoint preference. Not a valid URL.'));
return null;
}
$jsonPayload = json_encode([
'format' => self::DRAW_IO_IMAGE_FORMAT,
'embedXml' => '0',
'base64' => '1',
'xml' => $rawXml,
]);
$client = \TikiLib::lib('tiki')->get_http_client($serviceEndpoint, [
'timeout' => self::FETCH_IMAGE_CONTENTS_TIMEOUT
]);
$client->setRawBody($jsonPayload);
$client->setMethod(\Laminas\Http\Request::METHOD_POST);
$client->setHeaders([
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonPayload),
]);
$response = $client->send();
$statusCode = $response->getStatusCode();
// In case of bad requests or server issues (HTTP 4xx and 5xx)
if (empty($statusCode) || $statusCode >= 400) {
$logsLib->add_log(
'diagram export',
tr('Something went wrong when using the third party service to export the diagram. Status %0 received.', $statusCode)
);
return null;
}
return $response->getBody();
}
}