Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F547436
PhabricatorObjectRemarkupRule.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
PhabricatorObjectRemarkupRule.php
View Options
<?php
abstract
class
PhabricatorObjectRemarkupRule
extends
PhutilRemarkupRule
{
private
$referencePattern
;
private
$embedPattern
;
const
KEY_RULE_OBJECT
=
'rule.object'
;
const
KEY_MENTIONED_OBJECTS
=
'rule.object.mentioned'
;
abstract
protected
function
getObjectNamePrefix
();
abstract
protected
function
loadObjects
(
array
$ids
);
public
function
getPriority
()
{
return
450.0
;
}
protected
function
getObjectNamePrefixBeginsWithWordCharacter
()
{
$prefix
=
$this
->
getObjectNamePrefix
();
return
preg_match
(
'/^
\w
/'
,
$prefix
);
}
protected
function
getObjectIDPattern
()
{
return
'[1-9]
\d
*'
;
}
protected
function
shouldMarkupObject
(
array
$params
)
{
return
true
;
}
protected
function
getObjectNameText
(
$object
,
PhabricatorObjectHandle
$handle
,
$id
)
{
return
$this
->
getObjectNamePrefix
().
$id
;
}
protected
function
loadHandles
(
array
$objects
)
{
$phids
=
mpull
(
$objects
,
'getPHID'
);
$viewer
=
$this
->
getEngine
()->
getConfig
(
'viewer'
);
$handles
=
$viewer
->
loadHandles
(
$phids
);
$handles
=
iterator_to_array
(
$handles
);
$result
=
array
();
foreach
(
$objects
as
$id
=>
$object
)
{
$result
[
$id
]
=
$handles
[
$object
->
getPHID
()];
}
return
$result
;
}
protected
function
getObjectHref
(
$object
,
PhabricatorObjectHandle
$handle
,
$id
)
{
$uri
=
$handle
->
getURI
();
if
(
$this
->
getEngine
()->
getConfig
(
'uri.full'
))
{
$uri
=
PhabricatorEnv
::
getURI
(
$uri
);
}
return
$uri
;
}
protected
function
renderObjectRefForAnyMedia
(
$object
,
PhabricatorObjectHandle
$handle
,
$anchor
,
$id
)
{
$href
=
$this
->
getObjectHref
(
$object
,
$handle
,
$id
);
$text
=
$this
->
getObjectNameText
(
$object
,
$handle
,
$id
);
if
(
$anchor
)
{
$href
=
$href
.
'#'
.
$anchor
;
$text
=
$text
.
'#'
.
$anchor
;
}
if
(
$this
->
getEngine
()->
isTextMode
())
{
return
$text
.
' <'
.
PhabricatorEnv
::
getProductionURI
(
$href
).
'>'
;
}
else
if
(
$this
->
getEngine
()->
isHTMLMailMode
())
{
$href
=
PhabricatorEnv
::
getProductionURI
(
$href
);
return
$this
->
renderObjectTagForMail
(
$text
,
$href
,
$handle
);
}
return
$this
->
renderObjectRef
(
$object
,
$handle
,
$anchor
,
$id
);
}
protected
function
renderObjectRef
(
$object
,
PhabricatorObjectHandle
$handle
,
$anchor
,
$id
)
{
$href
=
$this
->
getObjectHref
(
$object
,
$handle
,
$id
);
$text
=
$this
->
getObjectNameText
(
$object
,
$handle
,
$id
);
$status_closed
=
PhabricatorObjectHandle
::
STATUS_CLOSED
;
if
(
$anchor
)
{
$href
=
$href
.
'#'
.
$anchor
;
$text
=
$text
.
'#'
.
$anchor
;
}
$attr
=
array
(
'phid'
=>
$handle
->
getPHID
(),
'closed'
=>
(
$handle
->
getStatus
()
==
$status_closed
),
);
return
$this
->
renderHovertag
(
$text
,
$href
,
$attr
);
}
protected
function
renderObjectEmbedForAnyMedia
(
$object
,
PhabricatorObjectHandle
$handle
,
$options
)
{
$name
=
$handle
->
getFullName
();
$href
=
$handle
->
getURI
();
if
(
$this
->
getEngine
()->
isTextMode
())
{
return
$name
.
' <'
.
PhabricatorEnv
::
getProductionURI
(
$href
).
'>'
;
}
else
if
(
$this
->
getEngine
()->
isHTMLMailMode
())
{
$href
=
PhabricatorEnv
::
getProductionURI
(
$href
);
return
$this
->
renderObjectTagForMail
(
$name
,
$href
,
$handle
);
}
// See T13678. If we're already rendering embedded content, render a
// default reference instead to avoid cycles.
if
(
PhabricatorMarkupEngine
::
isRenderingEmbeddedContent
())
{
return
$this
->
renderDefaultObjectEmbed
(
$object
,
$handle
);
}
return
$this
->
renderObjectEmbed
(
$object
,
$handle
,
$options
);
}
protected
function
renderObjectEmbed
(
$object
,
PhabricatorObjectHandle
$handle
,
$options
)
{
return
$this
->
renderDefaultObjectEmbed
(
$object
,
$handle
);
}
final
protected
function
renderDefaultObjectEmbed
(
$object
,
PhabricatorObjectHandle
$handle
)
{
$name
=
$handle
->
getFullName
();
$href
=
$handle
->
getURI
();
$status_closed
=
PhabricatorObjectHandle
::
STATUS_CLOSED
;
$attr
=
array
(
'phid'
=>
$handle
->
getPHID
(),
'closed'
=>
(
$handle
->
getStatus
()
==
$status_closed
),
);
return
$this
->
renderHovertag
(
$name
,
$href
,
$attr
);
}
protected
function
renderObjectTagForMail
(
$text
,
$href
,
PhabricatorObjectHandle
$handle
)
{
$status_closed
=
PhabricatorObjectHandle
::
STATUS_CLOSED
;
$strikethrough
=
$handle
->
getStatus
()
==
$status_closed
?
'text-decoration: line-through;'
:
'text-decoration: none;'
;
return
phutil_tag
(
'a'
,
array
(
'href'
=>
$href
,
'style'
=>
'background-color: #e7e7e7;
border-color: #e7e7e7;
border-radius: 3px;
padding: 0 4px;
font-weight: bold;
color: black;'
.
$strikethrough
,
),
$text
);
}
protected
function
renderHovertag
(
$name
,
$href
,
array
$attr
=
array
())
{
return
id
(
new
PHUITagView
())
->
setName
(
$name
)
->
setHref
(
$href
)
->
setType
(
PHUITagView
::
TYPE_OBJECT
)
->
setPHID
(
idx
(
$attr
,
'phid'
))
->
setClosed
(
idx
(
$attr
,
'closed'
))
->
render
();
}
public
function
apply
(
$text
)
{
$text
=
preg_replace_callback
(
$this
->
getObjectEmbedPattern
(),
array
(
$this
,
'markupObjectEmbed'
),
$text
);
$text
=
preg_replace_callback
(
$this
->
getObjectReferencePattern
(),
array
(
$this
,
'markupObjectReference'
),
$text
);
return
$text
;
}
private
function
getObjectEmbedPattern
()
{
if
(
$this
->
embedPattern
===
null
)
{
$prefix
=
$this
->
getObjectNamePrefix
();
$prefix
=
preg_quote
(
$prefix
);
$id
=
$this
->
getObjectIDPattern
();
$this
->
embedPattern
=
'(
\B
{'
.
$prefix
.
'('
.
$id
.
')([,
\s
](?:[^}
\\\\
]|
\\\\
.)*)?}
\B
)u'
;
}
return
$this
->
embedPattern
;
}
private
function
getObjectReferencePattern
()
{
if
(
$this
->
referencePattern
===
null
)
{
$prefix
=
$this
->
getObjectNamePrefix
();
$prefix
=
preg_quote
(
$prefix
);
$id
=
$this
->
getObjectIDPattern
();
// If the prefix starts with a word character (like "D"), we want to
// require a word boundary so that we don't match "XD1" as "D1". If the
// prefix does not start with a word character, we want to require no word
// boundary for the same reasons. Test if the prefix starts with a word
// character.
if
(
$this
->
getObjectNamePrefixBeginsWithWordCharacter
())
{
$boundary
=
'
\\
b'
;
}
else
{
$boundary
=
'
\\
B'
;
}
// The "(?<![#@-])" prevents us from linking "#abcdef" or similar, and
// "ABC-T1" (see T5714), and from matching "@T1" as a task (it is a user)
// (see T9479).
// The "\b" allows us to link "(abcdef)" or similar without linking things
// in the middle of words.
$this
->
referencePattern
=
'((?<![#@-])'
.
$boundary
.
$prefix
.
'('
.
$id
.
')(?:#([-
\w\d
]+))?(?!
\w
))u'
;
}
return
$this
->
referencePattern
;
}
/**
* Extract matched object references from a block of text.
*
* This is intended to make it easy to write unit tests for object remarkup
* rules. Production code is not normally expected to call this method.
*
* @param string $text Text to match rules against.
* @return wild Matches, suitable for writing unit tests against.
*/
public
function
extractReferences
(
$text
)
{
$embed_matches
=
null
;
preg_match_all
(
$this
->
getObjectEmbedPattern
(),
$text
,
$embed_matches
,
PREG_OFFSET_CAPTURE
|
PREG_SET_ORDER
);
$ref_matches
=
null
;
preg_match_all
(
$this
->
getObjectReferencePattern
(),
$text
,
$ref_matches
,
PREG_OFFSET_CAPTURE
|
PREG_SET_ORDER
);
$results
=
array
();
$sets
=
array
(
'embed'
=>
$embed_matches
,
'ref'
=>
$ref_matches
,
);
foreach
(
$sets
as
$type
=>
$matches
)
{
$formatted
=
array
();
foreach
(
$matches
as
$match
)
{
$format
=
array
(
'offset'
=>
$match
[
1
][
1
],
'id'
=>
$match
[
1
][
0
],
);
if
(
isset
(
$match
[
2
][
0
]))
{
$format
[
'tail'
]
=
$match
[
2
][
0
];
}
$formatted
[]
=
$format
;
}
$results
[
$type
]
=
$formatted
;
}
return
$results
;
}
public
function
markupObjectEmbed
(
array
$matches
)
{
if
(!
$this
->
isFlatText
(
$matches
[
0
]))
{
return
$matches
[
0
];
}
// If we're rendering a table of contents, just render the raw input.
// This could perhaps be handled more gracefully but it seems unusual to
// put something like "{P123}" in a header and it's not obvious what users
// expect? See T8845.
$engine
=
$this
->
getEngine
();
if
(
$engine
->
getState
(
'toc'
))
{
return
$matches
[
0
];
}
return
$this
->
markupObject
(
array
(
'type'
=>
'embed'
,
'id'
=>
$matches
[
1
],
'options'
=>
idx
(
$matches
,
2
),
'original'
=>
$matches
[
0
],
'quote.depth'
=>
$engine
->
getQuoteDepth
(),
));
}
public
function
markupObjectReference
(
array
$matches
)
{
if
(!
$this
->
isFlatText
(
$matches
[
0
]))
{
return
$matches
[
0
];
}
// If we're rendering a table of contents, just render the monogram.
$engine
=
$this
->
getEngine
();
if
(
$engine
->
getState
(
'toc'
))
{
return
$matches
[
0
];
}
return
$this
->
markupObject
(
array
(
'type'
=>
'ref'
,
'id'
=>
$matches
[
1
],
'anchor'
=>
idx
(
$matches
,
2
),
'original'
=>
$matches
[
0
],
'quote.depth'
=>
$engine
->
getQuoteDepth
(),
));
}
private
function
markupObject
(
array
$params
)
{
if
(!
$this
->
shouldMarkupObject
(
$params
))
{
return
$params
[
'original'
];
}
$regex
=
trim
(
PhabricatorEnv
::
getEnvConfig
(
'remarkup.ignored-object-names'
));
if
(
$regex
&&
preg_match
(
$regex
,
$params
[
'original'
]))
{
return
$params
[
'original'
];
}
$engine
=
$this
->
getEngine
();
$token
=
$engine
->
storeText
(
'x'
);
$metadata_key
=
self
::
KEY_RULE_OBJECT
.
'.'
.
$this
->
getObjectNamePrefix
();
$metadata
=
$engine
->
getTextMetadata
(
$metadata_key
,
array
());
$metadata
[]
=
array
(
'token'
=>
$token
,
)
+
$params
;
$engine
->
setTextMetadata
(
$metadata_key
,
$metadata
);
return
$token
;
}
public
function
didMarkupText
()
{
$engine
=
$this
->
getEngine
();
$metadata_key
=
self
::
KEY_RULE_OBJECT
.
'.'
.
$this
->
getObjectNamePrefix
();
$metadata
=
$engine
->
getTextMetadata
(
$metadata_key
,
array
());
if
(!
$metadata
)
{
return
;
}
$ids
=
ipull
(
$metadata
,
'id'
);
$objects
=
$this
->
loadObjects
(
$ids
);
// For objects that are invalid or which the user can't see, just render
// the original text.
// TODO: We should probably distinguish between these cases and render a
// "you can't see this" state for nonvisible objects.
foreach
(
$metadata
as
$key
=>
$spec
)
{
if
(
empty
(
$objects
[
$spec
[
'id'
]]))
{
$engine
->
overwriteStoredText
(
$spec
[
'token'
],
$spec
[
'original'
]);
unset
(
$metadata
[
$key
]);
}
}
$phids
=
$engine
->
getTextMetadata
(
self
::
KEY_MENTIONED_OBJECTS
,
array
());
foreach
(
$objects
as
$object
)
{
$phids
[
$object
->
getPHID
()]
=
$object
->
getPHID
();
}
$engine
->
setTextMetadata
(
self
::
KEY_MENTIONED_OBJECTS
,
$phids
);
$handles
=
$this
->
loadHandles
(
$objects
);
foreach
(
$metadata
as
$key
=>
$spec
)
{
$handle
=
$handles
[
$spec
[
'id'
]];
$object
=
$objects
[
$spec
[
'id'
]];
switch
(
$spec
[
'type'
])
{
case
'ref'
:
$view
=
$this
->
renderObjectRefForAnyMedia
(
$object
,
$handle
,
$spec
[
'anchor'
],
$spec
[
'id'
]);
break
;
case
'embed'
:
$spec
[
'options'
]
=
$this
->
assertFlatText
(
$spec
[
'options'
]);
$view
=
$this
->
renderObjectEmbedForAnyMedia
(
$object
,
$handle
,
$spec
[
'options'
]);
break
;
}
$engine
->
overwriteStoredText
(
$spec
[
'token'
],
$view
);
}
$engine
->
setTextMetadata
(
$metadata_key
,
array
());
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mon, May 12, 1:33 PM (2 d)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
122833
Default Alt Text
PhabricatorObjectRemarkupRule.php (11 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment