Page MenuHomePhorge

No OneTemporary

diff --git a/src/controller/DiagramController.php b/src/controller/DiagramController.php
index 4508f02..65220bf 100644
--- a/src/controller/DiagramController.php
+++ b/src/controller/DiagramController.php
@@ -1,947 +1,947 @@
<?php
final class DiagramController extends PhabricatorController {
/**
* Processes incoming HTTP requests from Diagram application
*/
public function handleRequest(AphrontRequest $request) {
$this->setRequest($request);
// detetermine if GET or POST HTTP call
if ($request->isHTTPPost()) {
// process POST calls (like save action)
return $this->handleHttpPostCall($request);
}
// determine type of URL by means of DiagramApplication route parameters
$diagramphid = $request->getURIData('diagramphid');
$diagramid = $request->getURIData('diagramid');
$versioneddiagramid = $request->getURIData('versioneddiagramid');
$version = $request->getURIData('version');
$route = $request->getURIData('route');
$versioninfoDiagramID = $request->getURIData('versioninfodiagram');
$versioninfoPage = $request->getURIData('versioninfopage');
$loadDiagramName = $request->getURIData('loadDiagramName');
$loadDiagramPHID = $request->getURIData('loadDiagramPHID');
$loadDiagramVersion = $request->getURIData('loadDiagramVersion');
if (isset($diagramphid) && !empty(trim($diagramphid))) {
// return PNG image data
$diagram = id(new DiagramVersion())->loadByDiagramPHID($diagramphid);
if ($diagram !== null) {
$response = new AphrontFileResponse();
$response->setMimeType('image/png');
$response->setContent($diagram->getData());
return $response;
}
}
if (isset($versioninfoDiagramID) && !empty(trim($versioninfoDiagramID))) {
// return diagram version info
if (!isset($versioninfoPage) || empty(trim($versioninfoPage))) {
// versioninfoPage was dismissed -> initialize to 1
$versioninfoPage = "1";
}
$diagramVersions = id(new DiagramVersion())->loadByDiagramID(
$versioninfoDiagramID);
if ($diagramVersions !== null) {
$result = [];
$viewer = $request->getViewer();
// determine total count of versions
$totalcount = count($diagramVersions);
// filter out some of the versions we want to show
$pageSize = 10;
$diagramVersions = array_slice($diagramVersions,
($versioninfoPage - 1) * $pageSize,
$pageSize
);
// calculate number of pages
$totalpages = ceil($totalcount / $pageSize);
// create menu-items
foreach ($diagramVersions as $diagramVersion) {
$author = $diagramVersion->getAuthorPHID();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array(
$author
))
->executeOne();
$dateModified = $diagramVersion->getDateModified();
$result[] = array(
"id" => $diagramVersion->getVersion(),
"datetime" => phabricator_datetime($dateModified, $viewer),
"author" => $user->getUsername()
);
}
// reply back
$response = id(new AphrontJSONResponse())->setAddJSONShield(false)
->setContent(array(
"data" => $result,
"pagecount" => $totalpages,
"nopager" => $totalcount <= $pageSize
));
return $response;
} else {
// version info requested for inexistant diagram
$response = id(new AphrontJSONResponse())->setAddJSONShield(false)
->setContent(array(
"data" => array(),
"pagecount" => 0
));
return $response;
}
}
if ($route == 'loadJsExtension') {
$response = new AphrontFileResponse();
$response->setMimeType('application/javascript');
$base64_data = "";
if (isset($loadDiagramName) && !empty(trim($loadDiagramName))) {
$diagram_id = (int) substr($loadDiagramName, strlen("DIAG"));
if (isset($loadDiagramVersion) && !empty(trim($loadDiagramVersion))) {
$diagramVersion = id(new DiagramVersion())->loadByVersionedDiagramID(
$diagram_id,
$loadDiagramVersion);
} else {
$diagramVersion = id(new DiagramVersion())->loadLatestByDiagramID(
$diagram_id);
}
if ($diagramVersion) {
$data = $diagramVersion->getData();
$base64_data = base64_encode($data);
}
}
$response->setContent('loadJsExtension("'
. $loadDiagramName
. '", "'
. $loadDiagramPHID
. '", "'
. $loadDiagramVersion
. '", "'
. $base64_data
. '");');
return $response;
}
$root = '';
$file = rtrim($request->getPath(), '/');
$root = dirname(phutil_get_library_root('diagram'));
// determine from which application area the file should be loaded:
// 1) Phorge extension source
// or 2) drawio source
if ($route == 'iframe') {
// load from drawio source
if ($file == '/diagram/iframe')
$file .= '/index.html';
if ($versioneddiagramid != null && $version != null) {
$file = preg_replace(
"/^\/diagram\/$versioneddiagramid\/$version" ."iframe\//",
"data/drawio/src/main/webapp/",
$file);
} else {
$file = preg_replace("/^\/diagram\/($diagramid\/?)?iframe\//",
"data/drawio/src/main/webapp/",
$file);
}
} else {
// load from extension source
if (rtrim($file, '/') == '/diagram') {
return $this->showApplication($request);
}
if ($versioneddiagramid !== null && $version !== null) {
$file = preg_replace(
'/^\/diagram\/' . $versioneddiagramid . '\/'. $version . '\/?/',
'data/',
$file
);
$file = rtrim($file, '/') . '/' . $versioneddiagramid;
} else {
$file = preg_replace(
'/^\/diagram\/(' . $diagramid . '\/)?/',
'data/',
$file
);
}
}
// check if we are trying to load "iframe loader" files,
// if so, correct the path accordingly
if ($file == "data/drawio/src/main/webapp/index.html") {
return $this->showIframe($request);
}
else
if ($file == "data/drawio/src/main/webapp/iframe.css"
|| $file == "data/drawio/src/main/webapp/iframe-toolbtn.css"
|| $file == "data/drawio/src/main/webapp/iframe1.js"
|| $file == "data/drawio/src/main/webapp/iframe2.js"
|| $file == "data/drawio/src/main/webapp/iframe-toolbtn.js") {
$file = str_replace("drawio/src/main/webapp/", "", $file);
}
// determine full path
$path = $root . '/' . $file;
if (file_exists($path) == false || is_readable($path) == false) {
if (preg_match('/^data\/DIAG(\d+)$/', $file, $matches)) {
$diagram_id = (int) $matches[1];
if ($version === null) {
$diagramVersion = id(new DiagramVersion())->loadLatestByDiagramID(
$diagram_id);
} else {
$diagramVersion = id(new DiagramVersion())->loadByVersionedDiagramID(
$diagram_id, $version);
}
if ($diagramVersion) {
$data = $diagramVersion->getData();
$base64_data = base64_encode($data);
$diagram = id(new Diagram())->loadByID($diagram_id);
return $this->showApplication(
$request,
'DIAG' . $diagram_id,
$diagram->getPHID(),
$version ?? "",
$base64_data
);
}
}
// Invalid URL
$response = id(new Aphront404Response());
return $response;
} else {
// process Iframe content
switch (pathinfo($file, PATHINFO_EXTENSION)) {
case 'html':
$response = id(new PlainHtmlWebpageResponse())
->setFrameable(true)
->setContent(file_get_contents($path));
break;
case 'js':
$response = new AphrontFileResponse();
$response->setMimeType('application/javascript');
break;
case 'css':
$response = new AphrontFileResponse();
$response->setMimeType('text/css');
break;
case 'txt':
$response = new AphrontFileResponse();
$response->setMimeType('text/plain');
break;
case 'png':
$response = new AphrontFileResponse();
$response->setMimeType('image/png');
break;
case 'gif':
$response = new AphrontFileResponse();
$response->setMimeType('image/gif');
break;
case 'jpg':
case 'jpeg':
$response = new AphrontFileResponse();
$response->setMimeType('image/jpeg');
break;
default:
$response = new AphrontFileResponse();
$response->setMimeType('application/octet-stream');
break;
}
try {
$response->setContent(file_get_contents($path));
} catch (Exception $e) {
$response->setContent($route);
}
return $response;
}
}
/**
* Compares the draw.io tEXt metadata from 2 PNG base64 strings.
* The content looks like this:
* <mxfile host="..." modified="..." agent="..." etag="..." version="..."...>
* <diagram id="O253AQcHVgjdl_wygdBA" name="Page-1">
* <mxGraphModel ...>
* <root>
* ...
* </mxCell>
* </root>
* </mxGraphModel>
* </diagram>
* </mxfile>
*
* The modified and etag attributes of mxfile will always be different.
* They are cut out before the 2 strings are compared.
*/
public static function equalPngMetaData($base64_1, $base64_2) {
$base64 = array($base64_1, $base64_2);
$textData = array();
for ($i = 0; $i < 2; $i++) {
$data = base64_decode($base64[$i]);
$fp = fopen('data://text/plain;base64,' . base64_encode($data), 'rb');
$sig = fread($fp, 8);
if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") {
fclose($fp);
return false;
}
$textData[$i] = array();
while (!feof($fp)) {
try {
$chunk = unpack('Nlength/a4type', fread($fp, 8));
} catch (Exception $e) {
// invalid base64 data
return false;
}
if ($chunk['type'] == 'IEND') break;
if ($chunk['type'] == 'tEXt') {
list($key, $val) = explode("\0", fread($fp, $chunk['length']));
if ($key == 'mxfile') {
// Decode the URL-encoded XML data
$decodedVal = urldecode($val);
// Load the XML and remove the modified and etag attributes
$xml = simplexml_load_string($decodedVal);
unset($xml->attributes()->modified);
unset($xml->attributes()->etag);
// Save the modified XML as the value
$val = $xml->asXML();
}
$textData[$i][$key] = $val;
fseek($fp, 4, SEEK_CUR);
} else {
fseek($fp, $chunk['length'] + 4, SEEK_CUR);
}
}
fclose($fp);
}
if (isset($textData[0]['mxfile']) && isset($textData[1]['mxfile'])) {
// Both arrays contain the mxfile key, compare their values
return $textData[0]['mxfile'] == $textData[1]['mxfile'];
} else {
// At least one of the arrays doesn't contain mxfile key, return false
return false;
}
}
/**
* Processes HTTP POST calls from Diagram application, like 'Save' action
*/
private function handleHttpPostCall(AphrontRequest $request) {
$subscriptionphid = $request->getURIData('subscriptionphid');
if (isset($subscriptionphid) && !empty(trim($subscriptionphid))) {
// get list of subscriber for specified diagram phid
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$subscriptionphid);
// verify if viewer is subscriber
$viewer = $request->getViewer();
if ($viewer == null) {
$isSubscribed = false;
} else {
$isSubscribed = in_array($viewer->getPHID(),$subscribers);
}
// reply back
$response = id(new AphrontJSONResponse())->setAddJSONShield(false)
->setContent(array(
"subscribed" => $isSubscribed
));
return $response;
}
$base64_data = $request->getStr("data");
$diagram_id = $request->getStr("diagramID");
// cut off "data:image/png;base64,"
$base64_data = substr($base64_data, strpos($base64_data, ',') + 1);
if ($diagram_id != "") {
// check if we are trying to save the same data as the current data
$diagram = id(new DiagramVersion())->loadLatestByDiagramID($diagram_id);
if ($diagram !== null) {
$data = $diagram->getData();
$old_data = base64_encode($data);
if (DiagramController::equalPngMetaData($base64_data, $old_data)) {
// data hasn't been modified
// => do not create new version
$response = id(new AphrontJSONResponse())->setAddJSONShield(false)
->setContent(array(
"Status" => "OK",
"DiagramID" => $diagram->getDiagramID(),
"Version" => $diagram->getVersion()
));
return $response;
}
}
}
// Set the options for the new file
$options = array(
'name' => 'diagram.png',
'viewPolicy' => PhabricatorPolicies::POLICY_USER,
'mime-type' => 'image/png',
'actor' => $this->getViewer(),
'diagramID' => $diagram_id
);
try {
// Create the new file object
$diagram = DiagramVersion::newFromFileData($base64_data, $options);
$diagram->publishNewVersion($request, $diagram->getDiagramID());
$response = id(new AphrontJSONResponse())->setAddJSONShield(false)
->setContent(array(
"Status" => "OK",
"DiagramID" => $diagram->getDiagramID(),
"Version" => $diagram->getVersion()
));
return $response;
} catch (Exception $e) {
$response = id(new AphrontJSONResponse())->setAddJSONShield(false)
->setContent(array(
"Status" => "ERROR",
"Error" => $e->getMessage(),
));
return $response;
}
}
/**
* Verifies if the given base64 data is draw.io compatible
*/
public static function isDrawioPngBase64($base64) {
$data = base64_decode($base64);
$fp = fopen('data://text/plain;base64,' . base64_encode($data), 'rb');
$sig = fread($fp, 8);
if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") {
fclose($fp);
return false;
}
while (!feof($fp)) {
try {
$chunk = unpack('Nlength/a4type', fread($fp, 8));
} catch (Exception $e) {
// invalid base64 data
return false;
}
if ($chunk['type'] == 'IEND') break;
if ($chunk['type'] == 'tEXt') {
list($key, $val) = explode("\0", fread($fp, $chunk['length']));
if ($key == 'mxfile') {
fclose($fp);
return true;
}
fseek($fp, 4, SEEK_CUR);
} else {
fseek($fp, $chunk['length'] + 4, SEEK_CUR);
}
}
fclose($fp);
return false;
}
/**
* Shows the draw.io application integrated in Phorge's layout
*/
private function showApplication(
AphrontRequest $request,
string $diagramName = null,
string $diagramPHID = null,
string $diagramVersion = null,
string $diagramBase64 = null
) {
$applicationUrl = "/" . explode("/", $request->getPath())[1];
$content = phutil_tag(
'div',
array(),
array(
phutil_tag(
'div',
array(
'id' => 'mainScreen',
)),
phutil_tag('div',
array(),
array(
phutil_tag(
'img',
array(
'class' => 'drawio',
)),
)),
phutil_tag(
'script',
array(
'src' => 'phorge_extension.js',
'defer' => true
),
''
),
phutil_tag(
'script',
array(
'src' => $applicationUrl . '/loadJsExtension/'
. $diagramName
. '/'
. $diagramPHID
. '/'
. $diagramVersion
. '/',
'defer' => true
),
''
),
phutil_tag(
'div',
array(
'class' => 'crumbs',
'style' => 'top:48px;'
. 'margin-left: 4px;'
. 'position: fixed;'
. 'font-weight: bold;'
),
array(
phutil_tag(
'a',
array(
'href' => $applicationUrl
),
array(
phutil_tag(
'span',
array(
'class' => 'phui-font-fa fa-sitemap',
'style' => 'padding-right:5px;'
))
)),
phutil_tag(
'a',
array(
'href' => $applicationUrl
),
'Diagram'
),
phutil_tag(
'span',
array(
'class' => 'diagramName',
'style' => 'display:none'
),
array(
phutil_tag(
'span',
array(
'style' => 'margin: 5px;'
. 'opacity: .5;'
),
'>'
),
phutil_tag(
'a',
array(),
''
),
phutil_tag(
'span',
array(
'class' => 'version',
'style' => 'margin-left: 8px;'
. 'color: #999;'),
''),
))
))
));
$view = id(new PhabricatorStandardPageView())
->setRequest($request)
->setController($this)
->setDeviceReady(true)
->setTitle("Diagrams")
->appendChild($content);
$response = id(new AphrontWebpageResponse())
->setContent($view->render());
return $response;
}
/**
* Shows the internal draw.io application
*/
private function showIframe(
AphrontRequest $request
) {
$content = phutil_tag(
'html',
array(),
array(
phutil_tag(
'head',
array(),
array(
phutil_tag(
'title',
array(),
'Flowchart Maker & Online Diagram Software'
),
phutil_tag(
'meta',
array(
'charset' => 'utf-8'
)
),
phutil_tag(
'meta',
array(
'http-equiv' => 'content-type',
'content' => 'text/html; charset=utf-8'
)
),
phutil_tag(
'meta',
array(
'name' => 'viewport',
'content' => 'width=device-width, initial-scale=1.0, '
. 'maximum-scale=1.0, user-scalable=no'
)
),
phutil_tag(
'meta',
array(
'name' => 'mobile-web-app-capable',
'content' => 'yes'
)
),
phutil_tag(
'link',
array(
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => 'iframe.css'
)
),
phutil_tag(
'link',
array(
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => 'iframe-toolbtn.css'
)
),
phutil_tag(
'link',
array(
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => 'styles/grapheditor.css'
)
),
phutil_tag(
'script',
array(
'type' => 'text/javascript',
'src' => 'iframe1.js',
'defer' => true
)
)
)
),
phutil_tag(
'body',
array(
'class' => 'geEditor'
),
array(
phutil_tag(
'div',
array(
'id' => 'geinfo'
),
phutil_tag(
'div',
array(
'class' => 'geBlock'
),
array(
phutil_tag(
'h1',
array(),
'Flowchart Maker and Online Diagram Software'
),
phutil_tag(
'p',
array(),
'draw.io is free online diagram software. You can use '
. 'it as a flowchart maker, network diagram software, '
. 'to create UML online, as an ER diagram tool, to '
. 'design database schema, to build BPMN online, as a '
. 'circuit diagram maker, and more. draw.io can import '
. '.vsdx, Gliffy™ and Lucidchart™ files.'
),
phutil_tag(
'h2',
array(
'id' => 'gestatus'
),
'Loading...'
),
phutil_tag(
'div',
array(
'class' => 'init-spinner'
),
phutil_tag(
'div',
array(
'class' => 'content'
),
array(
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(0deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -1s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(27deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.923077ss;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(55deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.846154s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(83deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.769231s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(110deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.692308s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(138deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.615385s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(166deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.538462s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(193deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.461538s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(221deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.384615s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(249deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.307692s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(276deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.230769s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(304deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.153846s;'
)
)
),
phutil_tag(
'div',
array(
'class' => 'spike',
'style' => 'transform: rotate(332deg)'
. ' translatex(13px);'
),
phutil_tag(
'div',
array(
'class' => 'animator',
'style' => 'animation-delay: -0.0769231s;'
)
)
)
)
)
)
)
- ),
+ )
),
phutil_tag(
'script',
array(
'type' => 'text/javascript',
'src' => 'iframe2.js',
'defer' => true
)
),
phutil_tag(
'script',
array(
'type' => 'text/javascript',
'src' => 'iframe-toolbtn.js',
'defer' => true
)
)
)
)
)
);
$response = id(new PlainHtmlWebpageResponse())
->setFrameable(true)
->setContent($content);
return $response;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Feb 23, 04:33 (1 d, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1132204
Default Alt Text
(32 KB)

Event Timeline