Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F3282890
DiagramController.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
15 KB
Referenced Files
None
Subscribers
None
DiagramController.php
View Options
<?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
$diagramid
=
$request
->
getURIData
(
'diagramid'
)
;
$versioneddiagramid
=
$request
->
getURIData
(
'versioneddiagramid'
)
;
$version
=
$request
->
getURIData
(
'version'
)
;
$route
=
$request
->
getURIData
(
'route'
)
;
$versioninfoDiagramID
=
$request
->
getURIData
(
'versioninfodiagram'
)
;
$versioninfoPage
=
$request
->
getURIData
(
'versioninfopage'
)
;
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
;
}
}
$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\//"
,
"drawio/src/main/webapp/"
,
$file
)
;
}
else
{
$file
=
preg_replace
(
"/^\/diagram\/($diagramid\/?)?iframe\//"
,
"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
)
;
}
}
// 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
(
)
)
->
setDisableContentSecurityPolicy
(
true
)
->
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
)
)
;
$response
->
setCacheDurationInSeconds
(
60
*
60
*
24
*
30
)
;
$response
->
setLastModified
(
time
(
)
)
;
$response
->
setCanCDN
(
true
)
;
}
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
=
''
,
string
$diagramPHID
=
''
,
string
$diagramVersion
=
''
,
string
$base64_data
=
''
)
{
$applicationUrl
=
"/"
.
explode
(
"/"
,
$request
->
getPath
(
)
)
[
1
]
;
$behaviorConfig
=
array
(
)
;
$behaviorConfig
[
'initParams'
]
=
array
(
$diagramName
,
$diagramPHID
,
$diagramVersion
,
$base64_data
)
;
$behaviorConfig
[
'toolbarCss'
]
=
celerity_get_resource_uri
(
'/iframe-toolbtn.css'
,
'diagram-resources'
)
;
$behaviorConfig
[
'toolbarJs'
]
=
celerity_get_resource_uri
(
'/iframe-toolbtn.js'
,
'diagram-resources'
)
;
require_celerity_resource
(
'javelin-behavior'
)
;
Javelin
::
initBehavior
(
'diagram-extension'
,
$behaviorConfig
,
'diagram-resources'
)
;
$content
=
phutil_tag
(
'div'
,
array
(
)
,
array
(
phutil_tag
(
'div'
,
array
(
'id'
=>
'mainScreen'
,
)
)
,
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
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mar 24 2025, 12:18 (7 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1117819
Default Alt Text
DiagramController.php (15 KB)
Attached To
Mode
R5 Diagrams
Attached
Detach File
Event Timeline
Log In to Comment