Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2991967
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R5 Diagrams
Attached
Detach File
Event Timeline
Log In to Comment