Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupNoteBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupNoteBlockRule.php
index e80f3e1953..ae2ef1fe34 100644
--- a/src/infrastructure/markup/blockrule/PhutilRemarkupNoteBlockRule.php
+++ b/src/infrastructure/markup/blockrule/PhutilRemarkupNoteBlockRule.php
@@ -1,121 +1,127 @@
<?php
final class PhutilRemarkupNoteBlockRule extends PhutilRemarkupBlockRule {
public function getMatchingLineCount(array $lines, $cursor) {
$num_lines = 0;
if (preg_match($this->getRegEx(), $lines[$cursor])) {
$num_lines++;
$cursor++;
while (isset($lines[$cursor])) {
if (trim($lines[$cursor])) {
$num_lines++;
$cursor++;
continue;
}
break;
}
}
return $num_lines;
}
public function markupText($text, $children) {
$matches = array();
preg_match($this->getRegEx(), $text, $matches);
if (idx($matches, 'showword')) {
$word = $matches['showword'];
$show = true;
} else {
$word = $matches['hideword'];
$show = false;
}
$class_suffix = phutil_utf8_strtolower($word);
// This is the "(IMPORTANT)" or "NOTE:" part.
$word_part = rtrim(substr($text, 0, strlen($matches[0])));
// This is the actual text.
$text_part = substr($text, strlen($matches[0]));
$text_part = $this->applyRules(rtrim($text_part));
$text_mode = $this->getEngine()->isTextMode();
$html_mail_mode = $this->getEngine()->isHTMLMailMode();
if ($text_mode) {
return $word_part.' '.$text_part;
}
if ($show) {
$content = array(
phutil_tag(
'span',
array(
'class' => 'remarkup-note-word',
),
$word_part),
' ',
$text_part,
);
} else {
$content = $text_part;
}
if ($html_mail_mode) {
if ($class_suffix == 'important') {
$attributes = array(
'style' => 'margin: 16px 0;
padding: 12px;
border-left: 3px solid #c0392b;
background: #f4dddb;',
);
} else if ($class_suffix == 'note') {
$attributes = array(
'style' => 'margin: 16px 0;
padding: 12px;
border-left: 3px solid #2980b9;
background: #daeaf3;',
);
} else if ($class_suffix == 'warning') {
$attributes = array(
'style' => 'margin: 16px 0;
padding: 12px;
border-left: 3px solid #f1c40f;
background: #fdf5d4;',
);
}
} else {
$attributes = array(
'class' => 'remarkup-'.$class_suffix,
);
}
return phutil_tag(
'div',
$attributes,
$content);
}
private function getRegEx() {
- $words = array(
- 'NOTE',
- 'IMPORTANT',
- 'WARNING',
- );
-
- foreach ($words as $k => $word) {
- $words[$k] = preg_quote($word, '/');
+ static $regex;
+
+ if ($regex === null) {
+ $words = array(
+ 'NOTE',
+ 'IMPORTANT',
+ 'WARNING',
+ );
+
+ foreach ($words as $k => $word) {
+ $words[$k] = preg_quote($word, '/');
+ }
+ $words = implode('|', $words);
+
+ $regex =
+ '/^(?:'.
+ '(?:\((?P<hideword>'.$words.')\))'.
+ '|'.
+ '(?:(?P<showword>'.$words.'):))\s*'.
+ '/';
}
- $words = implode('|', $words);
-
- return
- '/^(?:'.
- '(?:\((?P<hideword>'.$words.')\))'.
- '|'.
- '(?:(?P<showword>'.$words.'):))\s*'.
- '/';
+
+ return $regex;
}
}
diff --git a/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php b/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php
index 0b01fd6a69..5ba60d9b8e 100644
--- a/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php
+++ b/src/infrastructure/markup/remarkup/PhutilRemarkupEngine.php
@@ -1,306 +1,356 @@
<?php
final class PhutilRemarkupEngine extends PhutilMarkupEngine {
const MODE_DEFAULT = 0;
const MODE_TEXT = 1;
const MODE_HTML_MAIL = 2;
const MAX_CHILD_DEPTH = 32;
private $blockRules = array();
private $config = array();
private $mode;
private $metadata = array();
private $states = array();
private $postprocessRules = array();
private $storage;
public function setConfig($key, $value) {
$this->config[$key] = $value;
return $this;
}
public function getConfig($key, $default = null) {
return idx($this->config, $key, $default);
}
public function setMode($mode) {
$this->mode = $mode;
return $this;
}
public function isTextMode() {
return $this->mode & self::MODE_TEXT;
}
public function isAnchorMode() {
return $this->getState('toc');
}
public function isHTMLMailMode() {
return $this->mode & self::MODE_HTML_MAIL;
}
public function setBlockRules(array $rules) {
assert_instances_of($rules, 'PhutilRemarkupBlockRule');
$rules = msortv($rules, 'getPriorityVector');
$this->blockRules = $rules;
foreach ($this->blockRules as $rule) {
$rule->setEngine($this);
}
$post_rules = array();
foreach ($this->blockRules as $block_rule) {
foreach ($block_rule->getMarkupRules() as $rule) {
$key = $rule->getPostprocessKey();
if ($key !== null) {
$post_rules[$key] = $rule;
}
}
}
$this->postprocessRules = $post_rules;
return $this;
}
public function getTextMetadata($key, $default = null) {
if (isset($this->metadata[$key])) {
return $this->metadata[$key];
}
return idx($this->metadata, $key, $default);
}
public function setTextMetadata($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
public function storeText($text) {
if ($this->isTextMode()) {
$text = phutil_safe_html($text);
}
return $this->storage->store($text);
}
public function overwriteStoredText($token, $new_text) {
if ($this->isTextMode()) {
$new_text = phutil_safe_html($new_text);
}
$this->storage->overwrite($token, $new_text);
return $this;
}
public function markupText($text) {
return $this->postprocessText($this->preprocessText($text));
}
public function pushState($state) {
if (empty($this->states[$state])) {
$this->states[$state] = 0;
}
$this->states[$state]++;
return $this;
}
public function popState($state) {
if (empty($this->states[$state])) {
throw new Exception(pht("State '%s' pushed more than popped!", $state));
}
$this->states[$state]--;
if (!$this->states[$state]) {
unset($this->states[$state]);
}
return $this;
}
public function getState($state) {
return !empty($this->states[$state]);
}
public function preprocessText($text) {
$this->metadata = array();
$this->storage = new PhutilRemarkupBlockStorage();
$blocks = $this->splitTextIntoBlocks($text);
$output = array();
foreach ($blocks as $block) {
$output[] = $this->markupBlock($block);
}
$output = $this->flattenOutput($output);
$map = $this->storage->getMap();
$this->storage = null;
$metadata = $this->metadata;
return array(
'output' => $output,
'storage' => $map,
'metadata' => $metadata,
);
}
private function splitTextIntoBlocks($text, $depth = 0) {
// Apply basic block and paragraph normalization to the text. NOTE: We don't
// strip trailing whitespace because it is semantic in some contexts,
// notably inlined diffs that the author intends to show as a code block.
$text = phutil_split_lines($text, true);
$block_rules = $this->blockRules;
$blocks = array();
$cursor = 0;
- $prev_block = array();
+
+ $can_merge = array();
+ foreach ($block_rules as $key => $block_rule) {
+ if ($block_rule instanceof PhutilRemarkupDefaultBlockRule) {
+ $can_merge[$key] = true;
+ }
+ }
+
+ $last_block = null;
+ $last_block_key = -1;
+
+ // See T13487. For very large inputs, block separation can dominate
+ // runtime. This is written somewhat clumsily to attempt to handle
+ // very large inputs as gracefully as is practical.
while (isset($text[$cursor])) {
$starting_cursor = $cursor;
- foreach ($block_rules as $block_rule) {
+ foreach ($block_rules as $block_key => $block_rule) {
$num_lines = $block_rule->getMatchingLineCount($text, $cursor);
if ($num_lines) {
- if ($blocks) {
- $prev_block = last($blocks);
- }
-
- $curr_block = array(
+ $current_block = array(
'start' => $cursor,
'num_lines' => $num_lines,
'rule' => $block_rule,
- 'is_empty' => self::isEmptyBlock($text, $cursor, $num_lines),
+ 'empty' => self::isEmptyBlock($text, $cursor, $num_lines),
'children' => array(),
+ 'merge' => isset($can_merge[$block_key]),
);
- if ($prev_block
- && self::shouldMergeBlocks($text, $prev_block, $curr_block)) {
- $blocks[last_key($blocks)]['num_lines'] += $curr_block['num_lines'];
- $blocks[last_key($blocks)]['is_empty'] =
- $blocks[last_key($blocks)]['is_empty'] && $curr_block['is_empty'];
+ $should_merge = self::shouldMergeParagraphBlocks(
+ $text,
+ $last_block,
+ $current_block);
+
+ if ($should_merge) {
+ $last_block['num_lines'] =
+ ($last_block['num_lines'] + $current_block['num_lines']);
+
+ $last_block['empty'] =
+ ($last_block['empty'] && $current_block['empty']);
+
+ $blocks[$last_block_key] = $last_block;
} else {
- $blocks[] = $curr_block;
+ $blocks[] = $current_block;
+
+ $last_block = $current_block;
+ $last_block_key++;
}
$cursor += $num_lines;
break;
}
}
if ($starting_cursor === $cursor) {
throw new Exception(pht('Block in text did not match any block rule.'));
}
}
+ // See T13487. It's common for blocks to be small, and this loop seems to
+ // measure as faster if we manually concatenate blocks than if we
+ // "array_slice()" and "implode()" blocks. This is a bit muddy.
+
foreach ($blocks as $key => $block) {
- $lines = array_slice($text, $block['start'], $block['num_lines']);
- $blocks[$key]['text'] = implode('', $lines);
+ $min = $block['start'];
+ $max = $min + $block['num_lines'];
+
+ $lines = '';
+ for ($ii = $min; $ii < $max; $ii++) {
+ $lines .= $text[$ii];
+ }
+
+ $blocks[$key]['text'] = $lines;
}
// Stop splitting child blocks apart if we get too deep. This arrests
// any blocks which have looping child rules, and stops the stack from
// exploding if someone writes a hilarious comment with 5,000 levels of
// quoted text.
if ($depth < self::MAX_CHILD_DEPTH) {
foreach ($blocks as $key => $block) {
$rule = $block['rule'];
if (!$rule->supportsChildBlocks()) {
continue;
}
list($parent_text, $child_text) = $rule->extractChildText(
$block['text']);
$blocks[$key]['text'] = $parent_text;
$blocks[$key]['children'] = $this->splitTextIntoBlocks(
$child_text,
$depth + 1);
}
}
return $blocks;
}
private function markupBlock(array $block) {
$children = array();
foreach ($block['children'] as $child) {
$children[] = $this->markupBlock($child);
}
if ($children) {
$children = $this->flattenOutput($children);
} else {
$children = null;
}
return $block['rule']->markupText($block['text'], $children);
}
private function flattenOutput(array $output) {
if ($this->isTextMode()) {
$output = implode("\n\n", $output)."\n";
} else {
$output = phutil_implode_html("\n\n", $output);
}
return $output;
}
- private static function shouldMergeBlocks($text, $prev_block, $curr_block) {
- $block_rules = ipull(array($prev_block, $curr_block), 'rule');
+ private static function shouldMergeParagraphBlocks(
+ $text,
+ $last_block,
+ $current_block) {
- $default_rule = 'PhutilRemarkupDefaultBlockRule';
- try {
- assert_instances_of($block_rules, $default_rule);
+ // If we're at the beginning of the input, we can't merge.
+ if ($last_block === null) {
+ return false;
+ }
- // If the last block was empty keep merging
- if ($prev_block['is_empty']) {
- return true;
- }
+ // If the previous block wasn't a default block, we can't merge.
+ if (!$last_block['merge']) {
+ return false;
+ }
- // If this line is blank keep merging
- if ($curr_block['is_empty']) {
- return true;
- }
+ // If the current block isn't a default block, we can't merge.
+ if (!$current_block['merge']) {
+ return false;
+ }
- // If the current line and the last line have content, keep merging
- if (strlen(trim($text[$curr_block['start'] - 1]))) {
- if (strlen(trim($text[$curr_block['start']]))) {
- return true;
- }
- }
- } catch (Exception $e) {}
+ // If the last block was empty, we definitely want to merge.
+ if ($last_block['empty']) {
+ return true;
+ }
+
+ // If this block is empty, we definitely want to merge.
+ if ($current_block['empty']) {
+ return true;
+ }
+
+ // Check if the last line of the previous block or the first line of this
+ // block have any non-whitespace text. If they both do, we're going to
+ // merge.
+
+ // If either of them are a blank line or a line with only whitespace, we
+ // do not merge: this means we've found a paragraph break.
+
+ $tail = $text[$current_block['start'] - 1];
+ $head = $text[$current_block['start']];
+ if (strlen(trim($tail)) && strlen(trim($head))) {
+ return true;
+ }
return false;
}
private static function isEmptyBlock($text, $start, $num_lines) {
for ($cursor = $start; $cursor < $start + $num_lines; $cursor++) {
if (strlen(trim($text[$cursor]))) {
return false;
}
}
return true;
}
public function postprocessText(array $dict) {
$this->metadata = idx($dict, 'metadata', array());
$this->storage = new PhutilRemarkupBlockStorage();
$this->storage->setMap(idx($dict, 'storage', array()));
foreach ($this->blockRules as $block_rule) {
$block_rule->postprocess();
}
foreach ($this->postprocessRules as $rule) {
$rule->didMarkupText();
}
return $this->restoreText(idx($dict, 'output'));
}
public function restoreText($text) {
return $this->storage->restore($text, $this->isTextMode());
}
}
diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
index 31b4e52250..7fb997b2c5 100644
--- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
+++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
@@ -1,405 +1,418 @@
<?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);
}
return $this->renderObjectEmbed($object, $handle, $options);
}
protected function renderObjectEmbed(
$object,
PhabricatorObjectHandle $handle,
$options) {
$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() {
- $prefix = $this->getObjectNamePrefix();
- $prefix = preg_quote($prefix);
- $id = $this->getObjectIDPattern();
+ if ($this->embedPattern === null) {
+ $prefix = $this->getObjectNamePrefix();
+ $prefix = preg_quote($prefix);
+ $id = $this->getObjectIDPattern();
- return '(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u';
+ $this->embedPattern =
+ '(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u';
+ }
+
+ return $this->embedPattern;
}
private function getObjectReferencePattern() {
- $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';
- }
+ 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 "(?<![#@-])" 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.
+ // 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 '((?<![#@-])'.$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 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],
));
}
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],
));
}
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

Mime Type
text/x-diff
Expires
Mon, Apr 28, 3:11 AM (17 h, 37 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
107707
Default Alt Text
(27 KB)

Event Timeline