@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove the "Phragment" application

Summary:
Ref T5479. Ref T13658. This was a contributed application from the early days of Phabricator which never had customers or users in the wild. The contributor moved on from the project many years ago.

Any capabilities in this general role would look different today. It also has one or two product name literal strings, so this is as good a time as any to remove it.

This change does not remove storage; I'll issue upgrade guidance and do that separately after some time.

Test Plan: Grepped for "phragment", got no relevant hits.

Subscribers: hach-que, PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13658, T5479

Differential Revision: https://secure.phabricator.com/D21793

-5846
-2117
externals/diff_match_patch/diff_match_patch.php
··· 1 - <?php 2 - /** 3 - * Diff Match and Patch 4 - * 5 - * Copyright 2006 Google Inc. 6 - * http://code.google.com/p/google-diff-match-patch/ 7 - * 8 - * php port by Tobias Buschor shwups.ch 9 - * 10 - * Licensed under the Apache License, Version 2.0 (the "License"); 11 - * you may not use this file except in compliance with the License. 12 - * You may obtain a copy of the License at 13 - * 14 - * http://www.apache.org/licenses/LICENSE-2.0 15 - * 16 - * Unless required by applicable law or agreed to in writing, software 17 - * distributed under the License is distributed on an "AS IS" BASIS, 18 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 - * See the License for the specific language governing permissions and 20 - * limitations under the License. 21 - */ 22 - 23 - /** 24 - * @fileoverview Computes the difference between two texts to create a patch. 25 - * Applies the patch onto another text, allowing for errors. 26 - * @author fraser@google.com (Neil Fraser) 27 - */ 28 - 29 - /** 30 - * Class containing the diff, match and patch methods. 31 - * @constructor 32 - */ 33 - class diff_match_patch { 34 - 35 - // Defaults. 36 - // Redefine these in your program to override the defaults. 37 - 38 - // Number of seconds to map a diff before giving up (0 for infinity). 39 - public $Diff_Timeout = 1.0; 40 - // Cost of an empty edit operation in terms of edit characters. 41 - public $Diff_EditCost = 4; 42 - // The size beyond which the double-ended diff activates. 43 - // Double-ending is twice as fast, but less accurate. 44 - public $Diff_DualThreshold = 32; 45 - // At what point is no match declared (0.0 = perfection, 1.0 = very loose). 46 - public $Match_Threshold = 0.5; 47 - // How far to search for a match (0 = exact location, 1000+ = broad match). 48 - // A match this many characters away from the expected location will add 49 - // 1.0 to the score (0.0 is a perfect match). 50 - public $Match_Distance = 1000; 51 - // When deleting a large block of text (over ~64 characters), how close does 52 - // the contents have to match the expected contents. (0.0 = perfection, 53 - // 1.0 = very loose). Note that Match_Threshold controls how closely the 54 - // end points of a delete need to match. 55 - public $Patch_DeleteThreshold = 0.5; 56 - // Chunk size for context length. 57 - public $Patch_Margin = 4; 58 - 59 - /** 60 - * Compute the number of bits in an int. 61 - * The normal answer for JavaScript is 32. 62 - * @return {number} Max bits 63 - 64 - function getMaxBits() { 65 - var maxbits = 0; 66 - var oldi = 1; 67 - var newi = 2; 68 - while (oldi != newi) { 69 - maxbits++; 70 - oldi = newi; 71 - newi = newi << 1; 72 - } 73 - return maxbits; 74 - } 75 - // How many bits in a number? 76 - this.Match_MaxBits = getMaxBits(); 77 - */ 78 - // DIFF FUNCTIONS 79 - 80 - /** 81 - * Find the differences between two texts. Simplifies the problem by stripping 82 - * any common prefix or suffix off the texts before diffing. 83 - * @param {string} text1 Old string to be diffed. 84 - * @param {string} text2 New string to be diffed. 85 - * @param {boolean} opt_checklines Optional speedup flag. If present and false, 86 - * then don't run a line-level diff first to identify the changed areas. 87 - * Defaults to true, which does a faster, slightly less optimal diff 88 - * @return {Array.<Array.<number|string>>} Array of diff tuples. 89 - */ 90 - function diff_main($text1, $text2, $checklines = true) { 91 - // Check for equality (speedup) 92 - if ($text1 === $text2) { 93 - return array ( array ( DIFF_EQUAL, $text1) ); 94 - } 95 - 96 - // Trim off common prefix (speedup) 97 - $commonlength = $this->diff_commonPrefix($text1, $text2); 98 - $commonprefix = mb_substr($text1, 0, $commonlength); 99 - $text1 = mb_substr($text1, $commonlength); 100 - $text2 = mb_substr($text2, $commonlength); 101 - 102 - // Trim off common suffix (speedup) 103 - $commonlength = $this->diff_commonSuffix($text1, $text2); 104 - $commonsuffix = mb_substr($text1, mb_strlen($text1) - $commonlength); 105 - $text1 = mb_substr($text1, 0, mb_strlen($text1) - $commonlength); 106 - $text2 = mb_substr($text2, 0, mb_strlen($text2) - $commonlength); 107 - 108 - // Compute the diff on the middle block 109 - $diffs = $this->diff_compute($text1, $text2, $checklines); 110 - 111 - // Restore the prefix and suffix 112 - if ($commonprefix !== '') { 113 - array_unshift($diffs, array ( DIFF_EQUAL, $commonprefix )); 114 - } 115 - if ($commonsuffix !== '') { 116 - array_push($diffs, array ( DIFF_EQUAL, $commonsuffix )); 117 - } 118 - $this->diff_cleanupMerge($diffs); 119 - return $diffs; 120 - } 121 - 122 - /** 123 - * Find the differences between two texts. Assumes that the texts do not 124 - * have any common prefix or suffix. 125 - * @param {string} text1 Old string to be diffed. 126 - * @param {string} text2 New string to be diffed. 127 - * @param {boolean} checklines Speedup flag. If false, then don't run a 128 - * line-level diff first to identify the changed areas. 129 - * If true, then run a faster, slightly less optimal diff 130 - * @return {Array.<Array.<number|string>>} Array of diff tuples. 131 - * @private 132 - */ 133 - function diff_compute($text1, $text2, $checklines) { 134 - 135 - if ($text1 === '') { 136 - // Just add some text (speedup) 137 - return array ( array ( DIFF_INSERT, $text2 ) ); 138 - } 139 - 140 - if ($text2 === '') { 141 - // Just delete some text (speedup) 142 - return array ( array ( DIFF_DELETE, $text1 ) ); 143 - } 144 - 145 - $longtext = mb_strlen($text1) > mb_strlen($text2) ? $text1 : $text2; 146 - $shorttext = mb_strlen($text1) > mb_strlen($text2) ? $text2 : $text1; 147 - $i = mb_strpos($longtext, $shorttext); 148 - if ($i !== false) { 149 - // Shorter text is inside the longer text (speedup) 150 - $diffs = array ( 151 - array ( DIFF_INSERT, mb_substr($longtext, 0, $i) ), 152 - array ( DIFF_EQUAL, $shorttext ), 153 - array ( DIFF_INSERT, mb_substr($longtext, $i +mb_strlen($shorttext)) ) 154 - ); 155 - 156 - // Swap insertions for deletions if diff is reversed. 157 - if (mb_strlen($text1) > mb_strlen($text2)) { 158 - $diffs[0][0] = $diffs[2][0] = DIFF_DELETE; 159 - } 160 - return $diffs; 161 - } 162 - $longtext = $shorttext = null; // Garbage collect 163 - 164 - // Check to see if the problem can be split in two. 165 - $hm = $this->diff_halfMatch($text1, $text2); 166 - if ($hm) { 167 - // A half-match was found, sort out the return data. 168 - $text1_a = $hm[0]; 169 - $text1_b = $hm[1]; 170 - $text2_a = $hm[2]; 171 - $text2_b = $hm[3]; 172 - $mid_common = $hm[4]; 173 - // Send both pairs off for separate processing. 174 - $diffs_a = $this->diff_main($text1_a, $text2_a, $checklines); 175 - $diffs_b = $this->diff_main($text1_b, $text2_b, $checklines); 176 - // Merge the results. 177 - return array_merge($diffs_a, array ( 178 - array ( 179 - DIFF_EQUAL, 180 - $mid_common 181 - ) 182 - ), $diffs_b); 183 - } 184 - 185 - // Perform a real diff. 186 - if ($checklines && (mb_strlen($text1) < 100 || mb_strlen($text2) < 100)) { 187 - // Too trivial for the overhead. 188 - $checklines = false; 189 - } 190 - $linearray = null; 191 - if ($checklines) { 192 - // Scan the text on a line-by-line basis first. 193 - $a = $this->diff_linesToChars($text1, $text2); 194 - $text1 = $a[0]; 195 - $text2 = $a[1]; 196 - $linearray = $a[2]; 197 - } 198 - $diffs = $this->diff_map($text1, $text2); 199 - if (!$diffs) { 200 - // No acceptable result. 201 - $diffs = array ( 202 - array ( 203 - DIFF_DELETE, 204 - $text1 205 - ), 206 - array ( 207 - DIFF_INSERT, 208 - $text2 209 - ) 210 - ); 211 - } 212 - if ($checklines) { 213 - // Convert the diff back to original text. 214 - $this->diff_charsToLines($diffs, $linearray); 215 - // Eliminate freak matches (e.g. blank lines) 216 - $this->diff_cleanupSemantic($diffs); 217 - 218 - // Rediff any replacement blocks, this time character-by-character. 219 - // Add a dummy entry at the end. 220 - array_push($diffs, array ( 221 - DIFF_EQUAL, 222 - '' 223 - )); 224 - $pointer = 0; 225 - $count_delete = 0; 226 - $count_insert = 0; 227 - $text_delete = ''; 228 - $text_insert = ''; 229 - while ($pointer < count($diffs)) { 230 - switch ($diffs[$pointer][0]) { 231 - case DIFF_INSERT : 232 - $count_insert++; 233 - $text_insert .= $diffs[$pointer][1]; 234 - break; 235 - case DIFF_DELETE : 236 - $count_delete++; 237 - $text_delete .= $diffs[$pointer][1]; 238 - break; 239 - case DIFF_EQUAL : 240 - // Upon reaching an equality, check for prior redundancies. 241 - if ($count_delete >= 1 && $count_insert >= 1) { 242 - // Delete the offending records and add the merged ones. 243 - $a = $this->diff_main($text_delete, $text_insert, false); 244 - array_splice($diffs, $pointer - $count_delete - $count_insert, $count_delete + $count_insert); 245 - 246 - $pointer = $pointer - $count_delete - $count_insert; 247 - for ($j = count($a) - 1; $j >= 0; $j--) { 248 - array_splice($diffs, $pointer, 0, array($a[$j])); 249 - } 250 - $pointer = $pointer +count($a); 251 - } 252 - $count_insert = 0; 253 - $count_delete = 0; 254 - $text_delete = ''; 255 - $text_insert = ''; 256 - break; 257 - } 258 - $pointer++; 259 - } 260 - array_pop($diffs); // Remove the dummy entry at the end. 261 - } 262 - return $diffs; 263 - } 264 - 265 - /** 266 - * Split two texts into an array of strings. Reduce the texts to a string of 267 - * hashes where each Unicode character represents one line. 268 - * @param {string} text1 First string. 269 - * @param {string} text2 Second string. 270 - * @return {Array.<string|Array.<string>>} Three element Array, containing the 271 - * encoded text1, the encoded text2 and the array of unique strings. The 272 - * zeroth element of the array of unique strings is intentionally blank. 273 - * @private 274 - */ 275 - function diff_linesToChars($text1, $text2) { 276 - $lineArray = array(); // e.g. lineArray[4] == 'Hello\n' 277 - $lineHash = array(); // e.g. lineHash['Hello\n'] == 4 278 - 279 - // '\x00' is a valid character, but various debuggers don't like it. 280 - // So we'll insert a junk entry to avoid generating a null character. 281 - $lineArray[0] = ''; 282 - 283 - $chars1 = $this->diff_linesToCharsMunge($text1, $lineArray, $lineHash); 284 - $chars2 = $this->diff_linesToCharsMunge($text2, $lineArray, $lineHash); 285 - return array ( 286 - $chars1, 287 - $chars2, 288 - $lineArray 289 - ); 290 - } 291 - 292 - /** 293 - * Split a text into an array of strings. Reduce the texts to a string of 294 - * hashes where each Unicode character represents one line. 295 - * Modifies linearray and linehash through being a closure. 296 - * @param {string} text String to encode 297 - * @return {string} Encoded string 298 - * @private 299 - */ 300 - function diff_linesToCharsMunge($text, &$lineArray, &$lineHash) { 301 - $chars = ''; 302 - // Walk the text, pulling out a mb_substring for each line. 303 - // text.split('\n') would would temporarily double our memory footprint. 304 - // Modifying text would create many large strings to garbage collect. 305 - $lineStart = 0; 306 - $lineEnd = -1; 307 - // Keeping our own length variable is faster than looking it up. 308 - $lineArrayLength = count($lineArray); 309 - while ($lineEnd < mb_strlen($text) - 1) { 310 - $lineEnd = mb_strpos($text, "\n", $lineStart); 311 - if ($lineEnd === false) { 312 - $lineEnd = mb_strlen($text) - 1; 313 - } 314 - $line = mb_substr($text, $lineStart, $lineEnd +1 -$lineStart); 315 - $lineStart = $lineEnd +1; 316 - 317 - if ( isset($lineHash[$line]) ) { 318 - $chars .= mb_chr($lineHash[$line]); 319 - } else { 320 - $chars .= mb_chr($lineArrayLength); 321 - $lineHash[$line] = $lineArrayLength; 322 - $lineArray[$lineArrayLength++] = $line; 323 - } 324 - } 325 - return $chars; 326 - } 327 - /** 328 - * Rehydrate the text in a diff from a string of line hashes to real lines of 329 - * text. 330 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 331 - * @param {Array.<string>} lineArray Array of unique strings. 332 - * @private 333 - */ 334 - function diff_charsToLines(&$diffs, $lineArray) { 335 - for ($x = 0; $x < count($diffs); $x++) { 336 - $chars = $diffs[$x][1]; 337 - $text = array (); 338 - for ($y = 0; $y < mb_strlen($chars); $y++) { 339 - $text[$y] = $lineArray[charCodeAt($chars, $y)]; 340 - } 341 - $diffs[$x][1] = implode('',$text); 342 - } 343 - } 344 - 345 - /** 346 - * Explore the intersection points between the two texts. 347 - * @param {string} text1 Old string to be diffed. 348 - * @param {string} text2 New string to be diffed. 349 - * @return {Array.<Array.<number|string>>?} Array of diff tuples or null if no 350 - * diff available. 351 - * @private 352 - */ 353 - function diff_map($text1, $text2) { 354 - // Don't run for too long. 355 - $ms_end = microtime(true) + $this->Diff_Timeout; 356 - 357 - // Cache the text lengths to prevent multiple calls. 358 - $text1_length = mb_strlen($text1); 359 - $text2_length = mb_strlen($text2); 360 - $max_d = $text1_length + $text2_length -1; 361 - $doubleEnd = $this->Diff_DualThreshold * 2 < $max_d; 362 - $v_map1 = array(); 363 - $v_map2 = array(); 364 - $v1 = array(); 365 - $v2 = array(); 366 - $v1[1] = 0; 367 - $v2[1] = 0; 368 - $x = null; 369 - $y = null; 370 - $footstep = null; // Used to track overlapping paths. 371 - $footsteps = array(); 372 - $done = false; 373 - // Safari 1.x doesn't have hasOwnProperty 374 - //? $hasOwnProperty = !!(footsteps.hasOwnProperty); 375 - // If the total number of characters is odd, then the front path will collide 376 - // with the reverse path. 377 - $front = ($text1_length + $text2_length) % 2; 378 - for ($d = 0; $d < $max_d; $d++) { 379 - // Bail out if timeout reached. 380 - if ($this->Diff_Timeout > 0 && microtime(true) > $ms_end) { 381 - return null; // zzz 382 - } 383 - 384 - // Walk the front path one step. 385 - $v_map1[$d] = array (); 386 - for ($k = -$d; $k <= $d; $k += 2) { 387 - if ($k == -$d || $k != $d && $v1[$k -1] < $v1[$k +1]) { 388 - $x = $v1[$k +1]; 389 - } else { 390 - $x = $v1[$k -1] + 1; 391 - } 392 - $y = $x - $k; 393 - if ($doubleEnd) { 394 - $footstep = $x . ',' . $y; 395 - if ($front && isset ($footsteps[$footstep])) { 396 - $done = true; 397 - } 398 - if (!$front) { 399 - $footsteps[$footstep] = $d; 400 - } 401 - } 402 - while (!$done && ($x < $text1_length) && ($y < $text2_length) && (mb_substr($text1, $x, 1) == mb_substr($text2, $y, 1)) ) { 403 - $x++; 404 - $y++; 405 - if ($doubleEnd) { 406 - $footstep = $x . ',' . $y; 407 - if ($front && isset ($footsteps[$footstep])) { 408 - $done = true; 409 - } 410 - if (!$front) { 411 - $footsteps[$footstep] = $d; 412 - } 413 - } 414 - } 415 - $v1[$k] = $x; 416 - $v_map1[$d][$x . ',' . $y] = true; 417 - if ($x == $text1_length && $y == $text2_length) { 418 - // Reached the end in single-path mode. 419 - return $this->diff_path1($v_map1, $text1, $text2); 420 - } 421 - elseif ($done) { 422 - // Front path ran over reverse path. 423 - 424 - $v_map2 = array_slice($v_map2, 0, $footsteps[$footstep] + 1); 425 - $a = $this->diff_path1($v_map1, mb_substr($text1, 0, $x), mb_substr($text2, 0, $y)); 426 - 427 - return array_merge($a, $this->diff_path2($v_map2, mb_substr($text1, $x), mb_substr($text2, $y))); 428 - } 429 - } 430 - 431 - if ($doubleEnd) { 432 - // Walk the reverse path one step. 433 - $v_map2[$d] = array(); 434 - for ($k = -$d; $k <= $d; $k += 2) { 435 - if ($k == -$d || $k != $d && $v2[$k -1] < $v2[$k +1]) { 436 - $x = $v2[$k +1]; 437 - } else { 438 - $x = $v2[$k -1] + 1; 439 - } 440 - $y = $x - $k; 441 - $footstep = ($text1_length - $x) . ',' . ($text2_length - $y); 442 - if (!$front && isset ($footsteps[$footstep])) { 443 - $done = true; 444 - } 445 - if ($front) { 446 - $footsteps[$footstep] = $d; 447 - } 448 - while (!$done && $x < $text1_length && $y < $text2_length && mb_substr($text1, $text1_length - $x -1, 1) == mb_substr($text2, $text2_length - $y -1, 1) ) { 449 - $x++; 450 - $y++; 451 - $footstep = ($text1_length - $x) . ',' . ($text2_length - $y); 452 - if (!$front && isset ($footsteps[$footstep])) { 453 - $done = true; 454 - } 455 - if ($front) { 456 - $footsteps[$footstep] = $d; 457 - } 458 - } 459 - $v2[$k] = $x; 460 - $v_map2[$d][$x . ',' . $y] = true; 461 - if ($done) { 462 - // Reverse path ran over front path. 463 - $v_map1 = array_slice($v_map1, 0, $footsteps[$footstep] + 1); 464 - $a = $this->diff_path1($v_map1, mb_substr($text1, 0, $text1_length - $x), mb_substr($text2, 0, $text2_length - $y)); 465 - return array_merge($a, $this->diff_path2($v_map2, mb_substr($text1, $text1_length - $x), mb_substr($text2, $text2_length - $y))); 466 - } 467 - } 468 - } 469 - } 470 - // Number of diffs equals number of characters, no commonality at all. 471 - return null; 472 - } 473 - 474 - /** 475 - * Work from the middle back to the start to determine the path. 476 - * @param {Array.<Object>} v_map Array of paths.ers 477 - * @param {string} text1 Old string fragment to be diffed. 478 - * @param {string} text2 New string fragment to be diffed. 479 - * @return {Array.<Array.<number|string>>} Array of diff tuples. 480 - * @private 481 - */ 482 - function diff_path1($v_map, $text1, $text2) { 483 - $path = array (); 484 - $x = mb_strlen($text1); 485 - $y = mb_strlen($text2); 486 - /** @type {number?} */ 487 - $last_op = null; 488 - for ($d = count($v_map) - 2; $d >= 0; $d--) { 489 - while (1) { 490 - if (isset ($v_map[$d][($x -1) . ',' . $y])) { 491 - $x--; 492 - if ($last_op === DIFF_DELETE) { 493 - $path[0][1] = mb_substr($text1, $x, 1) . $path[0][1]; 494 - } else { 495 - array_unshift($path, array ( 496 - DIFF_DELETE, 497 - mb_substr($text1, $x, 1) 498 - )); 499 - } 500 - $last_op = DIFF_DELETE; 501 - break; 502 - } elseif (isset ($v_map[$d][$x . ',' . ($y -1)])) { 503 - $y--; 504 - if ($last_op === DIFF_INSERT) { 505 - $path[0][1] = mb_substr($text2, $y, 1) . $path[0][1]; 506 - } else { 507 - array_unshift($path, array ( 508 - DIFF_INSERT, 509 - mb_substr($text2, $y, 1) 510 - )); 511 - } 512 - $last_op = DIFF_INSERT; 513 - break; 514 - } else { 515 - $x--; 516 - $y--; 517 - //if (text1.charAt(x) != text2.charAt(y)) { 518 - // throw new Error('No diagonal. Can\'t happen. (diff_path1)'); 519 - //} 520 - if ($last_op === DIFF_EQUAL) { 521 - $path[0][1] = mb_substr($text1, $x, 1) . $path[0][1]; 522 - } else { 523 - array_unshift($path, array ( 524 - DIFF_EQUAL, 525 - mb_substr($text1, $x, 1) 526 - )); 527 - } 528 - $last_op = DIFF_EQUAL; 529 - } 530 - } 531 - } 532 - return $path; 533 - } 534 - 535 - /** 536 - * Work from the middle back to the end to determine the path. 537 - * @param {Array.<Object>} v_map Array of paths. 538 - * @param {string} text1 Old string fragment to be diffed. 539 - * @param {string} text2 New string fragment to be diffed. 540 - * @return {Array.<Array.<number|string>>} Array of diff tuples. 541 - * @private 542 - */ 543 - function diff_path2($v_map, $text1, $text2) { 544 - $path = array (); 545 - $pathLength = 0; 546 - $x = mb_strlen($text1); 547 - $y = mb_strlen($text2); 548 - /** @type {number?} */ 549 - $last_op = null; 550 - for ($d = count($v_map) - 2; $d >= 0; $d--) { 551 - while (1) { 552 - if (isset ($v_map[$d][($x -1) . ',' . $y])) { 553 - $x--; 554 - if ($last_op === DIFF_DELETE) { 555 - $path[$pathLength -1][1] .= $text1[mb_strlen($text1) - $x -1]; 556 - } else { 557 - $path[$pathLength++] = array ( 558 - DIFF_DELETE, 559 - $text1[mb_strlen($text1) - $x -1] 560 - ); 561 - } 562 - $last_op = DIFF_DELETE; 563 - break; 564 - } 565 - elseif (isset ($v_map[$d][$x . ',' . ($y -1)])) { 566 - $y--; 567 - if ($last_op === DIFF_INSERT) { 568 - $path[$pathLength -1][1] .= $text2[mb_strlen($text2) - $y -1]; 569 - } else { 570 - $path[$pathLength++] = array ( 571 - DIFF_INSERT, 572 - $text2[mb_strlen($text2) - $y -1] 573 - ); 574 - } 575 - $last_op = DIFF_INSERT; 576 - break; 577 - } else { 578 - $x--; 579 - $y--; 580 - //if (text1.charAt(text1.length - x - 1) != 581 - // text2.charAt(text2.length - y - 1)) { 582 - // throw new Error('No diagonal. Can\'t happen. (diff_path2)'); 583 - //} 584 - if ($last_op === DIFF_EQUAL) { 585 - $path[$pathLength -1][1] .= $text1[mb_strlen($text1) - $x -1]; 586 - } else { 587 - $path[$pathLength++] = array ( 588 - DIFF_EQUAL, 589 - $text1[mb_strlen($text1) - $x -1] 590 - ); 591 - } 592 - $last_op = DIFF_EQUAL; 593 - } 594 - } 595 - } 596 - return $path; 597 - } 598 - 599 - /** 600 - * Determine the common prefix of two strings 601 - * @param {string} text1 First string. 602 - * @param {string} text2 Second string. 603 - * @return {number} The number of characters common to the start of each 604 - * string. 605 - */ 606 - function diff_commonPrefix($text1, $text2) { 607 - for ($i = 0; 1; $i++) { 608 - $t1 = mb_substr($text1, $i, 1); 609 - $t2 = mb_substr($text2, $i, 1); 610 - if($t1==='' || $t2==='' || $t1 !== $t2 ){ 611 - return $i; 612 - } 613 - } 614 - } 615 - 616 - /** 617 - * Determine the common suffix of two strings 618 - * @param {string} text1 First string. 619 - * @param {string} text2 Second string. 620 - * @return {number} The number of characters common to the end of each string. 621 - */ 622 - function diff_commonSuffix($text1, $text2) { 623 - return $this->diff_commonPrefix( strrev($text1), strrev($text2) ); 624 - } 625 - 626 - /** 627 - * Do the two texts share a mb_substring which is at least half the length of the 628 - * longer text? 629 - * @param {string} text1 First string. 630 - * @param {string} text2 Second string. 631 - * @return {Array.<string>?} Five element Array, containing the prefix of 632 - * text1, the suffix of text1, the prefix of text2, the suffix of 633 - * text2 and the common middle. Or null if there was no match. 634 - */ 635 - function diff_halfMatch($text1, $text2) { 636 - $longtext = mb_strlen($text1) > mb_strlen($text2) ? $text1 : $text2; 637 - $shorttext = mb_strlen($text1) > mb_strlen($text2) ? $text2 : $text1; 638 - if (mb_strlen($longtext) < 10 || mb_strlen($shorttext) < 1) { 639 - return null; // Pointless. 640 - } 641 - 642 - // First check if the second quarter is the seed for a half-match. 643 - $hm1 = $this->diff_halfMatchI($longtext, $shorttext, ceil(mb_strlen($longtext) / 4)); 644 - // Check again based on the third quarter. 645 - $hm2 = $this->diff_halfMatchI($longtext, $shorttext, ceil(mb_strlen($longtext) / 2)); 646 - 647 - if (!$hm1 && !$hm2) { 648 - return null; 649 - } elseif (!$hm2) { 650 - $hm = $hm1; 651 - } elseif (!$hm1) { 652 - $hm = $hm2; 653 - } else { 654 - // Both matched. Select the longest. 655 - $hm = mb_strlen($hm1[4]) > mb_strlen($hm2[4]) ? $hm1 : $hm2; 656 - } 657 - 658 - // A half-match was found, sort out the return data. 659 - if (mb_strlen($text1) > mb_strlen($text2)) { 660 - $text1_a = $hm[0]; 661 - $text1_b = $hm[1]; 662 - $text2_a = $hm[2]; 663 - $text2_b = $hm[3]; 664 - } else { 665 - $text2_a = $hm[0]; 666 - $text2_b = $hm[1]; 667 - $text1_a = $hm[2]; 668 - $text1_b = $hm[3]; 669 - } 670 - $mid_common = $hm[4]; 671 - return array( $text1_a, $text1_b, $text2_a, $text2_b, $mid_common ); 672 - } 673 - 674 - /** 675 - * Does a mb_substring of shorttext exist within longtext such that the mb_substring 676 - * is at least half the length of longtext? 677 - * Closure, but does not reference any external variables. 678 - * @param {string} longtext Longer string. 679 - * @param {string} shorttext Shorter string. 680 - * @param {number} i Start index of quarter length mb_substring within longtext 681 - * @return {Array.<string>?} Five element Array, containing the prefix of 682 - * longtext, the suffix of longtext, the prefix of shorttext, the suffix 683 - * of shorttext and the common middle. Or null if there was no match. 684 - * @private 685 - */ 686 - function diff_halfMatchI($longtext, $shorttext, $i) { 687 - // Start with a 1/4 length mb_substring at position i as a seed. 688 - $seed = mb_substr($longtext, $i, floor(mb_strlen($longtext) / 4)); 689 - 690 - $j = -1; 691 - $best_common = ''; 692 - $best_longtext_a = null; 693 - $best_longtext_b = null; 694 - $best_shorttext_a = null; 695 - $best_shorttext_b = null; 696 - while ( ($j = mb_strpos($shorttext, $seed, $j + 1)) !== false ) { 697 - $prefixLength = $this->diff_commonPrefix(mb_substr($longtext, $i), mb_substr($shorttext, $j)); 698 - $suffixLength = $this->diff_commonSuffix(mb_substr($longtext, 0, $i), mb_substr($shorttext, 0, $j)); 699 - if (mb_strlen($best_common) < $suffixLength + $prefixLength) { 700 - $best_common = mb_substr($shorttext, $j - $suffixLength, $suffixLength) . mb_substr($shorttext, $j, $prefixLength); 701 - $best_longtext_a = mb_substr($longtext, 0, $i - $suffixLength); 702 - $best_longtext_b = mb_substr($longtext, $i + $prefixLength); 703 - $best_shorttext_a = mb_substr($shorttext, 0, $j - $suffixLength); 704 - $best_shorttext_b = mb_substr($shorttext, $j + $prefixLength); 705 - } 706 - } 707 - if (mb_strlen($best_common) >= mb_strlen($longtext) / 2) { 708 - return array ( 709 - $best_longtext_a, 710 - $best_longtext_b, 711 - $best_shorttext_a, 712 - $best_shorttext_b, 713 - $best_common 714 - ); 715 - } else { 716 - return null; 717 - } 718 - } 719 - 720 - /** 721 - * Reduce the number of edits by eliminating semantically trivial equalities. 722 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 723 - */ 724 - function diff_cleanupSemantic(&$diffs) { 725 - $changes = false; 726 - $equalities = array (); // Stack of indices where equalities are found. 727 - $equalitiesLength = 0; // Keeping our own length var is faster in JS. 728 - $lastequality = null; // Always equal to equalities[equalitiesLength-1][1] 729 - $pointer = 0; // Index of current position. 730 - // Number of characters that changed prior to the equality. 731 - $length_changes1 = 0; 732 - // Number of characters that changed after the equality. 733 - $length_changes2 = 0; 734 - while ($pointer < count($diffs)) { 735 - if ($diffs[$pointer][0] == DIFF_EQUAL) { // equality found 736 - $equalities[$equalitiesLength++] = $pointer; 737 - $length_changes1 = $length_changes2; 738 - $length_changes2 = 0; 739 - $lastequality = $diffs[$pointer][1]; 740 - } else { // an insertion or deletion 741 - $length_changes2 += mb_strlen($diffs[$pointer][1]); 742 - if ($lastequality !== null && (mb_strlen($lastequality) <= $length_changes1) && (mb_strlen($lastequality) <= $length_changes2)) { 743 - // Duplicate record 744 - $zzz_diffs = array_splice($diffs, $equalities[$equalitiesLength -1], 0, array(array ( 745 - DIFF_DELETE, 746 - $lastequality 747 - ))); 748 - // Change second copy to insert. 749 - $diffs[$equalities[$equalitiesLength -1] + 1][0] = DIFF_INSERT; 750 - // Throw away the equality we just deleted. 751 - $equalitiesLength--; 752 - // Throw away the previous equality (it needs to be reevaluated). 753 - $equalitiesLength--; 754 - $pointer = $equalitiesLength > 0 ? $equalities[$equalitiesLength -1] : -1; 755 - $length_changes1 = 0; // Reset the counters. 756 - $length_changes2 = 0; 757 - $lastequality = null; 758 - $changes = true; 759 - } 760 - } 761 - $pointer++; 762 - } 763 - if ($changes) { 764 - $this->diff_cleanupMerge($diffs); 765 - } 766 - $this->diff_cleanupSemanticLossless($diffs); 767 - } 768 - 769 - /** 770 - * Look for single edits surrounded on both sides by equalities 771 - * which can be shifted sideways to align the edit to a word boundary. 772 - * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came. 773 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 774 - */ 775 - function diff_cleanupSemanticLossless(&$diffs) { 776 - 777 - $pointer = 1; 778 - // Intentionally ignore the first and last element (don't need checking). 779 - while ($pointer < count($diffs) - 1) { 780 - if ($diffs[$pointer -1][0] == DIFF_EQUAL && $diffs[$pointer +1][0] == DIFF_EQUAL) { 781 - // This is a single edit surrounded by equalities. 782 - $equality1 = $diffs[$pointer -1][1]; 783 - $edit = $diffs[$pointer][1]; 784 - $equality2 = $diffs[$pointer +1][1]; 785 - 786 - // First, shift the edit as far left as possible. 787 - $commonOffset = $this->diff_commonSuffix($equality1, $edit); 788 - if ($commonOffset !== '') { 789 - $commonString = mb_substr($edit, mb_strlen($edit) - $commonOffset); 790 - $equality1 = mb_substr($equality1, 0, mb_strlen($equality1) - $commonOffset); 791 - $edit = $commonString . mb_substr($edit, 0, mb_strlen($edit) - $commonOffset); 792 - $equality2 = $commonString . $equality2; 793 - } 794 - 795 - // Second, step character by character right, looking for the best fit. 796 - $bestEquality1 = $equality1; 797 - $bestEdit = $edit; 798 - $bestEquality2 = $equality2; 799 - $bestScore = $this->diff_cleanupSemanticScore($equality1, $edit) + $this->diff_cleanupSemanticScore($edit, $equality2); 800 - while (isset($equality2[0]) && $edit[0] === $equality2[0]) { 801 - $equality1 .= $edit[0]; 802 - $edit = mb_substr($edit, 1) . $equality2[0]; 803 - $equality2 = mb_substr($equality2, 1); 804 - $score = $this->diff_cleanupSemanticScore($equality1, $edit) + $this->diff_cleanupSemanticScore($edit, $equality2); 805 - // The >= encourages trailing rather than leading whitespace on edits. 806 - if ($score >= $bestScore) { 807 - $bestScore = $score; 808 - $bestEquality1 = $equality1; 809 - $bestEdit = $edit; 810 - $bestEquality2 = $equality2; 811 - } 812 - } 813 - 814 - if ($diffs[$pointer -1][1] != $bestEquality1) { 815 - // We have an improvement, save it back to the diff. 816 - if ($bestEquality1) { 817 - $diffs[$pointer -1][1] = $bestEquality1; 818 - } else { 819 - $zzz_diffs = array_splice($diffs, $pointer -1, 1); 820 - $pointer--; 821 - } 822 - $diffs[$pointer][1] = $bestEdit; 823 - if ($bestEquality2) { 824 - $diffs[$pointer +1][1] = $bestEquality2; 825 - } else { 826 - $zzz_diffs = array_splice($diffs, $pointer +1, 1); 827 - $pointer--; 828 - } 829 - } 830 - } 831 - $pointer++; 832 - } 833 - } 834 - 835 - /** 836 - * Given two strings, compute a score representing whether the internal 837 - * boundary falls on logical boundaries. 838 - * Scores range from 5 (best) to 0 (worst). 839 - * Closure, makes reference to regex patterns defined above. 840 - * @param {string} one First string 841 - * @param {string} two Second string 842 - * @return {number} The score. 843 - */ 844 - function diff_cleanupSemanticScore($one, $two) { 845 - // Define some regex patterns for matching boundaries. 846 - $punctuation = '/[^a-zA-Z0-9]/'; 847 - $whitespace = '/\s/'; 848 - $linebreak = '/[\r\n]/'; 849 - $blanklineEnd = '/\n\r?\n$/'; 850 - $blanklineStart = '/^\r?\n\r?\n/'; 851 - 852 - if (!$one || !$two) { 853 - // Edges are the best. 854 - return 5; 855 - } 856 - 857 - // Each port of this function behaves slightly differently due to 858 - // subtle differences in each language's definition of things like 859 - // 'whitespace'. Since this function's purpose is largely cosmetic, 860 - // the choice has been made to use each language's native features 861 - // rather than force total conformity. 862 - $score = 0; 863 - // One point for non-alphanumeric. 864 - if (preg_match($punctuation, $one[mb_strlen($one) - 1]) || preg_match($punctuation, $two[0])) { 865 - $score++; 866 - // Two points for whitespace. 867 - if (preg_match($whitespace, $one[mb_strlen($one) - 1] ) || preg_match($whitespace, $two[0])) { 868 - $score++; 869 - // Three points for line breaks. 870 - if (preg_match($linebreak, $one[mb_strlen($one) - 1]) || preg_match($linebreak, $two[0])) { 871 - $score++; 872 - // Four points for blank lines. 873 - if (preg_match($blanklineEnd, $one) || preg_match($blanklineStart, $two)) { 874 - $score++; 875 - } 876 - } 877 - } 878 - } 879 - return $score; 880 - } 881 - 882 - /** 883 - * Reduce the number of edits by eliminating operationally trivial equalities. 884 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 885 - */ 886 - function diff_cleanupEfficiency(&$diffs) { 887 - $changes = false; 888 - $equalities = array (); // Stack of indices where equalities are found. 889 - $equalitiesLength = 0; // Keeping our own length var is faster in JS. 890 - $lastequality = ''; // Always equal to equalities[equalitiesLength-1][1] 891 - $pointer = 0; // Index of current position. 892 - // Is there an insertion operation before the last equality. 893 - $pre_ins = false; 894 - // Is there a deletion operation before the last equality. 895 - $pre_del = false; 896 - // Is there an insertion operation after the last equality. 897 - $post_ins = false; 898 - // Is there a deletion operation after the last equality. 899 - $post_del = false; 900 - while ($pointer < count($diffs)) { 901 - if ($diffs[$pointer][0] == DIFF_EQUAL) { // equality found 902 - if (mb_strlen($diffs[$pointer][1]) < $this->Diff_EditCost && ($post_ins || $post_del)) { 903 - // Candidate found. 904 - $equalities[$equalitiesLength++] = $pointer; 905 - $pre_ins = $post_ins; 906 - $pre_del = $post_del; 907 - $lastequality = $diffs[$pointer][1]; 908 - } else { 909 - // Not a candidate, and can never become one. 910 - $equalitiesLength = 0; 911 - $lastequality = ''; 912 - } 913 - $post_ins = $post_del = false; 914 - } else { // an insertion or deletion 915 - if ($diffs[$pointer][0] == DIFF_DELETE) { 916 - $post_del = true; 917 - } else { 918 - $post_ins = true; 919 - } 920 - /* 921 - * Five types to be split: 922 - * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del> 923 - * <ins>A</ins>X<ins>C</ins><del>D</del> 924 - * <ins>A</ins><del>B</del>X<ins>C</ins> 925 - * <ins>A</del>X<ins>C</ins><del>D</del> 926 - * <ins>A</ins><del>B</del>X<del>C</del> 927 - */ 928 - if ($lastequality && (($pre_ins && $pre_del && $post_ins && $post_del) || ((mb_strlen($lastequality) < $this->Diff_EditCost / 2) && ($pre_ins + $pre_del + $post_ins + $post_del) == 3))) { 929 - // Duplicate record 930 - $zzz_diffs = array_splice($diffs, $equalities[$equalitiesLength -1], 0, array(array ( 931 - DIFF_DELETE, 932 - $lastequality 933 - ))); 934 - // Change second copy to insert. 935 - $diffs[$equalities[$equalitiesLength -1] + 1][0] = DIFF_INSERT; 936 - $equalitiesLength--; // Throw away the equality we just deleted; 937 - $lastequality = ''; 938 - if ($pre_ins && $pre_del) { 939 - // No changes made which could affect previous entry, keep going. 940 - $post_ins = $post_del = true; 941 - $equalitiesLength = 0; 942 - } else { 943 - $equalitiesLength--; // Throw away the previous equality; 944 - $pointer = $equalitiesLength > 0 ? $equalities[$equalitiesLength -1] : -1; 945 - $post_ins = $post_del = false; 946 - } 947 - $changes = true; 948 - } 949 - } 950 - $pointer++; 951 - } 952 - 953 - if ($changes) { 954 - $this->diff_cleanupMerge($diffs); 955 - } 956 - } 957 - 958 - /** 959 - * Reorder and merge like edit sections. Merge equalities. 960 - * Any edit section can move as long as it doesn't cross an equality. 961 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 962 - */ 963 - function diff_cleanupMerge(&$diffs) { 964 - array_push($diffs, array ( DIFF_EQUAL, '' )); // Add a dummy entry at the end. 965 - $pointer = 0; 966 - $count_delete = 0; 967 - $count_insert = 0; 968 - $text_delete = ''; 969 - $text_insert = ''; 970 - $commonlength = null; 971 - while ($pointer < count($diffs)) { 972 - switch ($diffs[$pointer][0]) { 973 - case DIFF_INSERT : 974 - $count_insert++; 975 - $text_insert .= $diffs[$pointer][1]; 976 - $pointer++; 977 - break; 978 - case DIFF_DELETE : 979 - $count_delete++; 980 - $text_delete .= $diffs[$pointer][1]; 981 - $pointer++; 982 - break; 983 - case DIFF_EQUAL : 984 - // Upon reaching an equality, check for prior redundancies. 985 - if ($count_delete !== 0 || $count_insert !== 0) { 986 - if ($count_delete !== 0 && $count_insert !== 0) { 987 - // Factor out any common prefixies. 988 - $commonlength = $this->diff_commonPrefix($text_insert, $text_delete); 989 - if ($commonlength !== 0) { 990 - if (($pointer - $count_delete - $count_insert) > 0 && $diffs[$pointer - $count_delete - $count_insert -1][0] == DIFF_EQUAL) { 991 - $diffs[$pointer - $count_delete - $count_insert -1][1] .= mb_substr($text_insert, 0, $commonlength); 992 - } else { 993 - array_splice($diffs, 0, 0, array(array ( 994 - DIFF_EQUAL, 995 - mb_substr($text_insert, 0, $commonlength) 996 - ))); 997 - $pointer++; 998 - } 999 - $text_insert = mb_substr($text_insert, $commonlength); 1000 - $text_delete = mb_substr($text_delete, $commonlength); 1001 - } 1002 - // Factor out any common suffixies. 1003 - $commonlength = $this->diff_commonSuffix($text_insert, $text_delete); 1004 - if ($commonlength !== 0) { 1005 - $diffs[$pointer][1] = mb_substr($text_insert, mb_strlen($text_insert) - $commonlength) . $diffs[$pointer][1]; 1006 - $text_insert = mb_substr($text_insert, 0, mb_strlen($text_insert) - $commonlength); 1007 - $text_delete = mb_substr($text_delete, 0, mb_strlen($text_delete) - $commonlength); 1008 - } 1009 - } 1010 - // Delete the offending records and add the merged ones. 1011 - if ($count_delete === 0) { 1012 - array_splice($diffs, $pointer-$count_delete-$count_insert, $count_delete+$count_insert, array(array( 1013 - DIFF_INSERT, 1014 - $text_insert 1015 - ))); 1016 - } elseif ($count_insert === 0) { 1017 - array_splice($diffs, $pointer-$count_delete-$count_insert, $count_delete+$count_insert, array(array( 1018 - DIFF_DELETE, 1019 - $text_delete 1020 - ))); 1021 - } else { 1022 - array_splice($diffs, $pointer-$count_delete-$count_insert, $count_delete+$count_insert, array(array( 1023 - DIFF_DELETE, 1024 - $text_delete 1025 - ), array ( 1026 - DIFF_INSERT, 1027 - $text_insert 1028 - ))); 1029 - } 1030 - $pointer = $pointer - $count_delete - $count_insert + ($count_delete ? 1 : 0) + ($count_insert ? 1 : 0) + 1; 1031 - } elseif ($pointer !== 0 && $diffs[$pointer -1][0] == DIFF_EQUAL) { 1032 - // Merge this equality with the previous one. 1033 - $diffs[$pointer -1][1] .= $diffs[$pointer][1]; 1034 - array_splice($diffs, $pointer, 1); 1035 - } else { 1036 - $pointer++; 1037 - } 1038 - $count_insert = 0; 1039 - $count_delete = 0; 1040 - $text_delete = ''; 1041 - $text_insert = ''; 1042 - break; 1043 - } 1044 - } 1045 - if ($diffs[count($diffs) - 1][1] === '') { 1046 - array_pop($diffs); // Remove the dummy entry at the end. 1047 - } 1048 - 1049 - // Second pass: look for single edits surrounded on both sides by equalities 1050 - // which can be shifted sideways to eliminate an equality. 1051 - // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC 1052 - $changes = false; 1053 - $pointer = 1; 1054 - // Intentionally ignore the first and last element (don't need checking). 1055 - while ($pointer < count($diffs) - 1) { 1056 - if ($diffs[$pointer-1][0] == DIFF_EQUAL && $diffs[$pointer+1][0] == DIFF_EQUAL) { 1057 - // This is a single edit surrounded by equalities. 1058 - if ( mb_substr($diffs[$pointer][1], mb_strlen($diffs[$pointer][1]) - mb_strlen($diffs[$pointer -1][1])) == $diffs[$pointer -1][1]) { 1059 - // Shift the edit over the previous equality. 1060 - $diffs[$pointer][1] = $diffs[$pointer -1][1] . mb_substr($diffs[$pointer][1], 0, mb_strlen($diffs[$pointer][1]) - mb_strlen($diffs[$pointer -1][1])); 1061 - $diffs[$pointer +1][1] = $diffs[$pointer -1][1] . $diffs[$pointer +1][1]; 1062 - array_splice($diffs, $pointer -1, 1); 1063 - $changes = true; 1064 - } elseif (mb_substr($diffs[$pointer][1], 0, mb_strlen($diffs[$pointer +1][1])) == $diffs[$pointer +1][1]) { 1065 - // Shift the edit over the next equality. 1066 - $diffs[$pointer -1][1] .= $diffs[$pointer +1][1]; 1067 - 1068 - $diffs[$pointer][1] = mb_substr($diffs[$pointer][1], mb_strlen($diffs[$pointer +1][1])) . $diffs[$pointer +1][1]; 1069 - array_splice($diffs, $pointer +1, 1); 1070 - $changes = true; 1071 - } 1072 - } 1073 - $pointer++; 1074 - } 1075 - // If shifts were made, the diff needs reordering and another shift sweep. 1076 - if ($changes) { 1077 - $this->diff_cleanupMerge($diffs); 1078 - } 1079 - } 1080 - 1081 - /** 1082 - * loc is a location in text1, compute and return the equivalent location in 1083 - * text2. 1084 - * e.g. 'The cat' vs 'The big cat', 1->1, 5->8 1085 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 1086 - * @param {number} loc Location within text1. 1087 - * @return {number} Location within text2. 1088 - */ 1089 - function diff_xIndex($diffs, $loc) { 1090 - $chars1 = 0; 1091 - $chars2 = 0; 1092 - $last_chars1 = 0; 1093 - $last_chars2 = 0; 1094 - for ($x = 0; $x < count($diffs); $x++) { 1095 - if ($diffs[$x][0] !== DIFF_INSERT) { // Equality or deletion. 1096 - $chars1 += mb_strlen($diffs[$x][1]); 1097 - } 1098 - if ($diffs[$x][0] !== DIFF_DELETE) { // Equality or insertion. 1099 - $chars2 += mb_strlen($diffs[$x][1]); 1100 - } 1101 - if ($chars1 > $loc) { // Overshot the location. 1102 - break; 1103 - } 1104 - $last_chars1 = $chars1; 1105 - $last_chars2 = $chars2; 1106 - } 1107 - // Was the location was deleted? 1108 - if (count($diffs) != $x && $diffs[$x][0] === DIFF_DELETE) { 1109 - return $last_chars2; 1110 - } 1111 - // Add the remaining character length. 1112 - return $last_chars2 + ($loc - $last_chars1); 1113 - } 1114 - 1115 - /** 1116 - * Convert a diff array into a pretty HTML report. 1117 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 1118 - * @return {string} HTML representation. 1119 - */ 1120 - function diff_prettyHtml($diffs) { 1121 - $html = array (); 1122 - $i = 0; 1123 - for ($x = 0; $x < count($diffs); $x++) { 1124 - $op = $diffs[$x][0]; // Operation (insert, delete, equal) 1125 - $data = $diffs[$x][1]; // Text of change. 1126 - $text = preg_replace(array ( 1127 - '/&/', 1128 - '/</', 1129 - '/>/', 1130 - "/\n/" 1131 - ), array ( 1132 - '&amp;', 1133 - '&lt;', 1134 - '&gt;', 1135 - '&para;<BR>' 1136 - ), $data); 1137 - 1138 - switch ($op) { 1139 - case DIFF_INSERT : 1140 - $html[$x] = '<INS STYLE="background:#E6FFE6;" TITLE="i=' . $i . '">' . $text . '</INS>'; 1141 - break; 1142 - case DIFF_DELETE : 1143 - $html[$x] = '<DEL STYLE="background:#FFE6E6;" TITLE="i=' . $i . '">' . $text . '</DEL>'; 1144 - break; 1145 - case DIFF_EQUAL : 1146 - $html[$x] = '<SPAN TITLE="i=' . $i . '">' . $text . '</SPAN>'; 1147 - break; 1148 - } 1149 - if ($op !== DIFF_DELETE) { 1150 - $i += mb_strlen($data); 1151 - } 1152 - } 1153 - return implode('',$html); 1154 - } 1155 - 1156 - /** 1157 - * Compute and return the source text (all equalities and deletions). 1158 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 1159 - * @return {string} Source text. 1160 - */ 1161 - function diff_text1($diffs) { 1162 - $text = array (); 1163 - for ($x = 0; $x < count($diffs); $x++) { 1164 - if ($diffs[$x][0] !== DIFF_INSERT) { 1165 - $text[$x] = $diffs[$x][1]; 1166 - } 1167 - } 1168 - return implode('',$text); 1169 - } 1170 - 1171 - /** 1172 - * Compute and return the destination text (all equalities and insertions). 1173 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 1174 - * @return {string} Destination text. 1175 - */ 1176 - function diff_text2($diffs) { 1177 - $text = array (); 1178 - for ($x = 0; $x < count($diffs); $x++) { 1179 - if ($diffs[$x][0] !== DIFF_DELETE) { 1180 - $text[$x] = $diffs[$x][1]; 1181 - } 1182 - } 1183 - return implode('',$text); 1184 - } 1185 - 1186 - /** 1187 - * Compute the Levenshtein distance; the number of inserted, deleted or 1188 - * substituted characters. 1189 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 1190 - * @return {number} Number of changes. 1191 - */ 1192 - function diff_levenshtein($diffs) { 1193 - $levenshtein = 0; 1194 - $insertions = 0; 1195 - $deletions = 0; 1196 - for ($x = 0; $x < count($diffs); $x++) { 1197 - $op = $diffs[$x][0]; 1198 - $data = $diffs[$x][1]; 1199 - switch ($op) { 1200 - case DIFF_INSERT : 1201 - $insertions += mb_strlen($data); 1202 - break; 1203 - case DIFF_DELETE : 1204 - $deletions += mb_strlen($data); 1205 - break; 1206 - case DIFF_EQUAL : 1207 - // A deletion and an insertion is one substitution. 1208 - $levenshtein += max($insertions, $deletions); 1209 - $insertions = 0; 1210 - $deletions = 0; 1211 - break; 1212 - } 1213 - } 1214 - $levenshtein += max($insertions, $deletions); 1215 - return $levenshtein; 1216 - } 1217 - 1218 - /** 1219 - * Crush the diff into an encoded string which describes the operations 1220 - * required to transform text1 into text2. 1221 - * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. 1222 - * Operations are tab-separated. Inserted text is escaped using %xx notation. 1223 - * @param {Array.<Array.<number|string>>} diffs Array of diff tuples. 1224 - * @return {string} Delta text. 1225 - */ 1226 - function diff_toDelta($diffs) { 1227 - $text = array (); 1228 - for ($x = 0; $x < count($diffs); $x++) { 1229 - switch ($diffs[$x][0]) { 1230 - case DIFF_INSERT : 1231 - $text[$x] = '+' .encodeURI($diffs[$x][1]); 1232 - break; 1233 - case DIFF_DELETE : 1234 - $text[$x] = '-' .mb_strlen($diffs[$x][1]); 1235 - break; 1236 - case DIFF_EQUAL : 1237 - $text[$x] = '=' .mb_strlen($diffs[$x][1]); 1238 - break; 1239 - } 1240 - } 1241 - return str_replace('%20', ' ', implode("\t", $text)); 1242 - } 1243 - 1244 - /** 1245 - * Given the original text1, and an encoded string which describes the 1246 - * operations required to transform text1 into text2, compute the full diff. 1247 - * @param {string} text1 Source string for the diff. 1248 - * @param {string} delta Delta text. 1249 - * @return {Array.<Array.<number|string>>} Array of diff tuples. 1250 - * @throws {Error} If invalid input. 1251 - */ 1252 - function diff_fromDelta($text1, $delta) { 1253 - $diffs = array (); 1254 - $diffsLength = 0; // Keeping our own length var is faster in JS. 1255 - $pointer = 0; // Cursor in text1 1256 - $tokens = preg_split("/\t/", $delta); 1257 - 1258 - for ($x = 0; $x < count($tokens); $x++) { 1259 - // Each token begins with a one character parameter which specifies the 1260 - // operation of this token (delete, insert, equality). 1261 - $param = mb_substr($tokens[$x], 1); 1262 - switch ($tokens[$x][0]) { 1263 - case '+' : 1264 - try { 1265 - $diffs[$diffsLength++] = array ( 1266 - DIFF_INSERT, 1267 - decodeURI($param) 1268 - ); 1269 - } catch (Exception $ex) { 1270 - echo_Exception('Illegal escape in diff_fromDelta: ' . $param); 1271 - // Malformed URI sequence. 1272 - } 1273 - break; 1274 - case '-' : 1275 - // Fall through. 1276 - case '=' : 1277 - $n = (int) $param; 1278 - if ($n < 0) { 1279 - echo_Exception('Invalid number in diff_fromDelta: ' . $param); 1280 - } 1281 - $text = mb_substr($text1, $pointer, $n); 1282 - $pointer += $n; 1283 - if ($tokens[$x][0] == '=') { 1284 - $diffs[$diffsLength++] = array ( 1285 - DIFF_EQUAL, 1286 - $text 1287 - ); 1288 - } else { 1289 - $diffs[$diffsLength++] = array ( 1290 - DIFF_DELETE, 1291 - $text 1292 - ); 1293 - } 1294 - break; 1295 - default : 1296 - // Blank tokens are ok (from a trailing \t). 1297 - // Anything else is an error. 1298 - if ($tokens[$x]) { 1299 - echo_Exception('Invalid diff operation in diff_fromDelta: ' . $tokens[$x]); 1300 - } 1301 - } 1302 - } 1303 - if ($pointer != mb_strlen($text1)) { 1304 - // throw new Exception('Delta length (' . $pointer . ') does not equal source text length (' . mb_strlen($text1) . ').'); 1305 - echo_Exception('Delta length (' . $pointer . ') does not equal source text length (' . mb_strlen($text1) . ').'); 1306 - } 1307 - return $diffs; 1308 - } 1309 - 1310 - // MATCH FUNCTIONS 1311 - 1312 - /** 1313 - * Locate the best instance of 'pattern' in 'text' near 'loc'. 1314 - * @param {string} text The text to search. 1315 - * @param {string} pattern The pattern to search for. 1316 - * @param {number} loc The location to search around. 1317 - * @return {number} Best match index or -1. 1318 - */ 1319 - function match_main($text, $pattern, $loc) { 1320 - $loc = max(0, min($loc, mb_strlen($text))); 1321 - if ($text == $pattern) { 1322 - // Shortcut (potentially not guaranteed by the algorithm) 1323 - return 0; 1324 - } 1325 - elseif (!mb_strlen($text)) { 1326 - // Nothing to match. 1327 - return -1; 1328 - } 1329 - elseif (mb_substr($text, $loc, mb_strlen($pattern)) == $pattern) { 1330 - // Perfect match at the perfect spot! (Includes case of null pattern) 1331 - return $loc; 1332 - } else { 1333 - // Do a fuzzy compare. 1334 - return $this->match_bitap($text, $pattern, $loc); 1335 - } 1336 - } 1337 - 1338 - /** 1339 - * Locate the best instance of 'pattern' in 'text' near 'loc' using the 1340 - * Bitap algorithm. 1341 - * @param {string} text The text to search. 1342 - * @param {string} pattern The pattern to search for. 1343 - * @param {number} loc The location to search around. 1344 - * @return {number} Best match index or -1. 1345 - * @private 1346 - */ 1347 - function match_bitap($text, $pattern, $loc) { 1348 - if (mb_strlen($pattern) > Match_MaxBits) { 1349 - echo_Exception('Pattern too long for this browser.'); 1350 - } 1351 - 1352 - // Initialise the alphabet. 1353 - $s = $this->match_alphabet($pattern); 1354 - 1355 - // Highest score beyond which we give up. 1356 - $score_threshold = $this->Match_Threshold; 1357 - 1358 - // Is there a nearby exact match? (speedup) 1359 - $best_loc = mb_strpos($text, $pattern, $loc); 1360 - if ($best_loc !== false) { 1361 - $score_threshold = min($this->match_bitapScore(0, $best_loc, $pattern, $loc), $score_threshold); 1362 - } 1363 - 1364 - // What about in the other direction? (speedup) 1365 - $best_loc = mb_strrpos( $text, $pattern, min($loc + mb_strlen($pattern), mb_strlen($text)) ); 1366 - if ($best_loc !== false) { 1367 - $score_threshold = min($this->match_bitapScore(0, $best_loc, $pattern, $loc), $score_threshold); 1368 - } 1369 - 1370 - // Initialise the bit arrays. 1371 - $matchmask = 1 << (mb_strlen($pattern) - 1); 1372 - $best_loc = -1; 1373 - 1374 - $bin_min = null; 1375 - $bin_mid = null; 1376 - $bin_max = mb_strlen($pattern) + mb_strlen($text); 1377 - $last_rd = null; 1378 - for ($d = 0; $d < mb_strlen($pattern); $d++) { 1379 - // Scan for the best match; each iteration allows for one more error. 1380 - // Run a binary search to determine how far from 'loc' we can stray at this 1381 - // error level. 1382 - $bin_min = 0; 1383 - $bin_mid = $bin_max; 1384 - while ($bin_min < $bin_mid) { 1385 - if ($this->match_bitapScore($d, $loc + $bin_mid, $pattern, $loc) <= $score_threshold) { 1386 - $bin_min = $bin_mid; 1387 - } else { 1388 - $bin_max = $bin_mid; 1389 - } 1390 - $bin_mid = floor(($bin_max - $bin_min) / 2 + $bin_min); 1391 - } 1392 - // Use the result from this iteration as the maximum for the next. 1393 - $bin_max = $bin_mid; 1394 - $start = max(1, $loc - $bin_mid +1); 1395 - $finish = min($loc + $bin_mid, mb_strlen($text)) + mb_strlen($pattern); 1396 - 1397 - $rd = Array ( 1398 - $finish +2 1399 - ); 1400 - $rd[$finish +1] = (1 << $d) - 1; 1401 - for ($j = $finish; $j >= $start; $j--) { 1402 - // The alphabet (s) is a sparse hash, so the following line generates 1403 - // warnings. 1404 - @ $charMatch = $s[ $text[$j -1] ]; 1405 - if ($d === 0) { // First pass: exact match. 1406 - $rd[$j] = (($rd[$j +1] << 1) | 1) & $charMatch; 1407 - } else { // Subsequent passes: fuzzy match. 1408 - $rd[$j] = (($rd[$j +1] << 1) | 1) & $charMatch | ((($last_rd[$j +1] | $last_rd[$j]) << 1) | 1) | $last_rd[$j +1]; 1409 - } 1410 - if ($rd[$j] & $matchmask) { 1411 - $score = $this->match_bitapScore($d, $j -1, $pattern, $loc); 1412 - // This match will almost certainly be better than any existing match. 1413 - // But check anyway. 1414 - if ($score <= $score_threshold) { 1415 - // Told you so. 1416 - $score_threshold = $score; 1417 - $best_loc = $j -1; 1418 - if ($best_loc > $loc) { 1419 - // When passing loc, don't exceed our current distance from loc. 1420 - $start = max(1, 2 * $loc - $best_loc); 1421 - } else { 1422 - // Already passed loc, downhill from here on in. 1423 - break; 1424 - } 1425 - } 1426 - } 1427 - } 1428 - // No hope for a (better) match at greater error levels. 1429 - if ($this->match_bitapScore($d +1, $loc, $pattern, $loc) > $score_threshold) { 1430 - break; 1431 - } 1432 - $last_rd = $rd; 1433 - } 1434 - return (int)$best_loc; 1435 - } 1436 - 1437 - /** 1438 - * Compute and return the score for a match with e errors and x location. 1439 - * Accesses loc and pattern through being a closure. 1440 - * @param {number} e Number of errors in match. 1441 - * @param {number} x Location of match. 1442 - * @return {number} Overall score for match (0.0 = good, 1.0 = bad). 1443 - * @private 1444 - */ 1445 - function match_bitapScore($e, $x, $pattern, $loc) { 1446 - $accuracy = $e / mb_strlen($pattern); 1447 - $proximity = abs($loc - $x); 1448 - if (!$this->Match_Distance) { 1449 - // Dodge divide by zero error. 1450 - return $proximity ? 1.0 : $accuracy; 1451 - } 1452 - return $accuracy + ($proximity / $this->Match_Distance); 1453 - } 1454 - 1455 - /** 1456 - * Initialise the alphabet for the Bitap algorithm. 1457 - * @param {string} pattern The text to encode. 1458 - * @return {Object} Hash of character locations. 1459 - * @private 1460 - */ 1461 - function match_alphabet($pattern) { 1462 - $s = array (); 1463 - for ($i = 0; $i < mb_strlen($pattern); $i++) { 1464 - $s[ $pattern[$i] ] = 0; 1465 - } 1466 - for ($i = 0; $i < mb_strlen($pattern); $i++) { 1467 - $s[ $pattern[$i] ] |= 1 << (mb_strlen($pattern) - $i - 1); 1468 - } 1469 - return $s; 1470 - } 1471 - 1472 - // PATCH FUNCTIONS 1473 - 1474 - /** 1475 - * Increase the context until it is unique, 1476 - * but don't let the pattern expand beyond Match_MaxBits. 1477 - * @param {patch_obj} patch The patch to grow. 1478 - * @param {string} text Source text. 1479 - * @private 1480 - */ 1481 - function patch_addContext($patch, $text) { 1482 - $pattern = mb_substr($text, $patch->start2, $patch->length1 ); 1483 - $previousPattern = null; 1484 - $padding = 0; 1485 - $i = 0; 1486 - while ( 1487 - ( mb_strlen($pattern) === 0 // Javascript's indexOf/lastIndexOd return 0/strlen respectively if pattern = '' 1488 - || mb_strpos($text, $pattern) !== mb_strrpos($text, $pattern) 1489 - ) 1490 - && $pattern !== $previousPattern // avoid infinte loop 1491 - && mb_strlen($pattern) < Match_MaxBits - $this->Patch_Margin - $this->Patch_Margin ) { 1492 - $padding += $this->Patch_Margin; 1493 - $previousPattern = $pattern; 1494 - $pattern = mb_substr($text, max($patch->start2 - $padding,0), ($patch->start2 + $patch->length1 + $padding) - max($patch->start2 - $padding,0) ); 1495 - } 1496 - // Add one chunk for good luck. 1497 - $padding += $this->Patch_Margin; 1498 - // Add the prefix. 1499 - $prefix = mb_substr($text, max($patch->start2 - $padding,0), $patch->start2 - max($patch->start2 - $padding,0) ); 1500 - if ($prefix!=='') { 1501 - array_unshift($patch->diffs, array ( 1502 - DIFF_EQUAL, 1503 - $prefix 1504 - )); 1505 - } 1506 - // Add the suffix. 1507 - $suffix = mb_substr($text, $patch->start2 + $patch->length1, ($patch->start2 + $patch->length1 + $padding) - ($patch->start2 + $patch->length1) ); 1508 - if ($suffix!=='') { 1509 - array_push($patch->diffs, array ( 1510 - DIFF_EQUAL, 1511 - $suffix 1512 - )); 1513 - } 1514 - 1515 - // Roll back the start points. 1516 - $patch->start1 -= mb_strlen($prefix); 1517 - $patch->start2 -= mb_strlen($prefix); 1518 - // Extend the lengths. 1519 - $patch->length1 += mb_strlen($prefix) + mb_strlen($suffix); 1520 - $patch->length2 += mb_strlen($prefix) + mb_strlen($suffix); 1521 - } 1522 - 1523 - /** 1524 - * Compute a list of patches to turn text1 into text2. 1525 - * Use diffs if provided, otherwise compute it ourselves. 1526 - * There are four ways to call this function, depending on what data is 1527 - * available to the caller: 1528 - * Method 1: 1529 - * a = text1, b = text2 1530 - * Method 2: 1531 - * a = diffs 1532 - * Method 3 (optimal): 1533 - * a = text1, b = diffs 1534 - * Method 4 (deprecated, use method 3): 1535 - * a = text1, b = text2, c = diffs 1536 - * 1537 - * @param {string|Array.<Array.<number|string>>} a text1 (methods 1,3,4) or 1538 - * Array of diff tuples for text1 to text2 (method 2). 1539 - * @param {string|Array.<Array.<number|string>>} opt_b text2 (methods 1,4) or 1540 - * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). 1541 - * @param {string|Array.<Array.<number|string>>} opt_c Array of diff tuples for 1542 - * text1 to text2 (method 4) or undefined (methods 1,2,3). 1543 - * @return {Array.<patch_obj>} Array of patch objects. 1544 - */ 1545 - function patch_make($a, $opt_b = null, $opt_c = null ) { 1546 - if (is_string($a) && is_string($opt_b) && $opt_c === null ) { 1547 - // Method 1: text1, text2 1548 - // Compute diffs from text1 and text2. 1549 - $text1 = $a; 1550 - $diffs = $this->diff_main($text1, $opt_b, true); 1551 - if ( count($diffs) > 2) { 1552 - $this->diff_cleanupSemantic($diffs); 1553 - $this->diff_cleanupEfficiency($diffs); 1554 - } 1555 - } elseif ( is_array($a) && $opt_b === null && $opt_c === null) { 1556 - // Method 2: diffs 1557 - // Compute text1 from diffs. 1558 - $diffs = $a; 1559 - $text1 = $this->diff_text1($diffs); 1560 - } elseif ( is_string($a) && is_array($opt_b) && $opt_c === null) { 1561 - // Method 3: text1, diffs 1562 - $text1 = $a; 1563 - $diffs = $opt_b; 1564 - } elseif ( is_string($a) && is_string($opt_b) && is_array($opt_c) ) { 1565 - // Method 4: text1, text2, diffs 1566 - // text2 is not used. 1567 - $text1 = $a; 1568 - $diffs = $opt_c; 1569 - } else { 1570 - echo_Exception('Unknown call format to patch_make.'); 1571 - } 1572 - 1573 - if ( count($diffs) === 0) { 1574 - return array(); // Get rid of the null case. 1575 - } 1576 - $patches = array(); 1577 - $patch = new patch_obj(); 1578 - $patchDiffLength = 0; // Keeping our own length var is faster in JS. 1579 - $char_count1 = 0; // Number of characters into the text1 string. 1580 - $char_count2 = 0; // Number of characters into the text2 string. 1581 - // Start with text1 (prepatch_text) and apply the diffs until we arrive at 1582 - // text2 (postpatch_text). We recreate the patches one by one to determine 1583 - // context info. 1584 - $prepatch_text = $text1; 1585 - $postpatch_text = $text1; 1586 - for ($x = 0; $x < count($diffs); $x++) { 1587 - $diff_type = $diffs[$x][0]; 1588 - $diff_text = $diffs[$x][1]; 1589 - 1590 - if (!$patchDiffLength && $diff_type !== DIFF_EQUAL) { 1591 - // A new patch starts here. 1592 - $patch->start1 = $char_count1; 1593 - $patch->start2 = $char_count2; 1594 - } 1595 - 1596 - switch ($diff_type) { 1597 - case DIFF_INSERT : 1598 - $patch->diffs[$patchDiffLength++] = $diffs[$x]; 1599 - $patch->length2 += mb_strlen($diff_text); 1600 - $postpatch_text = mb_substr($postpatch_text, 0, $char_count2) . $diff_text . mb_substr($postpatch_text,$char_count2); 1601 - break; 1602 - case DIFF_DELETE : 1603 - $patch->length1 += mb_strlen($diff_text); 1604 - $patch->diffs[$patchDiffLength++] = $diffs[$x]; 1605 - $postpatch_text = mb_substr($postpatch_text, 0, $char_count2) . mb_substr($postpatch_text, $char_count2 + mb_strlen($diff_text) ); 1606 - break; 1607 - case DIFF_EQUAL : 1608 - if ( mb_strlen($diff_text) <= 2 * $this->Patch_Margin && $patchDiffLength && count($diffs) != $x + 1) { 1609 - // Small equality inside a patch. 1610 - $patch->diffs[$patchDiffLength++] = $diffs[$x]; 1611 - $patch->length1 += mb_strlen($diff_text); 1612 - $patch->length2 += mb_strlen($diff_text); 1613 - } elseif ( mb_strlen($diff_text) >= 2 * $this->Patch_Margin ) { 1614 - // Time for a new patch. 1615 - if ($patchDiffLength) { 1616 - $this->patch_addContext($patch, $prepatch_text); 1617 - array_push($patches,$patch); 1618 - $patch = new patch_obj(); 1619 - $patchDiffLength = 0; 1620 - // Unlike Unidiff, our patch lists have a rolling context. 1621 - // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff 1622 - // Update prepatch text & pos to reflect the application of the 1623 - // just completed patch. 1624 - $prepatch_text = $postpatch_text; 1625 - $char_count1 = $char_count2; 1626 - } 1627 - } 1628 - break; 1629 - } 1630 - 1631 - // Update the current character count. 1632 - if ($diff_type !== DIFF_INSERT) { 1633 - $char_count1 += mb_strlen($diff_text); 1634 - } 1635 - if ($diff_type !== DIFF_DELETE) { 1636 - $char_count2 += mb_strlen($diff_text); 1637 - } 1638 - } 1639 - // Pick up the leftover patch if not empty. 1640 - if ($patchDiffLength) { 1641 - $this->patch_addContext($patch, $prepatch_text); 1642 - array_push($patches, $patch); 1643 - } 1644 - 1645 - return $patches; 1646 - } 1647 - 1648 - /** 1649 - * Given an array of patches, return another array that is identical. 1650 - * @param {Array.<patch_obj>} patches Array of patch objects. 1651 - * @return {Array.<patch_obj>} Array of patch objects. 1652 - */ 1653 - function patch_deepCopy($patches) { 1654 - // Making deep copies is hard in JavaScript. 1655 - $patchesCopy = array(); 1656 - for ($x = 0; $x < count($patches); $x++) { 1657 - $patch = $patches[$x]; 1658 - $patchCopy = new patch_obj(); 1659 - for ($y = 0; $y < count($patch->diffs); $y++) { 1660 - $patchCopy->diffs[$y] = $patch->diffs[$y]; // ?? . slice(); 1661 - } 1662 - $patchCopy->start1 = $patch->start1; 1663 - $patchCopy->start2 = $patch->start2; 1664 - $patchCopy->length1 = $patch->length1; 1665 - $patchCopy->length2 = $patch->length2; 1666 - $patchesCopy[$x] = $patchCopy; 1667 - } 1668 - return $patchesCopy; 1669 - } 1670 - 1671 - /** 1672 - * Merge a set of patches onto the text. Return a patched text, as well 1673 - * as a list of true/false values indicating which patches were applied. 1674 - * @param {Array.<patch_obj>} patches Array of patch objects. 1675 - * @param {string} text Old text. 1676 - * @return {Array.<string|Array.<boolean>>} Two element Array, containing the 1677 - * new text and an array of boolean values. 1678 - */ 1679 - function patch_apply($patches, $text) { 1680 - if ( count($patches) == 0) { 1681 - return array($text,array()); 1682 - } 1683 - 1684 - // Deep copy the patches so that no changes are made to originals. 1685 - $patches = $this->patch_deepCopy($patches); 1686 - 1687 - $nullPadding = $this->patch_addPadding($patches); 1688 - $text = $nullPadding . $text . $nullPadding; 1689 - 1690 - $this->patch_splitMax($patches); 1691 - // delta keeps track of the offset between the expected and actual location 1692 - // of the previous patch. If there are patches expected at positions 10 and 1693 - // 20, but the first patch was found at 12, delta is 2 and the second patch 1694 - // has an effective expected position of 22. 1695 - $delta = 0; 1696 - $results = array(); 1697 - for ($x = 0; $x < count($patches) ; $x++) { 1698 - $expected_loc = $patches[$x]->start2 + $delta; 1699 - $text1 = $this->diff_text1($patches[$x]->diffs); 1700 - $start_loc = null; 1701 - $end_loc = -1; 1702 - if (mb_strlen($text1) > Match_MaxBits) { 1703 - // patch_splitMax will only provide an oversized pattern in the case of 1704 - // a monster delete. 1705 - $start_loc = $this->match_main($text, mb_substr($text1, 0, Match_MaxBits ), $expected_loc); 1706 - if ($start_loc != -1) { 1707 - $end_loc = $this->match_main($text, mb_substr($text1,mb_strlen($text1) - Match_MaxBits), $expected_loc + mb_strlen($text1) - Match_MaxBits); 1708 - if ($end_loc == -1 || $start_loc >= $end_loc) { 1709 - // Can't find valid trailing context. Drop this patch. 1710 - $start_loc = -1; 1711 - } 1712 - } 1713 - } else { 1714 - $start_loc = $this->match_main($text, $text1, $expected_loc); 1715 - } 1716 - if ($start_loc == -1) { 1717 - // No match found. :( 1718 - $results[$x] = false; 1719 - // Subtract the delta for this failed patch from subsequent patches. 1720 - $delta -= $patches[$x]->length2 - $patches[$x]->length1; 1721 - } else { 1722 - // Found a match. :) 1723 - $results[$x] = true; 1724 - $delta = $start_loc - $expected_loc; 1725 - $text2 = null; 1726 - if ($end_loc == -1) { 1727 - $text2 = mb_substr($text, $start_loc, mb_strlen($text1) ); 1728 - } else { 1729 - $text2 = mb_substr($text, $start_loc, $end_loc + Match_MaxBits - $start_loc); 1730 - } 1731 - if ($text1 == $text2) { 1732 - // Perfect match, just shove the replacement text in. 1733 - $text = mb_substr($text, 0, $start_loc) . $this->diff_text2($patches[$x]->diffs) . mb_substr($text,$start_loc + mb_strlen($text1) ); 1734 - } else { 1735 - // Imperfect match. Run a diff to get a framework of equivalent 1736 - // indices. 1737 - $diffs = $this->diff_main($text1, $text2, false); 1738 - if ( mb_strlen($text1) > Match_MaxBits && $this->diff_levenshtein($diffs) / mb_strlen($text1) > $this->Patch_DeleteThreshold) { 1739 - // The end points match, but the content is unacceptably bad. 1740 - $results[$x] = false; 1741 - } else { 1742 - $this->diff_cleanupSemanticLossless($diffs); 1743 - $index1 = 0; 1744 - $index2 = NULL; 1745 - for ($y = 0; $y < count($patches[$x]->diffs); $y++) { 1746 - $mod = $patches[$x]->diffs[$y]; 1747 - if ($mod[0] !== DIFF_EQUAL) { 1748 - $index2 = $this->diff_xIndex($diffs, $index1); 1749 - } 1750 - if ($mod[0] === DIFF_INSERT) { // Insertion 1751 - $text = mb_substr($text, 0, $start_loc + $index2) . $mod[1] . mb_substr($text, $start_loc + $index2); 1752 - } elseif ($mod[0] === DIFF_DELETE) { // Deletion 1753 - $text = mb_substr($text, 0, $start_loc + $index2) . mb_substr($text,$start_loc + $this->diff_xIndex($diffs, $index1 + mb_strlen($mod[1]) )); 1754 - } 1755 - if ($mod[0] !== DIFF_DELETE) { 1756 - $index1 += mb_strlen($mod[1]); 1757 - } 1758 - } 1759 - } 1760 - } 1761 - } 1762 - } 1763 - // Strip the padding off. 1764 - $text = mb_substr($text, mb_strlen($nullPadding), mb_strlen($text) - 2*mb_strlen($nullPadding) ); 1765 - return array($text, $results); 1766 - } 1767 - 1768 - /** 1769 - * Add some padding on text start and end so that edges can match something. 1770 - * Intended to be called only from within patch_apply. 1771 - * @param {Array.<patch_obj>} patches Array of patch objects. 1772 - * @return {string} The padding string added to each side. 1773 - */ 1774 - function patch_addPadding(&$patches){ 1775 - $paddingLength = $this->Patch_Margin; 1776 - $nullPadding = ''; 1777 - for ($x = 1; $x <= $paddingLength; $x++) { 1778 - $nullPadding .= mb_chr($x); 1779 - } 1780 - 1781 - // Bump all the patches forward. 1782 - for ($x = 0; $x < count($patches); $x++) { 1783 - $patches[$x]->start1 += $paddingLength; 1784 - $patches[$x]->start2 += $paddingLength; 1785 - } 1786 - 1787 - // Add some padding on start of first diff. 1788 - $patch = &$patches[0]; 1789 - $diffs = &$patch->diffs; 1790 - if (count($diffs) == 0 || $diffs[0][0] != DIFF_EQUAL) { 1791 - // Add nullPadding equality. 1792 - array_unshift($diffs, array(DIFF_EQUAL, $nullPadding)); 1793 - $patch->start1 -= $paddingLength; // Should be 0. 1794 - $patch->start2 -= $paddingLength; // Should be 0. 1795 - $patch->length1 += $paddingLength; 1796 - $patch->length2 += $paddingLength; 1797 - } elseif ($paddingLength > mb_strlen($diffs[0][1]) ) { 1798 - // Grow first equality. 1799 - $extraLength = $paddingLength - mb_strlen($diffs[0][1]); 1800 - $diffs[0][1] = mb_substr( $nullPadding , mb_strlen($diffs[0][1]) ) . $diffs[0][1]; 1801 - $patch->start1 -= $extraLength; 1802 - $patch->start2 -= $extraLength; 1803 - $patch->length1 += $extraLength; 1804 - $patch->length2 += $extraLength; 1805 - } 1806 - 1807 - // Add some padding on end of last diff. 1808 - $patch = &$patches[count($patches) - 1]; 1809 - $diffs = &$patch->diffs; 1810 - if ( count($diffs) == 0 || $diffs[ count($diffs) - 1][0] != DIFF_EQUAL) { 1811 - // Add nullPadding equality. 1812 - array_push($diffs, array(DIFF_EQUAL, $nullPadding) ); 1813 - $patch->length1 += $paddingLength; 1814 - $patch->length2 += $paddingLength; 1815 - } elseif ($paddingLength > mb_strlen( $diffs[count($diffs)-1][1] ) ) { 1816 - // Grow last equality. 1817 - $extraLength = $paddingLength - mb_strlen( $diffs[count($diffs)-1][1] ); 1818 - $diffs[ count($diffs)-1][1] .= mb_substr($nullPadding,0,$extraLength); 1819 - $patch->length1 += $extraLength; 1820 - $patch->length2 += $extraLength; 1821 - } 1822 - 1823 - return $nullPadding; 1824 - } 1825 - 1826 - /** 1827 - * Look through the patches and break up any which are longer than the maximum 1828 - * limit of the match algorithm. 1829 - * @param {Array.<patch_obj>} patches Array of patch objects. 1830 - */ 1831 - function patch_splitMax(&$patches) { 1832 - for ($x = 0; $x < count($patches); $x++) { 1833 - if ( $patches[$x]->length1 > Match_MaxBits) { 1834 - $bigpatch = $patches[$x]; 1835 - // Remove the big old patch. 1836 - array_splice($patches,$x--,1); 1837 - $patch_size = Match_MaxBits; 1838 - $start1 = $bigpatch->start1; 1839 - $start2 = $bigpatch->start2; 1840 - $precontext = ''; 1841 - while ( count($bigpatch->diffs) !== 0) { 1842 - // Create one of several smaller patches. 1843 - $patch = new patch_obj(); 1844 - $empty = true; 1845 - $patch->start1 = $start1 - mb_strlen($precontext); 1846 - $patch->start2 = $start2 - mb_strlen($precontext); 1847 - if ($precontext !== '') { 1848 - $patch->length1 = $patch->length2 = mb_strlen($precontext); 1849 - array_push($patch->diffs, array(DIFF_EQUAL, $precontext) ); 1850 - } 1851 - while ( count($bigpatch->diffs) !== 0 && $patch->length1 < $patch_size - $this->Patch_Margin) { 1852 - $diff_type = $bigpatch->diffs[0][0]; 1853 - $diff_text = $bigpatch->diffs[0][1]; 1854 - if ($diff_type === DIFF_INSERT) { 1855 - // Insertions are harmless. 1856 - $patch->length2 += mb_strlen($diff_text); 1857 - $start2 += mb_strlen($diff_text); 1858 - array_push($patch->diffs, array_shift($bigpatch->diffs) ); 1859 - $empty = false; 1860 - } else 1861 - if ($diff_type === DIFF_DELETE && count($patch->diffs) == 1 && $patch->diffs[0][0] == DIFF_EQUAL && (mb_strlen($diff_text) > 2 * $patch_size) ) { 1862 - // This is a large deletion. Let it pass in one chunk. 1863 - $patch->length1 += mb_strlen($diff_text); 1864 - $start1 += mb_strlen($diff_text); 1865 - $empty = false; 1866 - array_push( $patch->diffs, array($diff_type, $diff_text) ); 1867 - array_shift($bigpatch->diffs); 1868 - } else { 1869 - // Deletion or equality. Only take as much as we can stomach. 1870 - $diff_text = mb_substr($diff_text, 0, $patch_size - $patch->length1 - $this->Patch_Margin); 1871 - $patch->length1 += mb_strlen($diff_text); 1872 - $start1 += mb_strlen($diff_text); 1873 - if ($diff_type === DIFF_EQUAL) { 1874 - $patch->length2 += mb_strlen($diff_text); 1875 - $start2 += mb_strlen($diff_text); 1876 - } else { 1877 - $empty = false; 1878 - } 1879 - array_push($patch->diffs, array($diff_type, $diff_text) ); 1880 - if ($diff_text == $bigpatch->diffs[0][1]) { 1881 - array_shift($bigpatch->diffs); 1882 - } else { 1883 - $bigpatch->diffs[0][1] = mb_substr( $bigpatch->diffs[0][1],mb_strlen($diff_text) ); 1884 - } 1885 - } 1886 - } 1887 - // Compute the head context for the next patch. 1888 - $precontext = $this->diff_text2($patch->diffs); 1889 - $precontext = mb_substr($precontext, mb_strlen($precontext)-$this->Patch_Margin); 1890 - // Append the end context for this patch. 1891 - $postcontext = mb_substr( $this->diff_text1($bigpatch->diffs), 0, $this->Patch_Margin ); 1892 - if ($postcontext !== '') { 1893 - $patch->length1 += mb_strlen($postcontext); 1894 - $patch->length2 += mb_strlen($postcontext); 1895 - if ( count($patch->diffs) !== 0 && $patch->diffs[ count($patch->diffs) - 1][0] === DIFF_EQUAL) { 1896 - $patch->diffs[ count($patch->diffs) - 1][1] .= $postcontext; 1897 - } else { 1898 - array_push($patch->diffs, array(DIFF_EQUAL, $postcontext)); 1899 - } 1900 - } 1901 - if (!$empty) { 1902 - array_splice($patches, ++$x, 0, array($patch)); 1903 - } 1904 - } 1905 - } 1906 - } 1907 - } 1908 - 1909 - /** 1910 - * Take a list of patches and return a textual representation. 1911 - * @param {Array.<patch_obj>} patches Array of patch objects. 1912 - * @return {string} Text representation of patches. 1913 - */ 1914 - function patch_toText($patches) { 1915 - $text = array(); 1916 - for ($x = 0; $x < count($patches) ; $x++) { 1917 - $text[$x] = $patches[$x]; 1918 - } 1919 - return implode('',$text); 1920 - } 1921 - 1922 - /** 1923 - * Parse a textual representation of patches and return a list of patch objects. 1924 - * @param {string} textline Text representation of patches. 1925 - * @return {Array.<patch_obj>} Array of patch objects. 1926 - * @throws {Error} If invalid input. 1927 - */ 1928 - function patch_fromText($textline) { 1929 - $patches = array(); 1930 - if ($textline === '') { 1931 - return $patches; 1932 - } 1933 - $text = explode("\n",$textline); 1934 - foreach($text as $i=>$t){ if($t===''){ unset($text[$i]); } } 1935 - $textPointer = 0; 1936 - while ($textPointer < count($text) ) { 1937 - $m = null; 1938 - preg_match('/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/',$text[$textPointer],$m); 1939 - if (!$m) { 1940 - echo_Exception('Invalid patch string: ' . $text[$textPointer]); 1941 - } 1942 - $patch = new patch_obj(); 1943 - array_push($patches, $patch); 1944 - @$patch->start1 = (int)$m[1]; 1945 - if (@$m[2] === '') { 1946 - $patch->start1--; 1947 - $patch->length1 = 1; 1948 - } elseif ( @$m[2] == '0') { 1949 - $patch->length1 = 0; 1950 - } else { 1951 - $patch->start1--; 1952 - @$patch->length1 = (int)$m[2]; 1953 - } 1954 - 1955 - @$patch->start2 = (int)$m[3]; 1956 - if (@$m[4] === '') { 1957 - $patch->start2--; 1958 - $patch->length2 = 1; 1959 - } elseif ( @$m[4] == '0') { 1960 - $patch->length2 = 0; 1961 - } else { 1962 - $patch->start2--; 1963 - @$patch->length2 = (int)$m[4]; 1964 - } 1965 - $textPointer++; 1966 - 1967 - while ($textPointer < count($text) ) { 1968 - $sign = $text[$textPointer][0]; 1969 - try { 1970 - $line = decodeURI( mb_substr($text[$textPointer],1) ); 1971 - } catch (Exception $ex) { 1972 - // Malformed URI sequence. 1973 - throw new Exception('Illegal escape in patch_fromText: ' . $line); 1974 - } 1975 - if ($sign == '-') { 1976 - // Deletion. 1977 - array_push( $patch->diffs, array(DIFF_DELETE, $line) ); 1978 - } elseif ($sign == '+') { 1979 - // Insertion. 1980 - array_push($patch->diffs, array(DIFF_INSERT, $line) ); 1981 - } elseif ($sign == ' ') { 1982 - // Minor equality. 1983 - array_push($patch->diffs, array(DIFF_EQUAL, $line) ); 1984 - } elseif ($sign == '@') { 1985 - // Start of next patch. 1986 - break; 1987 - } elseif ($sign === '') { 1988 - // Blank line? Whatever. 1989 - } else { 1990 - // WTF? 1991 - echo_Exception('Invalid patch mode "' . $sign . '" in: ' . $line); 1992 - } 1993 - $textPointer++; 1994 - } 1995 - } 1996 - return $patches; 1997 - } 1998 - } 1999 - 2000 - /** 2001 - * Class representing one patch operation. 2002 - * @constructor 2003 - */ 2004 - class patch_obj { 2005 - /** @type {Array.<Array.<number|string>>} */ 2006 - public $diffs = array(); 2007 - /** @type {number?} */ 2008 - public $start1 = null; 2009 - /** @type {number?} */ 2010 - public $start2 = null; 2011 - /** @type {number} */ 2012 - public $length1 = 0; 2013 - /** @type {number} */ 2014 - public $length2 = 0; 2015 - 2016 - /** 2017 - * Emmulate GNU diff's format. 2018 - * Header: @@ -382,8 +481,9 @@ 2019 - * Indicies are printed as 1-based, not 0-based. 2020 - * @return {string} The GNU diff string. 2021 - */ 2022 - function toString() { 2023 - if ($this->length1 === 0) { 2024 - $coords1 = $this->start1 . ',0'; 2025 - } elseif ($this->length1 == 1) { 2026 - $coords1 = $this->start1 + 1; 2027 - } else { 2028 - $coords1 = ($this->start1 + 1) . ',' . $this->length1; 2029 - } 2030 - if ($this->length2 === 0) { 2031 - $coords2 = $this->start2 . ',0'; 2032 - } elseif ($this->length2 == 1) { 2033 - $coords2 = $this->start2 + 1; 2034 - } else { 2035 - $coords2 = ($this->start2 + 1) . ',' . $this->length2; 2036 - } 2037 - $text = array ( '@@ -' . $coords1 . ' +' . $coords2 . " @@\n" ); 2038 - 2039 - // Escape the body of the patch with %xx notation. 2040 - for ($x = 0; $x < count($this->diffs); $x++) { 2041 - switch ($this->diffs[$x][0]) { 2042 - case DIFF_INSERT : 2043 - $op = '+'; 2044 - break; 2045 - case DIFF_DELETE : 2046 - $op = '-'; 2047 - break; 2048 - case DIFF_EQUAL : 2049 - $op = ' '; 2050 - break; 2051 - } 2052 - $text[$x +1] = $op . encodeURI($this->diffs[$x][1]) . "\n"; 2053 - } 2054 - return str_replace('%20', ' ', implode('',$text)); 2055 - } 2056 - function __toString(){ 2057 - return $this->toString(); 2058 - } 2059 - } 2060 - 2061 - define('DIFF_DELETE', -1); 2062 - define('DIFF_INSERT', 1); 2063 - define('DIFF_EQUAL', 0); 2064 - 2065 - define('Match_MaxBits', PHP_INT_SIZE * 8); 2066 - 2067 - 2068 - function charCodeAt($str, $pos) { 2069 - return mb_ord(mb_substr($str, $pos, 1)); 2070 - } 2071 - function mb_ord($v) { 2072 - $k = mb_convert_encoding($v, 'UCS-2LE', 'UTF-8'); 2073 - $k1 = ord(substr($k, 0, 1)); 2074 - $k2 = ord(substr($k, 1, 1)); 2075 - return $k2 * 256 + $k1; 2076 - } 2077 - function mb_chr($num){ 2078 - return mb_convert_encoding('&#'.intval($num).';', 'UTF-8', 'HTML-ENTITIES'); 2079 - } 2080 - 2081 - /** 2082 - * as in javascript encodeURI() following the MDN description 2083 - * 2084 - * @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI 2085 - * @param $url 2086 - * @return string 2087 - */ 2088 - function encodeURI($url) { 2089 - return strtr(rawurlencode($url), array ( 2090 - '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', '%26' => '&', '%3D' => '=', 2091 - '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#', 2092 - )); 2093 - } 2094 - 2095 - function decodeURI($encoded) { 2096 - static $dontDecode; 2097 - if (!$dontDecode) { 2098 - $table = array ( 2099 - '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', '%26' => '&', '%3D' => '=', 2100 - '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#', 2101 - ); 2102 - $dontDecode = array(); 2103 - foreach ($table as $k => $v) { 2104 - $dontDecode[$k] = encodeURI($k); 2105 - } 2106 - } 2107 - return rawurldecode(strtr($encoded, $dontDecode)); 2108 - } 2109 - 2110 - function echo_Exception($str){ 2111 - global $lastException; 2112 - $lastException = $str; 2113 - echo $str; 2114 - } 2115 - //mb_internal_encoding("UTF-8"); 2116 - 2117 - ?>
-107
resources/sql/quickstart.sql
··· 7552 7552 KEY `key_merchant` (`merchantPHID`) 7553 7553 ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7554 7554 7555 - CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phragment` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; 7556 - 7557 - USE `{$NAMESPACE}_phragment`; 7558 - 7559 - SET NAMES utf8 ; 7560 - 7561 - SET character_set_client = {$CHARSET} ; 7562 - 7563 - CREATE TABLE `edge` ( 7564 - `src` varbinary(64) NOT NULL, 7565 - `type` int(10) unsigned NOT NULL, 7566 - `dst` varbinary(64) NOT NULL, 7567 - `dateCreated` int(10) unsigned NOT NULL, 7568 - `seq` int(10) unsigned NOT NULL, 7569 - `dataID` int(10) unsigned DEFAULT NULL, 7570 - PRIMARY KEY (`src`,`type`,`dst`), 7571 - UNIQUE KEY `key_dst` (`dst`,`type`,`src`), 7572 - KEY `src` (`src`,`type`,`dateCreated`,`seq`) 7573 - ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7574 - 7575 - USE `{$NAMESPACE}_phragment`; 7576 - 7577 - SET NAMES utf8 ; 7578 - 7579 - SET character_set_client = {$CHARSET} ; 7580 - 7581 - CREATE TABLE `edgedata` ( 7582 - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 7583 - `data` longtext CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL, 7584 - PRIMARY KEY (`id`) 7585 - ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7586 - 7587 - USE `{$NAMESPACE}_phragment`; 7588 - 7589 - SET NAMES utf8 ; 7590 - 7591 - SET character_set_client = {$CHARSET} ; 7592 - 7593 - CREATE TABLE `phragment_fragment` ( 7594 - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 7595 - `phid` varbinary(64) NOT NULL, 7596 - `path` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL, 7597 - `depth` int(10) unsigned NOT NULL, 7598 - `latestVersionPHID` varbinary(64) DEFAULT NULL, 7599 - `viewPolicy` varbinary(64) NOT NULL, 7600 - `editPolicy` varbinary(64) NOT NULL, 7601 - `dateCreated` int(10) unsigned NOT NULL, 7602 - `dateModified` int(10) unsigned NOT NULL, 7603 - PRIMARY KEY (`id`), 7604 - UNIQUE KEY `key_phid` (`phid`), 7605 - UNIQUE KEY `key_path` (`path`) 7606 - ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7607 - 7608 - USE `{$NAMESPACE}_phragment`; 7609 - 7610 - SET NAMES utf8 ; 7611 - 7612 - SET character_set_client = {$CHARSET} ; 7613 - 7614 - CREATE TABLE `phragment_fragmentversion` ( 7615 - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 7616 - `phid` varbinary(64) NOT NULL, 7617 - `sequence` int(10) unsigned NOT NULL, 7618 - `fragmentPHID` varbinary(64) NOT NULL, 7619 - `filePHID` varbinary(64) DEFAULT NULL, 7620 - `dateCreated` int(10) unsigned NOT NULL, 7621 - `dateModified` int(10) unsigned NOT NULL, 7622 - PRIMARY KEY (`id`), 7623 - UNIQUE KEY `key_version` (`fragmentPHID`,`sequence`), 7624 - UNIQUE KEY `key_phid` (`phid`) 7625 - ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7626 - 7627 - USE `{$NAMESPACE}_phragment`; 7628 - 7629 - SET NAMES utf8 ; 7630 - 7631 - SET character_set_client = {$CHARSET} ; 7632 - 7633 - CREATE TABLE `phragment_snapshot` ( 7634 - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 7635 - `phid` varbinary(64) NOT NULL, 7636 - `primaryFragmentPHID` varbinary(64) NOT NULL, 7637 - `name` varchar(128) CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} NOT NULL, 7638 - `dateCreated` int(10) unsigned NOT NULL, 7639 - `dateModified` int(10) unsigned NOT NULL, 7640 - PRIMARY KEY (`id`), 7641 - UNIQUE KEY `key_phid` (`phid`), 7642 - UNIQUE KEY `key_name` (`primaryFragmentPHID`,`name`) 7643 - ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7644 - 7645 - USE `{$NAMESPACE}_phragment`; 7646 - 7647 - SET NAMES utf8 ; 7648 - 7649 - SET character_set_client = {$CHARSET} ; 7650 - 7651 - CREATE TABLE `phragment_snapshotchild` ( 7652 - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 7653 - `snapshotPHID` varbinary(64) NOT NULL, 7654 - `fragmentPHID` varbinary(64) NOT NULL, 7655 - `fragmentVersionPHID` varbinary(64) DEFAULT NULL, 7656 - `dateCreated` int(10) unsigned NOT NULL, 7657 - `dateModified` int(10) unsigned NOT NULL, 7658 - PRIMARY KEY (`id`), 7659 - UNIQUE KEY `key_child` (`snapshotPHID`,`fragmentPHID`,`fragmentVersionPHID`) 7660 - ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; 7661 - 7662 7555 CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_phrequent` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; 7663 7556 7664 7557 USE `{$NAMESPACE}_phrequent`;
-80
src/__phutil_library_map__.php
··· 1536 1536 'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php', 1537 1537 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 1538 1538 'HarbormasterPrototypeBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php', 1539 - 'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php', 1540 1539 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php', 1541 1540 'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php', 1542 1541 'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php', ··· 4234 4233 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php', 4235 4234 'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php', 4236 4235 'PhabricatorPhortuneTestCase' => 'applications/phortune/__tests__/PhabricatorPhortuneTestCase.php', 4237 - 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php', 4238 4236 'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php', 4239 4237 'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php', 4240 4238 'PhabricatorPhurlApplication' => 'applications/phurl/application/PhabricatorPhurlApplication.php', ··· 5559 5557 'PhortuneSubscriptionTransactionType' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php', 5560 5558 'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php', 5561 5559 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 5562 - 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', 5563 - 'PhragmentCanCreateCapability' => 'applications/phragment/capability/PhragmentCanCreateCapability.php', 5564 - 'PhragmentConduitAPIMethod' => 'applications/phragment/conduit/PhragmentConduitAPIMethod.php', 5565 - 'PhragmentController' => 'applications/phragment/controller/PhragmentController.php', 5566 - 'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php', 5567 - 'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php', 5568 - 'PhragmentFragment' => 'applications/phragment/storage/PhragmentFragment.php', 5569 - 'PhragmentFragmentPHIDType' => 'applications/phragment/phid/PhragmentFragmentPHIDType.php', 5570 - 'PhragmentFragmentQuery' => 'applications/phragment/query/PhragmentFragmentQuery.php', 5571 - 'PhragmentFragmentVersion' => 'applications/phragment/storage/PhragmentFragmentVersion.php', 5572 - 'PhragmentFragmentVersionPHIDType' => 'applications/phragment/phid/PhragmentFragmentVersionPHIDType.php', 5573 - 'PhragmentFragmentVersionQuery' => 'applications/phragment/query/PhragmentFragmentVersionQuery.php', 5574 - 'PhragmentGetPatchConduitAPIMethod' => 'applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php', 5575 - 'PhragmentHistoryController' => 'applications/phragment/controller/PhragmentHistoryController.php', 5576 - 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 5577 - 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 5578 - 'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php', 5579 - 'PhragmentQueryFragmentsConduitAPIMethod' => 'applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php', 5580 - 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php', 5581 - 'PhragmentSchemaSpec' => 'applications/phragment/storage/PhragmentSchemaSpec.php', 5582 - 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php', 5583 - 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php', 5584 - 'PhragmentSnapshotChildQuery' => 'applications/phragment/query/PhragmentSnapshotChildQuery.php', 5585 - 'PhragmentSnapshotCreateController' => 'applications/phragment/controller/PhragmentSnapshotCreateController.php', 5586 - 'PhragmentSnapshotDeleteController' => 'applications/phragment/controller/PhragmentSnapshotDeleteController.php', 5587 - 'PhragmentSnapshotPHIDType' => 'applications/phragment/phid/PhragmentSnapshotPHIDType.php', 5588 - 'PhragmentSnapshotPromoteController' => 'applications/phragment/controller/PhragmentSnapshotPromoteController.php', 5589 - 'PhragmentSnapshotQuery' => 'applications/phragment/query/PhragmentSnapshotQuery.php', 5590 - 'PhragmentSnapshotViewController' => 'applications/phragment/controller/PhragmentSnapshotViewController.php', 5591 - 'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php', 5592 - 'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php', 5593 - 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', 5594 5560 'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php', 5595 5561 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 5596 5562 'PhrequentCurtainExtension' => 'applications/phrequent/engineextension/PhrequentCurtainExtension.php', ··· 7710 7676 'HarbormasterPlanRunController' => 'HarbormasterPlanController', 7711 7677 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 7712 7678 'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup', 7713 - 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 7714 7679 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 7715 7680 'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 7716 7681 'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', ··· 10822 10787 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow', 10823 10788 'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow', 10824 10789 'PhabricatorPhortuneTestCase' => 'PhabricatorTestCase', 10825 - 'PhabricatorPhragmentApplication' => 'PhabricatorApplication', 10826 10790 'PhabricatorPhrequentApplication' => 'PhabricatorApplication', 10827 10791 'PhabricatorPhrictionApplication' => 'PhabricatorApplication', 10828 10792 'PhabricatorPhurlApplication' => 'PhabricatorApplication', ··· 12442 12406 'PhortuneSubscriptionTransactionType' => 'PhabricatorModularTransactionType', 12443 12407 'PhortuneSubscriptionWorker' => 'PhabricatorWorker', 12444 12408 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 12445 - 'PhragmentBrowseController' => 'PhragmentController', 12446 - 'PhragmentCanCreateCapability' => 'PhabricatorPolicyCapability', 12447 - 'PhragmentConduitAPIMethod' => 'ConduitAPIMethod', 12448 - 'PhragmentController' => 'PhabricatorController', 12449 - 'PhragmentCreateController' => 'PhragmentController', 12450 - 'PhragmentDAO' => 'PhabricatorLiskDAO', 12451 - 'PhragmentFragment' => array( 12452 - 'PhragmentDAO', 12453 - 'PhabricatorPolicyInterface', 12454 - ), 12455 - 'PhragmentFragmentPHIDType' => 'PhabricatorPHIDType', 12456 - 'PhragmentFragmentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 12457 - 'PhragmentFragmentVersion' => array( 12458 - 'PhragmentDAO', 12459 - 'PhabricatorPolicyInterface', 12460 - ), 12461 - 'PhragmentFragmentVersionPHIDType' => 'PhabricatorPHIDType', 12462 - 'PhragmentFragmentVersionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 12463 - 'PhragmentGetPatchConduitAPIMethod' => 'PhragmentConduitAPIMethod', 12464 - 'PhragmentHistoryController' => 'PhragmentController', 12465 - 'PhragmentPatchController' => 'PhragmentController', 12466 - 'PhragmentPatchUtil' => 'Phobject', 12467 - 'PhragmentPolicyController' => 'PhragmentController', 12468 - 'PhragmentQueryFragmentsConduitAPIMethod' => 'PhragmentConduitAPIMethod', 12469 - 'PhragmentRevertController' => 'PhragmentController', 12470 - 'PhragmentSchemaSpec' => 'PhabricatorConfigSchemaSpec', 12471 - 'PhragmentSnapshot' => array( 12472 - 'PhragmentDAO', 12473 - 'PhabricatorPolicyInterface', 12474 - ), 12475 - 'PhragmentSnapshotChild' => array( 12476 - 'PhragmentDAO', 12477 - 'PhabricatorPolicyInterface', 12478 - ), 12479 - 'PhragmentSnapshotChildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 12480 - 'PhragmentSnapshotCreateController' => 'PhragmentController', 12481 - 'PhragmentSnapshotDeleteController' => 'PhragmentController', 12482 - 'PhragmentSnapshotPHIDType' => 'PhabricatorPHIDType', 12483 - 'PhragmentSnapshotPromoteController' => 'PhragmentController', 12484 - 'PhragmentSnapshotQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 12485 - 'PhragmentSnapshotViewController' => 'PhragmentController', 12486 - 'PhragmentUpdateController' => 'PhragmentController', 12487 - 'PhragmentVersionController' => 'PhragmentController', 12488 - 'PhragmentZIPController' => 'PhragmentController', 12489 12409 'PhrequentConduitAPIMethod' => 'ConduitAPIMethod', 12490 12410 'PhrequentController' => 'PhabricatorController', 12491 12411 'PhrequentCurtainExtension' => 'PHUICurtainExtension',
-89
src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php
··· 1 - <?php 2 - 3 - final class HarbormasterPublishFragmentBuildStepImplementation 4 - extends HarbormasterBuildStepImplementation { 5 - 6 - public function getName() { 7 - return pht('Publish Fragment'); 8 - } 9 - 10 - public function getGenericDescription() { 11 - return pht('Publish a fragment based on a file artifact.'); 12 - } 13 - 14 - 15 - public function getBuildStepGroupKey() { 16 - return HarbormasterPrototypeBuildStepGroup::GROUPKEY; 17 - } 18 - 19 - public function getDescription() { 20 - return pht( 21 - 'Publish file artifact %s as fragment %s.', 22 - $this->formatSettingForDescription('artifact'), 23 - $this->formatSettingForDescription('path')); 24 - } 25 - 26 - public function execute( 27 - HarbormasterBuild $build, 28 - HarbormasterBuildTarget $build_target) { 29 - 30 - $settings = $this->getSettings(); 31 - $variables = $build_target->getVariables(); 32 - $viewer = PhabricatorUser::getOmnipotentUser(); 33 - 34 - $path = $this->mergeVariables( 35 - 'vsprintf', 36 - $settings['path'], 37 - $variables); 38 - 39 - $artifact = $build_target->loadArtifact($settings['artifact']); 40 - $impl = $artifact->getArtifactImplementation(); 41 - $file = $impl->loadArtifactFile($viewer); 42 - 43 - $fragment = id(new PhragmentFragmentQuery()) 44 - ->setViewer($viewer) 45 - ->withPaths(array($path)) 46 - ->executeOne(); 47 - 48 - if ($fragment === null) { 49 - PhragmentFragment::createFromFile( 50 - $viewer, 51 - $file, 52 - $path, 53 - PhabricatorPolicies::getMostOpenPolicy(), 54 - PhabricatorPolicies::POLICY_USER); 55 - } else { 56 - if ($file->getMimeType() === 'application/zip') { 57 - $fragment->updateFromZIP($viewer, $file); 58 - } else { 59 - $fragment->updateFromFile($viewer, $file); 60 - } 61 - } 62 - } 63 - 64 - public function getArtifactInputs() { 65 - return array( 66 - array( 67 - 'name' => pht('Publishes File'), 68 - 'key' => $this->getSetting('artifact'), 69 - 'type' => HarbormasterFileArtifact::ARTIFACTCONST, 70 - ), 71 - ); 72 - } 73 - 74 - public function getFieldSpecifications() { 75 - return array( 76 - 'path' => array( 77 - 'name' => pht('Path'), 78 - 'type' => 'text', 79 - 'required' => true, 80 - ), 81 - 'artifact' => array( 82 - 'name' => pht('File Artifact'), 83 - 'type' => 'text', 84 - 'required' => true, 85 - ), 86 - ); 87 - } 88 - 89 - }
-66
src/applications/phragment/application/PhabricatorPhragmentApplication.php
··· 1 - <?php 2 - 3 - final class PhabricatorPhragmentApplication extends PhabricatorApplication { 4 - 5 - public function getName() { 6 - return pht('Phragment'); 7 - } 8 - 9 - public function getBaseURI() { 10 - return '/phragment/'; 11 - } 12 - 13 - public function getShortDescription() { 14 - return pht('Versioned Artifact Storage'); 15 - } 16 - 17 - public function getIcon() { 18 - return 'fa-floppy-o'; 19 - } 20 - 21 - public function getTitleGlyph() { 22 - return "\xE2\x96\x9B"; 23 - } 24 - 25 - public function getApplicationGroup() { 26 - return self::GROUP_UTILITIES; 27 - } 28 - 29 - public function isPrototype() { 30 - return true; 31 - } 32 - 33 - public function getRoutes() { 34 - return array( 35 - '/phragment/' => array( 36 - '' => 'PhragmentBrowseController', 37 - 'browse/(?P<dblob>.*)' => 'PhragmentBrowseController', 38 - 'create/(?P<dblob>.*)' => 'PhragmentCreateController', 39 - 'update/(?P<dblob>.+)' => 'PhragmentUpdateController', 40 - 'policy/(?P<dblob>.+)' => 'PhragmentPolicyController', 41 - 'history/(?P<dblob>.+)' => 'PhragmentHistoryController', 42 - 'zip/(?P<dblob>.+)' => 'PhragmentZIPController', 43 - 'zip@(?P<snapshot>[^/]+)/(?P<dblob>.+)' => 'PhragmentZIPController', 44 - 'version/(?P<id>[0-9]*)/' => 'PhragmentVersionController', 45 - 'patch/(?P<aid>[0-9x]*)/(?P<bid>[0-9]*)/' => 'PhragmentPatchController', 46 - 'revert/(?P<id>[0-9]*)/(?P<dblob>.*)' => 'PhragmentRevertController', 47 - 'snapshot/' => array( 48 - 'create/(?P<dblob>.*)' => 'PhragmentSnapshotCreateController', 49 - 'view/(?P<id>[0-9]*)/' => 'PhragmentSnapshotViewController', 50 - 'delete/(?P<id>[0-9]*)/' => 'PhragmentSnapshotDeleteController', 51 - 'promote/' => array( 52 - 'latest/(?P<dblob>.*)' => 'PhragmentSnapshotPromoteController', 53 - '(?P<id>[0-9]*)/' => 'PhragmentSnapshotPromoteController', 54 - ), 55 - ), 56 - ), 57 - ); 58 - } 59 - 60 - protected function getCustomCapabilities() { 61 - return array( 62 - PhragmentCanCreateCapability::CAPABILITY => array(), 63 - ); 64 - } 65 - 66 - }
-15
src/applications/phragment/capability/PhragmentCanCreateCapability.php
··· 1 - <?php 2 - 3 - final class PhragmentCanCreateCapability extends PhabricatorPolicyCapability { 4 - 5 - const CAPABILITY = 'phragment.create'; 6 - 7 - public function getCapabilityName() { 8 - return pht('Can Create Fragments'); 9 - } 10 - 11 - public function describeCapabilityRejection() { 12 - return pht('You do not have permission to create fragments.'); 13 - } 14 - 15 - }
-10
src/applications/phragment/conduit/PhragmentConduitAPIMethod.php
··· 1 - <?php 2 - 3 - abstract class PhragmentConduitAPIMethod extends ConduitAPIMethod { 4 - 5 - final public function getApplication() { 6 - return PhabricatorApplication::getByClass( 7 - 'PhabricatorPhragmentApplication'); 8 - } 9 - 10 - }
-192
src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php
··· 1 - <?php 2 - 3 - final class PhragmentGetPatchConduitAPIMethod 4 - extends PhragmentConduitAPIMethod { 5 - 6 - public function getAPIMethodName() { 7 - return 'phragment.getpatch'; 8 - } 9 - 10 - public function getMethodStatus() { 11 - return self::METHOD_STATUS_UNSTABLE; 12 - } 13 - 14 - public function getMethodDescription() { 15 - return pht('Retrieve the patches to apply for a given set of files.'); 16 - } 17 - 18 - protected function defineParamTypes() { 19 - return array( 20 - 'path' => 'required string', 21 - 'state' => 'required dict<string, string>', 22 - ); 23 - } 24 - 25 - protected function defineReturnType() { 26 - return 'nonempty dict'; 27 - } 28 - 29 - protected function defineErrorTypes() { 30 - return array( 31 - 'ERR_BAD_FRAGMENT' => pht('No such fragment exists.'), 32 - ); 33 - } 34 - 35 - protected function execute(ConduitAPIRequest $request) { 36 - $path = $request->getValue('path'); 37 - $state = $request->getValue('state'); 38 - // The state is an array mapping file paths to hashes. 39 - 40 - $patches = array(); 41 - 42 - // We need to get all of the mappings (like phragment.getstate) first 43 - // so that we can detect deletions and creations of files. 44 - $fragment = id(new PhragmentFragmentQuery()) 45 - ->setViewer($request->getUser()) 46 - ->withPaths(array($path)) 47 - ->executeOne(); 48 - if ($fragment === null) { 49 - throw new ConduitException('ERR_BAD_FRAGMENT'); 50 - } 51 - 52 - $mappings = $fragment->getFragmentMappings( 53 - $request->getUser(), 54 - $fragment->getPath()); 55 - 56 - $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID'); 57 - $files = id(new PhabricatorFileQuery()) 58 - ->setViewer($request->getUser()) 59 - ->withPHIDs($file_phids) 60 - ->execute(); 61 - $files = mpull($files, null, 'getPHID'); 62 - 63 - // Scan all of the files that the caller currently has and iterate 64 - // over that. 65 - foreach ($state as $path => $hash) { 66 - // If $mappings[$path] exists, then the user has the file and it's 67 - // also a fragment. 68 - if (array_key_exists($path, $mappings)) { 69 - $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID(); 70 - if ($file_phid !== null) { 71 - // If the file PHID is present, then we need to check the 72 - // hashes to see if they are the same. 73 - $hash_caller = strtolower($state[$path]); 74 - $hash_current = $files[$file_phid]->getContentHash(); 75 - if ($hash_caller === $hash_current) { 76 - // The user's version is identical to our version, so 77 - // there is no update needed. 78 - } else { 79 - // The hash differs, and the user needs to update. 80 - $patches[] = array( 81 - 'path' => $path, 82 - 'fileOld' => null, 83 - 'fileNew' => $files[$file_phid], 84 - 'hashOld' => $hash_caller, 85 - 'hashNew' => $hash_current, 86 - 'patchURI' => null, 87 - ); 88 - } 89 - } else { 90 - // We have a record of this as a file, but there is no file 91 - // attached to the latest version, so we consider this to be 92 - // a deletion. 93 - $patches[] = array( 94 - 'path' => $path, 95 - 'fileOld' => null, 96 - 'fileNew' => null, 97 - 'hashOld' => $hash_caller, 98 - 'hashNew' => PhragmentPatchUtil::EMPTY_HASH, 99 - 'patchURI' => null, 100 - ); 101 - } 102 - } else { 103 - // If $mappings[$path] does not exist, then the user has a file, 104 - // and we have absolutely no record of it what-so-ever (we haven't 105 - // even recorded a deletion). Assuming most applications will store 106 - // some form of data near their own files, this is probably a data 107 - // file relevant for the application that is not versioned, so we 108 - // don't tell the client to do anything with it. 109 - } 110 - } 111 - 112 - // Check the remaining files that we know about but the caller has 113 - // not reported. 114 - foreach ($mappings as $path => $child) { 115 - if (array_key_exists($path, $state)) { 116 - // We have already evaluated this above. 117 - } else { 118 - $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID(); 119 - if ($file_phid !== null) { 120 - // If the file PHID is present, then this is a new file that 121 - // we know about, but the caller does not. We need to tell 122 - // the caller to create the file. 123 - $hash_current = $files[$file_phid]->getContentHash(); 124 - $patches[] = array( 125 - 'path' => $path, 126 - 'fileOld' => null, 127 - 'fileNew' => $files[$file_phid], 128 - 'hashOld' => PhragmentPatchUtil::EMPTY_HASH, 129 - 'hashNew' => $hash_current, 130 - 'patchURI' => null, 131 - ); 132 - } else { 133 - // We have a record of deleting this file, and the caller hasn't 134 - // reported it, so they've probably deleted it in a previous 135 - // update. 136 - } 137 - } 138 - } 139 - 140 - // Before we can calculate patches, we need to resolve the old versions 141 - // of files so we can draw diffs on them. 142 - $hashes = array(); 143 - foreach ($patches as $patch) { 144 - if ($patch['hashOld'] !== PhragmentPatchUtil::EMPTY_HASH) { 145 - $hashes[] = $patch['hashOld']; 146 - } 147 - } 148 - $old_files = array(); 149 - if (count($hashes) !== 0) { 150 - $old_files = id(new PhabricatorFileQuery()) 151 - ->setViewer($request->getUser()) 152 - ->withContentHashes($hashes) 153 - ->execute(); 154 - } 155 - $old_files = mpull($old_files, null, 'getContentHash'); 156 - foreach ($patches as $key => $patch) { 157 - if ($patch['hashOld'] !== PhragmentPatchUtil::EMPTY_HASH) { 158 - if (array_key_exists($patch['hashOld'], $old_files)) { 159 - $patches[$key]['fileOld'] = $old_files[$patch['hashOld']]; 160 - } else { 161 - // We either can't see or can't read the old file. 162 - $patches[$key]['hashOld'] = PhragmentPatchUtil::EMPTY_HASH; 163 - $patches[$key]['fileOld'] = null; 164 - } 165 - } 166 - } 167 - 168 - // Now run through all of the patch entries, calculate the patches 169 - // and return the results. 170 - foreach ($patches as $key => $patch) { 171 - $data = PhragmentPatchUtil::calculatePatch( 172 - $patches[$key]['fileOld'], 173 - $patches[$key]['fileNew']); 174 - unset($patches[$key]['fileOld']); 175 - unset($patches[$key]['fileNew']); 176 - 177 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 178 - $file = PhabricatorFile::newFromFileData( 179 - $data, 180 - array( 181 - 'name' => 'patch.dmp', 182 - 'ttl.relative' => phutil_units('24 hours in seconds'), 183 - )); 184 - unset($unguarded); 185 - 186 - $patches[$key]['patchURI'] = $file->getDownloadURI(); 187 - } 188 - 189 - return $patches; 190 - } 191 - 192 - }
-85
src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php
··· 1 - <?php 2 - 3 - final class PhragmentQueryFragmentsConduitAPIMethod 4 - extends PhragmentConduitAPIMethod { 5 - 6 - public function getAPIMethodName() { 7 - return 'phragment.queryfragments'; 8 - } 9 - 10 - public function getMethodStatus() { 11 - return self::METHOD_STATUS_UNSTABLE; 12 - } 13 - 14 - public function getMethodDescription() { 15 - return pht('Query fragments based on their paths.'); 16 - } 17 - 18 - protected function defineParamTypes() { 19 - return array( 20 - 'paths' => 'required list<string>', 21 - ); 22 - } 23 - 24 - protected function defineReturnType() { 25 - return 'nonempty dict'; 26 - } 27 - 28 - protected function defineErrorTypes() { 29 - return array( 30 - 'ERR_BAD_FRAGMENT' => pht('No such fragment exists.'), 31 - ); 32 - } 33 - 34 - protected function execute(ConduitAPIRequest $request) { 35 - $paths = $request->getValue('paths'); 36 - 37 - $fragments = id(new PhragmentFragmentQuery()) 38 - ->setViewer($request->getUser()) 39 - ->withPaths($paths) 40 - ->execute(); 41 - $fragments = mpull($fragments, null, 'getPath'); 42 - foreach ($paths as $path) { 43 - if (!array_key_exists($path, $fragments)) { 44 - throw new ConduitException('ERR_BAD_FRAGMENT'); 45 - } 46 - } 47 - 48 - $results = array(); 49 - foreach ($fragments as $path => $fragment) { 50 - $mappings = $fragment->getFragmentMappings( 51 - $request->getUser(), 52 - $fragment->getPath()); 53 - 54 - $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID'); 55 - $files = id(new PhabricatorFileQuery()) 56 - ->setViewer($request->getUser()) 57 - ->withPHIDs($file_phids) 58 - ->execute(); 59 - $files = mpull($files, null, 'getPHID'); 60 - 61 - $result = array(); 62 - foreach ($mappings as $cpath => $child) { 63 - $file_phid = $child->getLatestVersion()->getFilePHID(); 64 - if (!isset($files[$file_phid])) { 65 - // Skip any files we don't have permission to access. 66 - continue; 67 - } 68 - 69 - $file = $files[$file_phid]; 70 - $cpath = substr($child->getPath(), strlen($fragment->getPath()) + 1); 71 - $result[] = array( 72 - 'phid' => $child->getPHID(), 73 - 'phidVersion' => $child->getLatestVersionPHID(), 74 - 'path' => $cpath, 75 - 'hash' => $file->getContentHash(), 76 - 'version' => $child->getLatestVersion()->getSequence(), 77 - 'uri' => $file->getViewURI(), 78 - ); 79 - } 80 - $results[$path] = $result; 81 - } 82 - return $results; 83 - } 84 - 85 - }
-95
src/applications/phragment/controller/PhragmentBrowseController.php
··· 1 - <?php 2 - 3 - final class PhragmentBrowseController extends PhragmentController { 4 - 5 - public function shouldAllowPublic() { 6 - return true; 7 - } 8 - 9 - public function handleRequest(AphrontRequest $request) { 10 - $viewer = $request->getViewer(); 11 - $dblob = $request->getURIData('dblob'); 12 - 13 - $parents = $this->loadParentFragments($dblob); 14 - if ($parents === null) { 15 - return new Aphront404Response(); 16 - } 17 - $current = nonempty(last($parents), null); 18 - 19 - $path = ''; 20 - if ($current !== null) { 21 - $path = $current->getPath(); 22 - } 23 - 24 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 25 - if ($this->hasApplicationCapability( 26 - PhragmentCanCreateCapability::CAPABILITY)) { 27 - $crumbs->addAction( 28 - id(new PHUIListItemView()) 29 - ->setName(pht('Create Fragment')) 30 - ->setHref($this->getApplicationURI('/create/'.$path)) 31 - ->setIcon('fa-plus-square')); 32 - } 33 - 34 - $current_box = $this->createCurrentFragmentView($current, false); 35 - 36 - $list = id(new PHUIObjectItemListView()) 37 - ->setUser($viewer); 38 - 39 - $fragments = null; 40 - if ($current === null) { 41 - // Find all root fragments. 42 - $fragments = id(new PhragmentFragmentQuery()) 43 - ->setViewer($this->getRequest()->getUser()) 44 - ->needLatestVersion(true) 45 - ->withDepths(array(1)) 46 - ->execute(); 47 - } else { 48 - // Find all child fragments. 49 - $fragments = id(new PhragmentFragmentQuery()) 50 - ->setViewer($this->getRequest()->getUser()) 51 - ->needLatestVersion(true) 52 - ->withLeadingPath($current->getPath().'/') 53 - ->withDepths(array($current->getDepth() + 1)) 54 - ->execute(); 55 - } 56 - 57 - foreach ($fragments as $fragment) { 58 - $item = id(new PHUIObjectItemView()); 59 - $item->setHeader($fragment->getName()); 60 - $item->setHref($fragment->getURI()); 61 - if (!$fragment->isDirectory()) { 62 - $item->addAttribute(pht( 63 - 'Last Updated %s', 64 - phabricator_datetime( 65 - $fragment->getLatestVersion()->getDateCreated(), 66 - $viewer))); 67 - $item->addAttribute(pht( 68 - 'Latest Version %s', 69 - $fragment->getLatestVersion()->getSequence())); 70 - if ($fragment->isDeleted()) { 71 - $item->setDisabled(true); 72 - $item->addAttribute(pht('Deleted')); 73 - } 74 - } else { 75 - $item->addAttribute(pht('Directory')); 76 - } 77 - $list->addItem($item); 78 - } 79 - 80 - $title = pht('Browse Fragments'); 81 - 82 - $view = array( 83 - $this->renderConfigurationWarningIfRequired(), 84 - $current_box, 85 - $list, 86 - ); 87 - 88 - return $this->newPage() 89 - ->setTitle($title) 90 - ->setCrumbs($crumbs) 91 - ->appendChild($view); 92 - 93 - } 94 - 95 - }
-232
src/applications/phragment/controller/PhragmentController.php
··· 1 - <?php 2 - 3 - abstract class PhragmentController extends PhabricatorController { 4 - 5 - protected function loadParentFragments($path) { 6 - $components = explode('/', $path); 7 - 8 - $combinations = array(); 9 - $current = ''; 10 - foreach ($components as $component) { 11 - $current .= '/'.$component; 12 - $current = trim($current, '/'); 13 - if (trim($current) === '') { 14 - continue; 15 - } 16 - 17 - $combinations[] = $current; 18 - } 19 - 20 - $fragments = array(); 21 - $results = id(new PhragmentFragmentQuery()) 22 - ->setViewer($this->getRequest()->getUser()) 23 - ->needLatestVersion(true) 24 - ->withPaths($combinations) 25 - ->execute(); 26 - foreach ($combinations as $combination) { 27 - $found = false; 28 - foreach ($results as $fragment) { 29 - if ($fragment->getPath() === $combination) { 30 - $fragments[] = $fragment; 31 - $found = true; 32 - break; 33 - } 34 - } 35 - if (!$found) { 36 - return null; 37 - } 38 - } 39 - return $fragments; 40 - } 41 - 42 - protected function buildApplicationCrumbsWithPath(array $fragments) { 43 - $crumbs = $this->buildApplicationCrumbs(); 44 - $crumbs->addTextCrumb('/', '/phragment/'); 45 - foreach ($fragments as $parent) { 46 - $crumbs->addTextCrumb( 47 - $parent->getName(), 48 - '/phragment/browse/'.$parent->getPath()); 49 - } 50 - return $crumbs; 51 - } 52 - 53 - protected function createCurrentFragmentView($fragment, $is_history_view) { 54 - if ($fragment === null) { 55 - return null; 56 - } 57 - 58 - $viewer = $this->getRequest()->getUser(); 59 - 60 - $snapshot_phids = array(); 61 - $snapshots = id(new PhragmentSnapshotQuery()) 62 - ->setViewer($viewer) 63 - ->withPrimaryFragmentPHIDs(array($fragment->getPHID())) 64 - ->execute(); 65 - foreach ($snapshots as $snapshot) { 66 - $snapshot_phids[] = $snapshot->getPHID(); 67 - } 68 - 69 - $file = null; 70 - $file_uri = null; 71 - if (!$fragment->isDirectory()) { 72 - $file = id(new PhabricatorFileQuery()) 73 - ->setViewer($viewer) 74 - ->withPHIDs(array($fragment->getLatestVersion()->getFilePHID())) 75 - ->executeOne(); 76 - if ($file !== null) { 77 - $file_uri = $file->getDownloadURI(); 78 - } 79 - } 80 - 81 - $header = id(new PHUIHeaderView()) 82 - ->setHeader($fragment->getName()) 83 - ->setPolicyObject($fragment) 84 - ->setUser($viewer); 85 - 86 - $can_edit = PhabricatorPolicyFilter::hasCapability( 87 - $viewer, 88 - $fragment, 89 - PhabricatorPolicyCapability::CAN_EDIT); 90 - 91 - $zip_uri = $this->getApplicationURI('zip/'.$fragment->getPath()); 92 - 93 - $actions = id(new PhabricatorActionListView()) 94 - ->setUser($viewer) 95 - ->setObject($fragment); 96 - $actions->addAction( 97 - id(new PhabricatorActionView()) 98 - ->setName(pht('Download Fragment')) 99 - ->setHref($this->isCorrectlyConfigured() ? $file_uri : null) 100 - ->setDisabled($file === null || !$this->isCorrectlyConfigured()) 101 - ->setIcon('fa-download')); 102 - $actions->addAction( 103 - id(new PhabricatorActionView()) 104 - ->setName(pht('Download Contents as ZIP')) 105 - ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null) 106 - ->setDisabled(!$this->isCorrectlyConfigured()) 107 - ->setIcon('fa-floppy-o')); 108 - if (!$fragment->isDirectory()) { 109 - $actions->addAction( 110 - id(new PhabricatorActionView()) 111 - ->setName(pht('Update Fragment')) 112 - ->setHref($this->getApplicationURI('update/'.$fragment->getPath())) 113 - ->setDisabled(!$can_edit) 114 - ->setWorkflow(!$can_edit) 115 - ->setIcon('fa-refresh')); 116 - } else { 117 - $actions->addAction( 118 - id(new PhabricatorActionView()) 119 - ->setName(pht('Convert to File')) 120 - ->setHref($this->getApplicationURI('update/'.$fragment->getPath())) 121 - ->setDisabled(!$can_edit) 122 - ->setWorkflow(!$can_edit) 123 - ->setIcon('fa-file-o')); 124 - } 125 - $actions->addAction( 126 - id(new PhabricatorActionView()) 127 - ->setName(pht('Set Fragment Policies')) 128 - ->setHref($this->getApplicationURI('policy/'.$fragment->getPath())) 129 - ->setDisabled(!$can_edit) 130 - ->setWorkflow(!$can_edit) 131 - ->setIcon('fa-asterisk')); 132 - if ($is_history_view) { 133 - $actions->addAction( 134 - id(new PhabricatorActionView()) 135 - ->setName(pht('View Child Fragments')) 136 - ->setHref($this->getApplicationURI('browse/'.$fragment->getPath())) 137 - ->setIcon('fa-search-plus')); 138 - } else { 139 - $actions->addAction( 140 - id(new PhabricatorActionView()) 141 - ->setName(pht('View History')) 142 - ->setHref($this->getApplicationURI('history/'.$fragment->getPath())) 143 - ->setIcon('fa-list')); 144 - } 145 - $actions->addAction( 146 - id(new PhabricatorActionView()) 147 - ->setName(pht('Create Snapshot')) 148 - ->setHref($this->getApplicationURI( 149 - 'snapshot/create/'.$fragment->getPath())) 150 - ->setDisabled(!$can_edit) 151 - ->setWorkflow(!$can_edit) 152 - ->setIcon('fa-files-o')); 153 - $actions->addAction( 154 - id(new PhabricatorActionView()) 155 - ->setName(pht('Promote Snapshot to Here')) 156 - ->setHref($this->getApplicationURI( 157 - 'snapshot/promote/latest/'.$fragment->getPath())) 158 - ->setWorkflow(true) 159 - ->setDisabled(!$can_edit) 160 - ->setIcon('fa-arrow-circle-up')); 161 - 162 - $properties = id(new PHUIPropertyListView()) 163 - ->setUser($viewer) 164 - ->setObject($fragment) 165 - ->setActionList($actions); 166 - 167 - if (!$fragment->isDirectory()) { 168 - if ($fragment->isDeleted()) { 169 - $properties->addProperty( 170 - pht('Type'), 171 - pht('File (Deleted)')); 172 - } else { 173 - $properties->addProperty( 174 - pht('Type'), 175 - pht('File')); 176 - } 177 - $properties->addProperty( 178 - pht('Latest Version'), 179 - $viewer->renderHandle($fragment->getLatestVersionPHID())); 180 - } else { 181 - $properties->addProperty( 182 - pht('Type'), 183 - pht('Directory')); 184 - } 185 - 186 - if (count($snapshot_phids) > 0) { 187 - $properties->addProperty( 188 - pht('Snapshots'), 189 - $viewer->renderHandleList($snapshot_phids)); 190 - } 191 - 192 - return id(new PHUIObjectBoxView()) 193 - ->setHeader($header) 194 - ->addPropertyList($properties); 195 - } 196 - 197 - public function renderConfigurationWarningIfRequired() { 198 - $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); 199 - if ($alt === null) { 200 - return id(new PHUIInfoView()) 201 - ->setTitle(pht( 202 - '%s must be configured!', 203 - 'security.alternate-file-domain')) 204 - ->setSeverity(PHUIInfoView::SEVERITY_ERROR) 205 - ->appendChild( 206 - phutil_tag( 207 - 'p', 208 - array(), 209 - pht( 210 - "Because Phragment generates files (such as ZIP archives and ". 211 - "patches) as they are requested, it requires that you configure ". 212 - "the `%s` option. This option on it's own will also provide ". 213 - "additional security when serving files across Phabricator.", 214 - 'security.alternate-file-domain'))); 215 - } 216 - return null; 217 - } 218 - 219 - /** 220 - * We use this to disable the download links if the alternate domain is 221 - * not configured correctly. Although the download links will mostly work 222 - * for logged in users without an alternate domain, the behaviour is 223 - * reasonably non-consistent and will deny public users, even if policies 224 - * are configured otherwise (because the Files app does not support showing 225 - * the info page to viewers who are not logged in). 226 - */ 227 - public function isCorrectlyConfigured() { 228 - $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); 229 - return $alt !== null; 230 - } 231 - 232 - }
-135
src/applications/phragment/controller/PhragmentCreateController.php
··· 1 - <?php 2 - 3 - final class PhragmentCreateController extends PhragmentController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $dblob = $request->getURIData('dblob'); 8 - 9 - $parent = null; 10 - $parents = $this->loadParentFragments($dblob); 11 - if ($parents === null) { 12 - return new Aphront404Response(); 13 - } 14 - if (count($parents) !== 0) { 15 - $parent = idx($parents, count($parents) - 1, null); 16 - } 17 - 18 - $parent_path = ''; 19 - if ($parent !== null) { 20 - $parent_path = $parent->getPath(); 21 - } 22 - $parent_path = trim($parent_path, '/'); 23 - 24 - $fragment = id(new PhragmentFragment()); 25 - 26 - $error_view = null; 27 - 28 - if ($request->isFormPost()) { 29 - $errors = array(); 30 - 31 - $v_name = $request->getStr('name'); 32 - $v_fileid = $request->getInt('fileID'); 33 - $v_viewpolicy = $request->getStr('viewPolicy'); 34 - $v_editpolicy = $request->getStr('editPolicy'); 35 - 36 - if (strpos($v_name, '/') !== false) { 37 - $errors[] = pht("The fragment name can not contain '/'."); 38 - } 39 - 40 - $file = id(new PhabricatorFileQuery()) 41 - ->setViewer($viewer) 42 - ->withIDs(array($v_fileid)) 43 - ->executeOne(); 44 - if (!$file) { 45 - $errors[] = pht("The specified file doesn't exist."); 46 - } 47 - 48 - if (!count($errors)) { 49 - $depth = 1; 50 - if ($parent !== null) { 51 - $depth = $parent->getDepth() + 1; 52 - } 53 - 54 - PhragmentFragment::createFromFile( 55 - $viewer, 56 - $file, 57 - trim($parent_path.'/'.$v_name, '/'), 58 - $v_viewpolicy, 59 - $v_editpolicy); 60 - 61 - return id(new AphrontRedirectResponse()) 62 - ->setURI('/phragment/browse/'.trim($parent_path.'/'.$v_name, '/')); 63 - } else { 64 - $error_view = id(new PHUIInfoView()) 65 - ->setErrors($errors) 66 - ->setTitle(pht('Errors while creating fragment')); 67 - } 68 - } 69 - 70 - $policies = id(new PhabricatorPolicyQuery()) 71 - ->setViewer($viewer) 72 - ->setObject($fragment) 73 - ->execute(); 74 - 75 - $form = id(new AphrontFormView()) 76 - ->setUser($viewer) 77 - ->appendChild( 78 - id(new AphrontFormTextControl()) 79 - ->setLabel(pht('Parent Path')) 80 - ->setDisabled(true) 81 - ->setValue('/'.trim($parent_path.'/', '/'))) 82 - ->appendChild( 83 - id(new AphrontFormTextControl()) 84 - ->setLabel(pht('Name')) 85 - ->setName('name')) 86 - ->appendChild( 87 - id(new AphrontFormTextControl()) 88 - ->setLabel(pht('File ID')) 89 - ->setName('fileID')) 90 - ->appendChild( 91 - id(new AphrontFormPolicyControl()) 92 - ->setUser($viewer) 93 - ->setName('viewPolicy') 94 - ->setPolicyObject($fragment) 95 - ->setPolicies($policies) 96 - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) 97 - ->appendChild( 98 - id(new AphrontFormPolicyControl()) 99 - ->setUser($viewer) 100 - ->setName('editPolicy') 101 - ->setPolicyObject($fragment) 102 - ->setPolicies($policies) 103 - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)) 104 - ->appendChild( 105 - id(new AphrontFormSubmitControl()) 106 - ->setValue(pht('Create Fragment')) 107 - ->addCancelButton( 108 - $this->getApplicationURI('browse/'.$parent_path))); 109 - 110 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 111 - $crumbs->addTextCrumb(pht('Create Fragment')); 112 - 113 - $box = id(new PHUIObjectBoxView()) 114 - ->setHeaderText(pht('Create Fragment')) 115 - ->setForm($form); 116 - 117 - if ($error_view) { 118 - $box->setInfoView($error_view); 119 - } 120 - 121 - $title = pht('Create Fragments'); 122 - 123 - $view = array( 124 - $this->renderConfigurationWarningIfRequired(), 125 - $box, 126 - ); 127 - 128 - return $this->newPage() 129 - ->setTitle($title) 130 - ->setCrumbs($crumbs) 131 - ->appendChild($view); 132 - 133 - } 134 - 135 - }
-109
src/applications/phragment/controller/PhragmentHistoryController.php
··· 1 - <?php 2 - 3 - final class PhragmentHistoryController extends PhragmentController { 4 - 5 - public function shouldAllowPublic() { 6 - return true; 7 - } 8 - 9 - public function handleRequest(AphrontRequest $request) { 10 - $viewer = $request->getViewer(); 11 - $dblob = $request->getURIData('dblob'); 12 - 13 - $parents = $this->loadParentFragments($dblob); 14 - if ($parents === null) { 15 - return new Aphront404Response(); 16 - } 17 - $current = idx($parents, count($parents) - 1, null); 18 - 19 - $path = $current->getPath(); 20 - 21 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 22 - if ($this->hasApplicationCapability( 23 - PhragmentCanCreateCapability::CAPABILITY)) { 24 - $crumbs->addAction( 25 - id(new PHUIListItemView()) 26 - ->setName(pht('Create Fragment')) 27 - ->setHref($this->getApplicationURI('/create/'.$path)) 28 - ->setIcon('fa-plus-square')); 29 - } 30 - 31 - $current_box = $this->createCurrentFragmentView($current, true); 32 - 33 - $versions = id(new PhragmentFragmentVersionQuery()) 34 - ->setViewer($viewer) 35 - ->withFragmentPHIDs(array($current->getPHID())) 36 - ->execute(); 37 - 38 - $list = id(new PHUIObjectItemListView()) 39 - ->setUser($viewer); 40 - 41 - $file_phids = mpull($versions, 'getFilePHID'); 42 - $files = id(new PhabricatorFileQuery()) 43 - ->setViewer($viewer) 44 - ->withPHIDs($file_phids) 45 - ->execute(); 46 - $files = mpull($files, null, 'getPHID'); 47 - 48 - $can_edit = PhabricatorPolicyFilter::hasCapability( 49 - $viewer, 50 - $current, 51 - PhabricatorPolicyCapability::CAN_EDIT); 52 - 53 - $first = true; 54 - foreach ($versions as $version) { 55 - $item = id(new PHUIObjectItemView()); 56 - $item->setHeader(pht('Version %s', $version->getSequence())); 57 - $item->setHref($version->getURI()); 58 - $item->addAttribute(phabricator_datetime( 59 - $version->getDateCreated(), 60 - $viewer)); 61 - 62 - if ($version->getFilePHID() === null) { 63 - $item->setDisabled(true); 64 - $item->addAttribute(pht('Deletion')); 65 - } 66 - 67 - if (!$first && $can_edit) { 68 - $item->addAction(id(new PHUIListItemView()) 69 - ->setIcon('fa-refresh') 70 - ->setRenderNameAsTooltip(true) 71 - ->setWorkflow(true) 72 - ->setName(pht('Revert to Here')) 73 - ->setHref($this->getApplicationURI( 74 - 'revert/'.$version->getID().'/'.$current->getPath()))); 75 - } 76 - 77 - $disabled = !isset($files[$version->getFilePHID()]); 78 - $action = id(new PHUIListItemView()) 79 - ->setIcon('fa-download') 80 - ->setDisabled($disabled || !$this->isCorrectlyConfigured()) 81 - ->setRenderNameAsTooltip(true) 82 - ->setName(pht('Download')); 83 - if (!$disabled && $this->isCorrectlyConfigured()) { 84 - $action->setHref($files[$version->getFilePHID()] 85 - ->getDownloadURI($version->getURI())); 86 - } 87 - $item->addAction($action); 88 - 89 - $list->addItem($item); 90 - 91 - $first = false; 92 - } 93 - 94 - $title = pht('Fragment History'); 95 - 96 - $view = array( 97 - $this->renderConfigurationWarningIfRequired(), 98 - $current_box, 99 - $list, 100 - ); 101 - 102 - return $this->newPage() 103 - ->setTitle($title) 104 - ->setCrumbs($crumbs) 105 - ->appendChild($view); 106 - 107 - } 108 - 109 - }
-97
src/applications/phragment/controller/PhragmentPatchController.php
··· 1 - <?php 2 - 3 - final class PhragmentPatchController extends PhragmentController { 4 - 5 - public function shouldAllowPublic() { 6 - return true; 7 - } 8 - 9 - public function handleRequest(AphrontRequest $request) { 10 - $viewer = $request->getViewer(); 11 - $aid = $request->getURIData('aid'); 12 - $bid = $request->getURIData('bid'); 13 - 14 - // If "aid" is "x", then it means the user wants to generate 15 - // a patch of an empty file to the version specified by "bid". 16 - 17 - $ids = array($aid, $bid); 18 - if ($aid === 'x') { 19 - $ids = array($bid); 20 - } 21 - 22 - $versions = id(new PhragmentFragmentVersionQuery()) 23 - ->setViewer($viewer) 24 - ->withIDs($ids) 25 - ->execute(); 26 - 27 - $version_a = null; 28 - if ($aid !== 'x') { 29 - $version_a = idx($versions, $aid, null); 30 - if ($version_a === null) { 31 - return new Aphront404Response(); 32 - } 33 - } 34 - 35 - $version_b = idx($versions, $bid, null); 36 - if ($version_b === null) { 37 - return new Aphront404Response(); 38 - } 39 - 40 - $file_phids = array(); 41 - if ($version_a !== null) { 42 - $file_phids[] = $version_a->getFilePHID(); 43 - } 44 - $file_phids[] = $version_b->getFilePHID(); 45 - 46 - $files = id(new PhabricatorFileQuery()) 47 - ->setViewer($viewer) 48 - ->withPHIDs($file_phids) 49 - ->execute(); 50 - $files = mpull($files, null, 'getPHID'); 51 - 52 - $file_a = null; 53 - if ($version_a != null) { 54 - $file_a = idx($files, $version_a->getFilePHID(), null); 55 - } 56 - $file_b = idx($files, $version_b->getFilePHID(), null); 57 - 58 - $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b); 59 - 60 - if ($patch === null) { 61 - // There are no differences between the two files, so we output 62 - // an empty patch. 63 - $patch = ''; 64 - } 65 - 66 - $a_sequence = 'x'; 67 - if ($version_a !== null) { 68 - $a_sequence = $version_a->getSequence(); 69 - } 70 - 71 - $name = 72 - $version_b->getFragment()->getName().'.'. 73 - $a_sequence.'.'. 74 - $version_b->getSequence().'.patch'; 75 - 76 - $return = $version_b->getURI(); 77 - if ($request->getExists('return')) { 78 - $return = $request->getStr('return'); 79 - } 80 - 81 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 82 - $result = PhabricatorFile::newFromFileData( 83 - $patch, 84 - array( 85 - 'name' => $name, 86 - 'mime-type' => 'text/plain', 87 - 'ttl.relative' => phutil_units('24 hours in seconds'), 88 - )); 89 - 90 - $result->attachToObject($version_b->getFragmentPHID()); 91 - unset($unguarded); 92 - 93 - return id(new AphrontRedirectResponse()) 94 - ->setURI($result->getDownloadURI($return)); 95 - } 96 - 97 - }
-106
src/applications/phragment/controller/PhragmentPolicyController.php
··· 1 - <?php 2 - 3 - final class PhragmentPolicyController extends PhragmentController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $dblob = $request->getURIData('dblob'); 8 - 9 - $parents = $this->loadParentFragments($dblob); 10 - if ($parents === null) { 11 - return new Aphront404Response(); 12 - } 13 - $fragment = idx($parents, count($parents) - 1, null); 14 - 15 - $error_view = null; 16 - 17 - if ($request->isFormPost()) { 18 - $errors = array(); 19 - 20 - $v_view_policy = $request->getStr('viewPolicy'); 21 - $v_edit_policy = $request->getStr('editPolicy'); 22 - $v_replace_children = $request->getBool('replacePoliciesOnChildren'); 23 - 24 - $fragment->setViewPolicy($v_view_policy); 25 - $fragment->setEditPolicy($v_edit_policy); 26 - 27 - $fragment->save(); 28 - 29 - if ($v_replace_children) { 30 - // If you can edit a fragment, you can forcibly set the policies 31 - // on child fragments, regardless of whether you can see them or not. 32 - $children = id(new PhragmentFragmentQuery()) 33 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 34 - ->withLeadingPath($fragment->getPath().'/') 35 - ->execute(); 36 - $children_phids = mpull($children, 'getPHID'); 37 - 38 - $fragment->openTransaction(); 39 - foreach ($children as $child) { 40 - $child->setViewPolicy($v_view_policy); 41 - $child->setEditPolicy($v_edit_policy); 42 - $child->save(); 43 - } 44 - $fragment->saveTransaction(); 45 - } 46 - 47 - return id(new AphrontRedirectResponse()) 48 - ->setURI('/phragment/browse/'.$fragment->getPath()); 49 - } 50 - 51 - $policies = id(new PhabricatorPolicyQuery()) 52 - ->setViewer($viewer) 53 - ->setObject($fragment) 54 - ->execute(); 55 - 56 - $form = id(new AphrontFormView()) 57 - ->setUser($viewer) 58 - ->appendChild( 59 - id(new AphrontFormPolicyControl()) 60 - ->setName('viewPolicy') 61 - ->setPolicyObject($fragment) 62 - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 63 - ->setPolicies($policies)) 64 - ->appendChild( 65 - id(new AphrontFormPolicyControl()) 66 - ->setName('editPolicy') 67 - ->setPolicyObject($fragment) 68 - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 69 - ->setPolicies($policies)) 70 - ->appendChild( 71 - id(new AphrontFormCheckboxControl()) 72 - ->addCheckbox( 73 - 'replacePoliciesOnChildren', 74 - 'true', 75 - pht( 76 - 'Replace policies on child fragments with '. 77 - 'the policies above.'))) 78 - ->appendChild( 79 - id(new AphrontFormSubmitControl()) 80 - ->setValue(pht('Save Fragment Policies')) 81 - ->addCancelButton( 82 - $this->getApplicationURI('browse/'.$fragment->getPath()))); 83 - 84 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 85 - $crumbs->addTextCrumb(pht('Edit Fragment Policies')); 86 - 87 - $box = id(new PHUIObjectBoxView()) 88 - ->setHeaderText(pht('Edit Fragment Policies: %s', $fragment->getPath())) 89 - ->setValidationException(null) 90 - ->setForm($form); 91 - 92 - $title = pht('Edit Fragment Policies'); 93 - 94 - $view = array( 95 - $this->renderConfigurationWarningIfRequired(), 96 - $box, 97 - ); 98 - 99 - return $this->newPage() 100 - ->setTitle($title) 101 - ->setCrumbs($crumbs) 102 - ->appendChild($view); 103 - 104 - } 105 - 106 - }
-78
src/applications/phragment/controller/PhragmentRevertController.php
··· 1 - <?php 2 - 3 - final class PhragmentRevertController extends PhragmentController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $id = $request->getURIData('id'); 8 - $dblob = $request->getURIData('dblob'); 9 - 10 - $fragment = id(new PhragmentFragmentQuery()) 11 - ->setViewer($viewer) 12 - ->withPaths(array($dblob)) 13 - ->requireCapabilities( 14 - array( 15 - PhabricatorPolicyCapability::CAN_VIEW, 16 - PhabricatorPolicyCapability::CAN_EDIT, 17 - )) 18 - ->executeOne(); 19 - if ($fragment === null) { 20 - return new Aphront404Response(); 21 - } 22 - 23 - $version = id(new PhragmentFragmentVersionQuery()) 24 - ->setViewer($viewer) 25 - ->withFragmentPHIDs(array($fragment->getPHID())) 26 - ->withIDs(array($id)) 27 - ->executeOne(); 28 - if ($version === null) { 29 - return new Aphront404Response(); 30 - } 31 - 32 - if ($request->isDialogFormPost()) { 33 - $file_phid = $version->getFilePHID(); 34 - 35 - $file = null; 36 - if ($file_phid !== null) { 37 - $file = id(new PhabricatorFileQuery()) 38 - ->setViewer($viewer) 39 - ->withPHIDs(array($file_phid)) 40 - ->executeOne(); 41 - if ($file === null) { 42 - throw new Exception( 43 - pht('The file associated with this version was not found.')); 44 - } 45 - } 46 - 47 - if ($file === null) { 48 - $fragment->deleteFile($viewer); 49 - } else { 50 - $fragment->updateFromFile($viewer, $file); 51 - } 52 - 53 - return id(new AphrontRedirectResponse()) 54 - ->setURI($this->getApplicationURI('/history/'.$dblob)); 55 - } 56 - 57 - return $this->createDialog($fragment, $version); 58 - } 59 - 60 - public function createDialog( 61 - PhragmentFragment $fragment, 62 - PhragmentFragmentVersion $version) { 63 - 64 - $viewer = $this->getViewer(); 65 - 66 - $dialog = id(new AphrontDialogView()) 67 - ->setTitle(pht('Really revert this fragment?')) 68 - ->setUser($this->getViewer()) 69 - ->addSubmitButton(pht('Revert')) 70 - ->addCancelButton(pht('Cancel')) 71 - ->appendParagraph(pht( 72 - 'Reverting this fragment to version %d will create a new version of '. 73 - 'the fragment. It will not delete any version history.', 74 - $version->getSequence())); 75 - return id(new AphrontDialogResponse())->setDialog($dialog); 76 - } 77 - 78 - }
-170
src/applications/phragment/controller/PhragmentSnapshotCreateController.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotCreateController extends PhragmentController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $dblob = $request->getURIData('dblob'); 8 - 9 - $parents = $this->loadParentFragments($dblob); 10 - if ($parents === null) { 11 - return new Aphront404Response(); 12 - } 13 - $fragment = nonempty(last($parents), null); 14 - if ($fragment === null) { 15 - return new Aphront404Response(); 16 - } 17 - 18 - PhabricatorPolicyFilter::requireCapability( 19 - $viewer, 20 - $fragment, 21 - PhabricatorPolicyCapability::CAN_EDIT); 22 - 23 - $children = id(new PhragmentFragmentQuery()) 24 - ->setViewer($viewer) 25 - ->needLatestVersion(true) 26 - ->withLeadingPath($fragment->getPath().'/') 27 - ->execute(); 28 - 29 - $errors = array(); 30 - if ($request->isFormPost()) { 31 - 32 - $v_name = $request->getStr('name'); 33 - if (strlen($v_name) === 0) { 34 - $errors[] = pht('You must specify a name.'); 35 - } 36 - if (strpos($v_name, '/') !== false) { 37 - $errors[] = pht('Snapshot names can not contain "/".'); 38 - } 39 - 40 - if (!count($errors)) { 41 - $snapshot = null; 42 - 43 - try { 44 - // Create the snapshot. 45 - $snapshot = id(new PhragmentSnapshot()) 46 - ->setPrimaryFragmentPHID($fragment->getPHID()) 47 - ->setName($v_name) 48 - ->save(); 49 - } catch (AphrontDuplicateKeyQueryException $e) { 50 - $errors[] = pht('A snapshot with this name already exists.'); 51 - } 52 - 53 - if (!count($errors)) { 54 - // Add the primary fragment. 55 - id(new PhragmentSnapshotChild()) 56 - ->setSnapshotPHID($snapshot->getPHID()) 57 - ->setFragmentPHID($fragment->getPHID()) 58 - ->setFragmentVersionPHID($fragment->getLatestVersionPHID()) 59 - ->save(); 60 - 61 - // Add all of the child fragments. 62 - foreach ($children as $child) { 63 - id(new PhragmentSnapshotChild()) 64 - ->setSnapshotPHID($snapshot->getPHID()) 65 - ->setFragmentPHID($child->getPHID()) 66 - ->setFragmentVersionPHID($child->getLatestVersionPHID()) 67 - ->save(); 68 - } 69 - 70 - return id(new AphrontRedirectResponse()) 71 - ->setURI('/phragment/snapshot/view/'.$snapshot->getID()); 72 - } 73 - } 74 - } 75 - 76 - $fragment_sequence = '-'; 77 - if ($fragment->getLatestVersion() !== null) { 78 - $fragment_sequence = $fragment->getLatestVersion()->getSequence(); 79 - } 80 - 81 - $rows = array(); 82 - $rows[] = phutil_tag( 83 - 'tr', 84 - array(), 85 - array( 86 - phutil_tag('th', array(), pht('Fragment')), 87 - phutil_tag('th', array(), pht('Version')), 88 - )); 89 - $rows[] = phutil_tag( 90 - 'tr', 91 - array(), 92 - array( 93 - phutil_tag('td', array(), $fragment->getPath()), 94 - phutil_tag('td', array(), $fragment_sequence), 95 - )); 96 - foreach ($children as $child) { 97 - $sequence = '-'; 98 - if ($child->getLatestVersion() !== null) { 99 - $sequence = $child->getLatestVersion()->getSequence(); 100 - } 101 - $rows[] = phutil_tag( 102 - 'tr', 103 - array(), 104 - array( 105 - phutil_tag('td', array(), $child->getPath()), 106 - phutil_tag('td', array(), $sequence), 107 - )); 108 - } 109 - 110 - $table = phutil_tag( 111 - 'table', 112 - array('class' => 'remarkup-table'), 113 - $rows); 114 - 115 - $container = phutil_tag( 116 - 'div', 117 - array('class' => 'phabricator-remarkup'), 118 - array( 119 - phutil_tag( 120 - 'p', 121 - array(), 122 - pht( 123 - 'The snapshot will contain the following fragments at '. 124 - 'the specified versions: ')), 125 - $table, 126 - )); 127 - 128 - $form = id(new AphrontFormView()) 129 - ->setUser($viewer) 130 - ->appendChild( 131 - id(new AphrontFormTextControl()) 132 - ->setLabel(pht('Fragment Path')) 133 - ->setDisabled(true) 134 - ->setValue('/'.$fragment->getPath())) 135 - ->appendChild( 136 - id(new AphrontFormTextControl()) 137 - ->setLabel(pht('Snapshot Name')) 138 - ->setName('name')) 139 - ->appendChild( 140 - id(new AphrontFormSubmitControl()) 141 - ->setValue(pht('Create Snapshot')) 142 - ->addCancelButton( 143 - $this->getApplicationURI('browse/'.$fragment->getPath()))) 144 - ->appendChild( 145 - id(new PHUIFormDividerControl())) 146 - ->appendInstructions($container); 147 - 148 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 149 - $crumbs->addTextCrumb(pht('Create Snapshot')); 150 - 151 - $box = id(new PHUIObjectBoxView()) 152 - ->setHeaderText(pht('Create Snapshot of %s', $fragment->getName())) 153 - ->setFormErrors($errors) 154 - ->setForm($form); 155 - 156 - $title = pht('Create Snapshot'); 157 - 158 - $view = array( 159 - $this->renderConfigurationWarningIfRequired(), 160 - $box, 161 - ); 162 - 163 - return $this->newPage() 164 - ->setTitle($title) 165 - ->setCrumbs($crumbs) 166 - ->appendChild($view); 167 - 168 - } 169 - 170 - }
-47
src/applications/phragment/controller/PhragmentSnapshotDeleteController.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotDeleteController extends PhragmentController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $id = $request->getURIData('id'); 8 - 9 - $snapshot = id(new PhragmentSnapshotQuery()) 10 - ->setViewer($viewer) 11 - ->requireCapabilities(array( 12 - PhabricatorPolicyCapability::CAN_VIEW, 13 - PhabricatorPolicyCapability::CAN_EDIT, 14 - )) 15 - ->withIDs(array($id)) 16 - ->executeOne(); 17 - if ($snapshot === null) { 18 - return new Aphront404Response(); 19 - } 20 - 21 - if ($request->isDialogFormPost()) { 22 - $fragment_uri = $snapshot->getPrimaryFragment()->getURI(); 23 - 24 - $snapshot->delete(); 25 - 26 - return id(new AphrontRedirectResponse()) 27 - ->setURI($fragment_uri); 28 - } 29 - 30 - return $this->createDialog(); 31 - } 32 - 33 - public function createDialog() { 34 - $viewer = $this->getViewer(); 35 - 36 - $dialog = id(new AphrontDialogView()) 37 - ->setTitle(pht('Really delete this snapshot?')) 38 - ->setUser($this->getViewer()) 39 - ->addSubmitButton(pht('Delete')) 40 - ->addCancelButton(pht('Cancel')) 41 - ->appendParagraph(pht( 42 - 'Deleting this snapshot is a permanent operation. You can not '. 43 - 'recover the state of the snapshot.')); 44 - return id(new AphrontDialogResponse())->setDialog($dialog); 45 - } 46 - 47 - }
-188
src/applications/phragment/controller/PhragmentSnapshotPromoteController.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotPromoteController extends PhragmentController { 4 - 5 - private $targetSnapshot; 6 - private $targetFragment; 7 - private $snapshots; 8 - private $options; 9 - 10 - public function handleRequest(AphrontRequest $request) { 11 - $viewer = $request->getViewer(); 12 - $id = $request->getURIData('id'); 13 - $dblob = $request->getURIData('dblob'); 14 - 15 - // When the user is promoting a snapshot to the latest version, the 16 - // identifier is a fragment path. 17 - if ($dblob !== null) { 18 - $this->targetFragment = id(new PhragmentFragmentQuery()) 19 - ->setViewer($viewer) 20 - ->requireCapabilities(array( 21 - PhabricatorPolicyCapability::CAN_VIEW, 22 - PhabricatorPolicyCapability::CAN_EDIT, 23 - )) 24 - ->withPaths(array($dblob)) 25 - ->executeOne(); 26 - if ($this->targetFragment === null) { 27 - return new Aphront404Response(); 28 - } 29 - 30 - $this->snapshots = id(new PhragmentSnapshotQuery()) 31 - ->setViewer($viewer) 32 - ->withPrimaryFragmentPHIDs(array($this->targetFragment->getPHID())) 33 - ->execute(); 34 - } 35 - 36 - // When the user is promoting a snapshot to another snapshot, the 37 - // identifier is another snapshot ID. 38 - if ($id !== null) { 39 - $this->targetSnapshot = id(new PhragmentSnapshotQuery()) 40 - ->setViewer($viewer) 41 - ->requireCapabilities(array( 42 - PhabricatorPolicyCapability::CAN_VIEW, 43 - PhabricatorPolicyCapability::CAN_EDIT, 44 - )) 45 - ->withIDs(array($id)) 46 - ->executeOne(); 47 - if ($this->targetSnapshot === null) { 48 - return new Aphront404Response(); 49 - } 50 - 51 - $this->snapshots = id(new PhragmentSnapshotQuery()) 52 - ->setViewer($viewer) 53 - ->withPrimaryFragmentPHIDs(array( 54 - $this->targetSnapshot->getPrimaryFragmentPHID(), 55 - )) 56 - ->execute(); 57 - } 58 - 59 - // If there's no identifier, just 404. 60 - if ($this->snapshots === null) { 61 - return new Aphront404Response(); 62 - } 63 - 64 - // Work out what options the user has. 65 - $this->options = mpull( 66 - $this->snapshots, 67 - 'getName', 68 - 'getID'); 69 - if ($id !== null) { 70 - unset($this->options[$id]); 71 - } 72 - 73 - // If there's no options, show a dialog telling the 74 - // user there are no snapshots to promote. 75 - if (count($this->options) === 0) { 76 - return id(new AphrontDialogResponse())->setDialog( 77 - id(new AphrontDialogView()) 78 - ->setTitle(pht('No snapshots to promote')) 79 - ->appendParagraph(pht( 80 - 'There are no snapshots available to promote.')) 81 - ->setUser($this->getViewer()) 82 - ->addCancelButton(pht('Cancel'))); 83 - } 84 - 85 - // Handle snapshot promotion. 86 - if ($request->isDialogFormPost()) { 87 - $snapshot = id(new PhragmentSnapshotQuery()) 88 - ->setViewer($viewer) 89 - ->withIDs(array($request->getStr('snapshot'))) 90 - ->executeOne(); 91 - if ($snapshot === null) { 92 - return new Aphront404Response(); 93 - } 94 - 95 - $snapshot->openTransaction(); 96 - // Delete all existing child entries. 97 - $children = id(new PhragmentSnapshotChildQuery()) 98 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 99 - ->withSnapshotPHIDs(array($snapshot->getPHID())) 100 - ->execute(); 101 - foreach ($children as $child) { 102 - $child->delete(); 103 - } 104 - 105 - if ($id === null) { 106 - // The user is promoting the snapshot to the latest version. 107 - $children = id(new PhragmentFragmentQuery()) 108 - ->setViewer($viewer) 109 - ->needLatestVersion(true) 110 - ->withLeadingPath($this->targetFragment->getPath().'/') 111 - ->execute(); 112 - 113 - // Add the primary fragment. 114 - id(new PhragmentSnapshotChild()) 115 - ->setSnapshotPHID($snapshot->getPHID()) 116 - ->setFragmentPHID($this->targetFragment->getPHID()) 117 - ->setFragmentVersionPHID( 118 - $this->targetFragment->getLatestVersionPHID()) 119 - ->save(); 120 - 121 - // Add all of the child fragments. 122 - foreach ($children as $child) { 123 - id(new PhragmentSnapshotChild()) 124 - ->setSnapshotPHID($snapshot->getPHID()) 125 - ->setFragmentPHID($child->getPHID()) 126 - ->setFragmentVersionPHID($child->getLatestVersionPHID()) 127 - ->save(); 128 - } 129 - } else { 130 - // The user is promoting the snapshot to another snapshot. We just 131 - // copy the other snapshot's child entries and change the snapshot 132 - // PHID to make it identical. 133 - $children = id(new PhragmentSnapshotChildQuery()) 134 - ->setViewer($viewer) 135 - ->withSnapshotPHIDs(array($this->targetSnapshot->getPHID())) 136 - ->execute(); 137 - foreach ($children as $child) { 138 - id(new PhragmentSnapshotChild()) 139 - ->setSnapshotPHID($snapshot->getPHID()) 140 - ->setFragmentPHID($child->getFragmentPHID()) 141 - ->setFragmentVersionPHID($child->getFragmentVersionPHID()) 142 - ->save(); 143 - } 144 - } 145 - $snapshot->saveTransaction(); 146 - 147 - if ($id === null) { 148 - return id(new AphrontRedirectResponse()) 149 - ->setURI($this->targetFragment->getURI()); 150 - } else { 151 - return id(new AphrontRedirectResponse()) 152 - ->setURI($this->targetSnapshot->getURI()); 153 - } 154 - } 155 - 156 - return $this->createDialog($id); 157 - } 158 - 159 - public function createDialog($id) { 160 - $viewer = $this->getViewer(); 161 - 162 - $dialog = id(new AphrontDialogView()) 163 - ->setTitle(pht('Promote which snapshot?')) 164 - ->setUser($this->getViewer()) 165 - ->addSubmitButton(pht('Promote')) 166 - ->addCancelButton(pht('Cancel')); 167 - 168 - if ($id === null) { 169 - // The user is promoting a snapshot to the latest version. 170 - $dialog->appendParagraph(pht( 171 - 'Select the snapshot you want to promote to the latest version:')); 172 - } else { 173 - // The user is promoting a snapshot to another snapshot. 174 - $dialog->appendParagraph(pht( 175 - "Select the snapshot you want to promote to '%s':", 176 - $this->targetSnapshot->getName())); 177 - } 178 - 179 - $dialog->appendChild( 180 - id(new AphrontFormSelectControl()) 181 - ->setUser($viewer) 182 - ->setName('snapshot') 183 - ->setOptions($this->options)); 184 - 185 - return id(new AphrontDialogResponse())->setDialog($dialog); 186 - } 187 - 188 - }
-145
src/applications/phragment/controller/PhragmentSnapshotViewController.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotViewController extends PhragmentController { 4 - 5 - public function shouldAllowPublic() { 6 - return true; 7 - } 8 - 9 - public function handleRequest(AphrontRequest $request) { 10 - $viewer = $request->getViewer(); 11 - $id = $request->getURIData('id'); 12 - 13 - $snapshot = id(new PhragmentSnapshotQuery()) 14 - ->setViewer($viewer) 15 - ->withIDs(array($id)) 16 - ->executeOne(); 17 - if ($snapshot === null) { 18 - return new Aphront404Response(); 19 - } 20 - 21 - $box = $this->createSnapshotView($snapshot); 22 - 23 - $fragment = id(new PhragmentFragmentQuery()) 24 - ->setViewer($viewer) 25 - ->withPHIDs(array($snapshot->getPrimaryFragmentPHID())) 26 - ->executeOne(); 27 - if ($fragment === null) { 28 - return new Aphront404Response(); 29 - } 30 - 31 - $parents = $this->loadParentFragments($fragment->getPath()); 32 - if ($parents === null) { 33 - return new Aphront404Response(); 34 - } 35 - 36 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 37 - $crumbs->addTextCrumb(pht('"%s" Snapshot', $snapshot->getName())); 38 - 39 - $children = id(new PhragmentSnapshotChildQuery()) 40 - ->setViewer($viewer) 41 - ->needFragments(true) 42 - ->needFragmentVersions(true) 43 - ->withSnapshotPHIDs(array($snapshot->getPHID())) 44 - ->execute(); 45 - 46 - $list = id(new PHUIObjectItemListView()) 47 - ->setUser($viewer); 48 - 49 - foreach ($children as $child) { 50 - $item = id(new PHUIObjectItemView()) 51 - ->setHeader($child->getFragment()->getPath()); 52 - 53 - if ($child->getFragmentVersion() !== null) { 54 - $item 55 - ->setHref($child->getFragmentVersion()->getURI()) 56 - ->addAttribute(pht( 57 - 'Version %s', 58 - $child->getFragmentVersion()->getSequence())); 59 - } else { 60 - $item 61 - ->setHref($child->getFragment()->getURI()) 62 - ->addAttribute(pht('Directory')); 63 - } 64 - 65 - $list->addItem($item); 66 - } 67 - 68 - $title = pht('View Snapshot'); 69 - 70 - $view = array( 71 - $this->renderConfigurationWarningIfRequired(), 72 - $box, 73 - $list, 74 - ); 75 - 76 - return $this->newPage() 77 - ->setTitle($title) 78 - ->setCrumbs($crumbs) 79 - ->appendChild($view); 80 - } 81 - 82 - protected function createSnapshotView($snapshot) { 83 - if ($snapshot === null) { 84 - return null; 85 - } 86 - 87 - $viewer = $this->getRequest()->getUser(); 88 - 89 - $header = id(new PHUIHeaderView()) 90 - ->setHeader(pht('"%s" Snapshot', $snapshot->getName())) 91 - ->setPolicyObject($snapshot) 92 - ->setUser($viewer); 93 - 94 - $zip_uri = $this->getApplicationURI( 95 - 'zip@'.$snapshot->getName(). 96 - '/'.$snapshot->getPrimaryFragment()->getPath()); 97 - 98 - $can_edit = PhabricatorPolicyFilter::hasCapability( 99 - $viewer, 100 - $snapshot, 101 - PhabricatorPolicyCapability::CAN_EDIT); 102 - 103 - $actions = id(new PhabricatorActionListView()) 104 - ->setUser($viewer) 105 - ->setObject($snapshot); 106 - $actions->addAction( 107 - id(new PhabricatorActionView()) 108 - ->setName(pht('Download Snapshot as ZIP')) 109 - ->setHref($this->isCorrectlyConfigured() ? $zip_uri : null) 110 - ->setDisabled(!$this->isCorrectlyConfigured()) 111 - ->setIcon('fa-floppy-o')); 112 - $actions->addAction( 113 - id(new PhabricatorActionView()) 114 - ->setName(pht('Delete Snapshot')) 115 - ->setHref($this->getApplicationURI( 116 - 'snapshot/delete/'.$snapshot->getID().'/')) 117 - ->setDisabled(!$can_edit) 118 - ->setWorkflow(true) 119 - ->setIcon('fa-times')); 120 - $actions->addAction( 121 - id(new PhabricatorActionView()) 122 - ->setName(pht('Promote Another Snapshot to Here')) 123 - ->setHref($this->getApplicationURI( 124 - 'snapshot/promote/'.$snapshot->getID().'/')) 125 - ->setDisabled(!$can_edit) 126 - ->setWorkflow(true) 127 - ->setIcon('fa-arrow-up')); 128 - 129 - $properties = id(new PHUIPropertyListView()) 130 - ->setUser($viewer) 131 - ->setObject($snapshot) 132 - ->setActionList($actions); 133 - 134 - $properties->addProperty( 135 - pht('Name'), 136 - $snapshot->getName()); 137 - $properties->addProperty( 138 - pht('Fragment'), 139 - $viewer->renderHandle($snapshot->getPrimaryFragmentPHID())); 140 - 141 - return id(new PHUIObjectBoxView()) 142 - ->setHeader($header) 143 - ->addPropertyList($properties); 144 - } 145 - }
-80
src/applications/phragment/controller/PhragmentUpdateController.php
··· 1 - <?php 2 - 3 - final class PhragmentUpdateController extends PhragmentController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $dblob = $request->getURIData('dblob'); 8 - 9 - $parents = $this->loadParentFragments($dblob); 10 - if ($parents === null) { 11 - return new Aphront404Response(); 12 - } 13 - $fragment = idx($parents, count($parents) - 1, null); 14 - 15 - $error_view = null; 16 - 17 - if ($request->isFormPost()) { 18 - $errors = array(); 19 - 20 - $v_fileid = $request->getInt('fileID'); 21 - 22 - $file = id(new PhabricatorFile())->load($v_fileid); 23 - if ($file === null) { 24 - $errors[] = pht('The specified file doesn\'t exist.'); 25 - } 26 - 27 - if (!count($errors)) { 28 - // If the file is a ZIP archive (has application/zip mimetype) 29 - // then we extract the zip and apply versions for each of the 30 - // individual fragments, creating and deleting files as needed. 31 - if ($file->getMimeType() === 'application/zip') { 32 - $fragment->updateFromZIP($viewer, $file); 33 - } else { 34 - $fragment->updateFromFile($viewer, $file); 35 - } 36 - 37 - return id(new AphrontRedirectResponse()) 38 - ->setURI('/phragment/browse/'.$fragment->getPath()); 39 - } else { 40 - $error_view = id(new PHUIInfoView()) 41 - ->setErrors($errors) 42 - ->setTitle(pht('Errors while updating fragment')); 43 - } 44 - } 45 - 46 - $form = id(new AphrontFormView()) 47 - ->setUser($viewer) 48 - ->appendChild( 49 - id(new AphrontFormTextControl()) 50 - ->setLabel(pht('File ID')) 51 - ->setName('fileID')) 52 - ->appendChild( 53 - id(new AphrontFormSubmitControl()) 54 - ->setValue(pht('Update Fragment')) 55 - ->addCancelButton( 56 - $this->getApplicationURI('browse/'.$fragment->getPath()))); 57 - 58 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 59 - $crumbs->addTextCrumb(pht('Update Fragment')); 60 - 61 - $box = id(new PHUIObjectBoxView()) 62 - ->setHeaderText(pht('Update Fragment: %s', $fragment->getPath())) 63 - ->setValidationException(null) 64 - ->setForm($form); 65 - 66 - $title = pht('Update Fragment'); 67 - 68 - $view = array( 69 - $this->renderConfigurationWarningIfRequired(), 70 - $box, 71 - ); 72 - 73 - return $this->newPage() 74 - ->setTitle($title) 75 - ->setCrumbs($crumbs) 76 - ->appendChild($view); 77 - 78 - } 79 - 80 - }
-125
src/applications/phragment/controller/PhragmentVersionController.php
··· 1 - <?php 2 - 3 - final class PhragmentVersionController extends PhragmentController { 4 - 5 - public function shouldAllowPublic() { 6 - return true; 7 - } 8 - 9 - public function handleRequest(AphrontRequest $request) { 10 - $viewer = $request->getViewer(); 11 - $id = $request->getURIData('id'); 12 - 13 - $version = id(new PhragmentFragmentVersionQuery()) 14 - ->setViewer($viewer) 15 - ->withIDs(array($id)) 16 - ->executeOne(); 17 - if ($version === null) { 18 - return new Aphront404Response(); 19 - } 20 - 21 - $parents = $this->loadParentFragments($version->getFragment()->getPath()); 22 - if ($parents === null) { 23 - return new Aphront404Response(); 24 - } 25 - $current = idx($parents, count($parents) - 1, null); 26 - 27 - $crumbs = $this->buildApplicationCrumbsWithPath($parents); 28 - $crumbs->addTextCrumb(pht('View Version %d', $version->getSequence())); 29 - 30 - $file = id(new PhabricatorFileQuery()) 31 - ->setViewer($viewer) 32 - ->withPHIDs(array($version->getFilePHID())) 33 - ->executeOne(); 34 - if ($file !== null) { 35 - $file_uri = $file->getDownloadURI(); 36 - } 37 - 38 - $header = id(new PHUIHeaderView()) 39 - ->setHeader(pht( 40 - '%s at version %d', 41 - $version->getFragment()->getName(), 42 - $version->getSequence())) 43 - ->setPolicyObject($version) 44 - ->setUser($viewer); 45 - 46 - $actions = id(new PhabricatorActionListView()) 47 - ->setUser($viewer) 48 - ->setObject($version); 49 - $actions->addAction( 50 - id(new PhabricatorActionView()) 51 - ->setName(pht('Download Version')) 52 - ->setDisabled($file === null || !$this->isCorrectlyConfigured()) 53 - ->setHref($this->isCorrectlyConfigured() ? $file_uri : null) 54 - ->setIcon('fa-download')); 55 - 56 - $properties = id(new PHUIPropertyListView()) 57 - ->setUser($viewer) 58 - ->setObject($version) 59 - ->setActionList($actions); 60 - $properties->addProperty( 61 - pht('File'), 62 - $viewer->renderHandle($version->getFilePHID())); 63 - 64 - $box = id(new PHUIObjectBoxView()) 65 - ->setHeader($header) 66 - ->addPropertyList($properties); 67 - 68 - $title = pht('View Version'); 69 - 70 - $view = array( 71 - $this->renderConfigurationWarningIfRequired(), 72 - $box, 73 - $this->renderPreviousVersionList($version), 74 - ); 75 - 76 - return $this->newPage() 77 - ->setTitle($title) 78 - ->setCrumbs($crumbs) 79 - ->appendChild($view); 80 - } 81 - 82 - private function renderPreviousVersionList( 83 - PhragmentFragmentVersion $version) { 84 - $viewer = $this->getViewer(); 85 - 86 - $previous_versions = id(new PhragmentFragmentVersionQuery()) 87 - ->setViewer($viewer) 88 - ->withFragmentPHIDs(array($version->getFragmentPHID())) 89 - ->withSequenceBefore($version->getSequence()) 90 - ->execute(); 91 - 92 - $list = id(new PHUIObjectItemListView()) 93 - ->setUser($viewer); 94 - 95 - foreach ($previous_versions as $previous_version) { 96 - $item = id(new PHUIObjectItemView()); 97 - $item->setHeader(pht('Version %s', $previous_version->getSequence())); 98 - $item->setHref($previous_version->getURI()); 99 - $item->addAttribute(phabricator_datetime( 100 - $previous_version->getDateCreated(), 101 - $viewer)); 102 - $patch_uri = $this->getApplicationURI( 103 - 'patch/'.$previous_version->getID().'/'.$version->getID()); 104 - $item->addAction(id(new PHUIListItemView()) 105 - ->setIcon('fa-file-o') 106 - ->setName(pht('Get Patch')) 107 - ->setHref($this->isCorrectlyConfigured() ? $patch_uri : null) 108 - ->setDisabled(!$this->isCorrectlyConfigured())); 109 - $list->addItem($item); 110 - } 111 - 112 - $item = id(new PHUIObjectItemView()); 113 - $item->setHeader(pht('Prior to Version 0')); 114 - $item->addAttribute(pht('Prior to any content (empty file)')); 115 - $item->addAction(id(new PHUIListItemView()) 116 - ->setIcon('fa-file-o') 117 - ->setName(pht('Get Patch')) 118 - ->setHref($this->getApplicationURI( 119 - 'patch/x/'.$version->getID()))); 120 - $list->addItem($item); 121 - 122 - return $list; 123 - } 124 - 125 - }
-154
src/applications/phragment/controller/PhragmentZIPController.php
··· 1 - <?php 2 - 3 - final class PhragmentZIPController extends PhragmentController { 4 - 5 - private $snapshotCache; 6 - 7 - public function shouldAllowPublic() { 8 - return true; 9 - } 10 - 11 - public function handleRequest(AphrontRequest $request) { 12 - $viewer = $request->getViewer(); 13 - $dblob = $request->getURIData('dblob'); 14 - $snapshot = $request->getURIData('snapshot'); 15 - 16 - $parents = $this->loadParentFragments($dblob); 17 - if ($parents === null) { 18 - return new Aphront404Response(); 19 - } 20 - $fragment = idx($parents, count($parents) - 1, null); 21 - 22 - if ($snapshot !== null) { 23 - $snapshot = id(new PhragmentSnapshotQuery()) 24 - ->setViewer($viewer) 25 - ->withPrimaryFragmentPHIDs(array($fragment->getPHID())) 26 - ->withNames(array($snapshot)) 27 - ->executeOne(); 28 - if ($snapshot === null) { 29 - return new Aphront404Response(); 30 - } 31 - 32 - $cache = id(new PhragmentSnapshotChildQuery()) 33 - ->setViewer($viewer) 34 - ->needFragmentVersions(true) 35 - ->withSnapshotPHIDs(array($snapshot->getPHID())) 36 - ->execute(); 37 - $this->snapshotCache = mpull( 38 - $cache, 39 - 'getFragmentVersion', 40 - 'getFragmentPHID'); 41 - } 42 - 43 - $temp = new TempFile(); 44 - 45 - $zip = null; 46 - try { 47 - $zip = new ZipArchive(); 48 - } catch (Exception $e) { 49 - $dialog = new AphrontDialogView(); 50 - $dialog->setUser($viewer); 51 - 52 - $inst = pht( 53 - 'This system does not have the ZIP PHP extension installed. This '. 54 - 'is required to download ZIPs from Phragment.'); 55 - 56 - $dialog->setTitle(pht('ZIP Extension Not Installed')); 57 - $dialog->appendParagraph($inst); 58 - 59 - $dialog->addCancelButton('/phragment/browse/'.$dblob); 60 - return id(new AphrontDialogResponse())->setDialog($dialog); 61 - } 62 - 63 - if (!$zip->open((string)$temp, ZipArchive::CREATE)) { 64 - throw new Exception(pht('Unable to create ZIP archive!')); 65 - } 66 - 67 - $mappings = $this->getFragmentMappings( 68 - $fragment, $fragment->getPath(), $snapshot); 69 - 70 - $phids = array(); 71 - foreach ($mappings as $path => $file_phid) { 72 - $phids[] = $file_phid; 73 - } 74 - 75 - $files = id(new PhabricatorFileQuery()) 76 - ->setViewer($viewer) 77 - ->withPHIDs($phids) 78 - ->execute(); 79 - $files = mpull($files, null, 'getPHID'); 80 - foreach ($mappings as $path => $file_phid) { 81 - if (!isset($files[$file_phid])) { 82 - // The path is most likely pointing to a deleted fragment, which 83 - // hence no longer has a file associated with it. 84 - unset($mappings[$path]); 85 - continue; 86 - } 87 - $mappings[$path] = $files[$file_phid]; 88 - } 89 - 90 - foreach ($mappings as $path => $file) { 91 - if ($file !== null) { 92 - $zip->addFromString($path, $file->loadFileData()); 93 - } 94 - } 95 - $zip->close(); 96 - 97 - $zip_name = $fragment->getName(); 98 - if (substr($zip_name, -4) !== '.zip') { 99 - $zip_name .= '.zip'; 100 - } 101 - 102 - $data = Filesystem::readFile((string)$temp); 103 - 104 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 105 - $file = PhabricatorFile::newFromFileData( 106 - $data, 107 - array( 108 - 'name' => $zip_name, 109 - 'ttl.relative' => phutil_units('24 hours in seconds'), 110 - )); 111 - 112 - $file->attachToObject($fragment->getPHID()); 113 - unset($unguarded); 114 - 115 - $return = $fragment->getURI(); 116 - if ($request->getExists('return')) { 117 - $return = $request->getStr('return'); 118 - } 119 - 120 - return id(new AphrontRedirectResponse()) 121 - ->setIsExternal(true) 122 - ->setURI($file->getDownloadURI($return)); 123 - } 124 - 125 - /** 126 - * Returns a list of mappings like array('some/path.txt' => 'file PHID'); 127 - */ 128 - private function getFragmentMappings( 129 - PhragmentFragment $current, 130 - $base_path, 131 - $snapshot) { 132 - $mappings = $current->getFragmentMappings( 133 - $this->getRequest()->getUser(), 134 - $base_path); 135 - 136 - $result = array(); 137 - foreach ($mappings as $path => $fragment) { 138 - $version = $this->getVersion($fragment, $snapshot); 139 - if ($version !== null) { 140 - $result[$path] = $version->getFilePHID(); 141 - } 142 - } 143 - return $result; 144 - } 145 - 146 - private function getVersion($fragment, $snapshot) { 147 - if ($snapshot === null) { 148 - return $fragment->getLatestVersion(); 149 - } else { 150 - return idx($this->snapshotCache, $fragment->getPHID(), null); 151 - } 152 - } 153 - 154 - }
-44
src/applications/phragment/phid/PhragmentFragmentPHIDType.php
··· 1 - <?php 2 - 3 - final class PhragmentFragmentPHIDType extends PhabricatorPHIDType { 4 - 5 - const TYPECONST = 'PHRF'; 6 - 7 - public function getTypeName() { 8 - return pht('Fragment'); 9 - } 10 - 11 - public function newObject() { 12 - return new PhragmentFragment(); 13 - } 14 - 15 - public function getPHIDTypeApplicationClass() { 16 - return 'PhabricatorPhragmentApplication'; 17 - } 18 - 19 - protected function buildQueryForObjects( 20 - PhabricatorObjectQuery $query, 21 - array $phids) { 22 - 23 - return id(new PhragmentFragmentQuery()) 24 - ->withPHIDs($phids); 25 - } 26 - 27 - public function loadHandles( 28 - PhabricatorHandleQuery $query, 29 - array $handles, 30 - array $objects) { 31 - 32 - $viewer = $query->getViewer(); 33 - foreach ($handles as $phid => $handle) { 34 - $fragment = $objects[$phid]; 35 - 36 - $handle->setName(pht( 37 - 'Fragment %s: %s', 38 - $fragment->getID(), 39 - $fragment->getName())); 40 - $handle->setURI($fragment->getURI()); 41 - } 42 - } 43 - 44 - }
-44
src/applications/phragment/phid/PhragmentFragmentVersionPHIDType.php
··· 1 - <?php 2 - 3 - final class PhragmentFragmentVersionPHIDType extends PhabricatorPHIDType { 4 - 5 - const TYPECONST = 'PHRV'; 6 - 7 - public function getTypeName() { 8 - return pht('Fragment Version'); 9 - } 10 - 11 - public function newObject() { 12 - return new PhragmentFragmentVersion(); 13 - } 14 - 15 - public function getPHIDTypeApplicationClass() { 16 - return 'PhabricatorPhragmentApplication'; 17 - } 18 - 19 - protected function buildQueryForObjects( 20 - PhabricatorObjectQuery $query, 21 - array $phids) { 22 - 23 - return id(new PhragmentFragmentVersionQuery()) 24 - ->withPHIDs($phids); 25 - } 26 - 27 - public function loadHandles( 28 - PhabricatorHandleQuery $query, 29 - array $handles, 30 - array $objects) { 31 - 32 - $viewer = $query->getViewer(); 33 - foreach ($handles as $phid => $handle) { 34 - $version = $objects[$phid]; 35 - 36 - $handle->setName(pht( 37 - 'Fragment Version %d: %s', 38 - $version->getSequence(), 39 - $version->getFragment()->getName())); 40 - $handle->setURI($version->getURI()); 41 - } 42 - } 43 - 44 - }
-43
src/applications/phragment/phid/PhragmentSnapshotPHIDType.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotPHIDType extends PhabricatorPHIDType { 4 - 5 - const TYPECONST = 'PHRS'; 6 - 7 - public function getTypeName() { 8 - return pht('Snapshot'); 9 - } 10 - 11 - public function newObject() { 12 - return new PhragmentSnapshot(); 13 - } 14 - 15 - public function getPHIDTypeApplicationClass() { 16 - return 'PhabricatorPhragmentApplication'; 17 - } 18 - 19 - protected function buildQueryForObjects( 20 - PhabricatorObjectQuery $query, 21 - array $phids) { 22 - 23 - return id(new PhragmentSnapshotQuery()) 24 - ->withPHIDs($phids); 25 - } 26 - 27 - public function loadHandles( 28 - PhabricatorHandleQuery $query, 29 - array $handles, 30 - array $objects) { 31 - 32 - $viewer = $query->getViewer(); 33 - foreach ($handles as $phid => $handle) { 34 - $snapshot = $objects[$phid]; 35 - 36 - $handle->setName(pht( 37 - 'Snapshot: %s', 38 - $snapshot->getName())); 39 - $handle->setURI($snapshot->getURI()); 40 - } 41 - } 42 - 43 - }
-130
src/applications/phragment/query/PhragmentFragmentQuery.php
··· 1 - <?php 2 - 3 - final class PhragmentFragmentQuery 4 - extends PhabricatorCursorPagedPolicyAwareQuery { 5 - 6 - private $ids; 7 - private $phids; 8 - private $paths; 9 - private $leadingPath; 10 - private $depths; 11 - private $needLatestVersion; 12 - 13 - public function withIDs(array $ids) { 14 - $this->ids = $ids; 15 - return $this; 16 - } 17 - 18 - public function withPHIDs(array $phids) { 19 - $this->phids = $phids; 20 - return $this; 21 - } 22 - 23 - public function withPaths(array $paths) { 24 - $this->paths = $paths; 25 - return $this; 26 - } 27 - 28 - public function withLeadingPath($path) { 29 - $this->leadingPath = $path; 30 - return $this; 31 - } 32 - 33 - public function withDepths($depths) { 34 - $this->depths = $depths; 35 - return $this; 36 - } 37 - 38 - public function needLatestVersion($need_latest_version) { 39 - $this->needLatestVersion = $need_latest_version; 40 - return $this; 41 - } 42 - 43 - protected function loadPage() { 44 - $table = new PhragmentFragment(); 45 - $conn_r = $table->establishConnection('r'); 46 - 47 - $data = queryfx_all( 48 - $conn_r, 49 - 'SELECT * FROM %T %Q %Q %Q', 50 - $table->getTableName(), 51 - $this->buildWhereClause($conn_r), 52 - $this->buildOrderClause($conn_r), 53 - $this->buildLimitClause($conn_r)); 54 - 55 - return $table->loadAllFromArray($data); 56 - } 57 - 58 - protected function buildWhereClause(AphrontDatabaseConnection $conn) { 59 - $where = array(); 60 - 61 - if ($this->ids) { 62 - $where[] = qsprintf( 63 - $conn, 64 - 'id IN (%Ld)', 65 - $this->ids); 66 - } 67 - 68 - if ($this->phids) { 69 - $where[] = qsprintf( 70 - $conn, 71 - 'phid IN (%Ls)', 72 - $this->phids); 73 - } 74 - 75 - if ($this->paths) { 76 - $where[] = qsprintf( 77 - $conn, 78 - 'path IN (%Ls)', 79 - $this->paths); 80 - } 81 - 82 - if ($this->leadingPath) { 83 - $where[] = qsprintf( 84 - $conn, 85 - 'path LIKE %>', 86 - $this->leadingPath); 87 - } 88 - 89 - if ($this->depths) { 90 - $where[] = qsprintf( 91 - $conn, 92 - 'depth IN (%Ld)', 93 - $this->depths); 94 - } 95 - 96 - $where[] = $this->buildPagingClause($conn); 97 - 98 - return $this->formatWhereClause($conn, $where); 99 - } 100 - 101 - protected function didFilterPage(array $page) { 102 - if ($this->needLatestVersion) { 103 - $versions = array(); 104 - 105 - $version_phids = array_filter(mpull($page, 'getLatestVersionPHID')); 106 - if ($version_phids) { 107 - $versions = id(new PhabricatorObjectQuery()) 108 - ->setViewer($this->getViewer()) 109 - ->withPHIDs($version_phids) 110 - ->setParentQuery($this) 111 - ->execute(); 112 - $versions = mpull($versions, null, 'getPHID'); 113 - } 114 - 115 - foreach ($page as $key => $fragment) { 116 - $version_phid = $fragment->getLatestVersionPHID(); 117 - if (empty($versions[$version_phid])) { 118 - continue; 119 - } 120 - $fragment->attachLatestVersion($versions[$version_phid]); 121 - } 122 - } 123 - 124 - return $page; 125 - } 126 - 127 - public function getQueryApplicationClass() { 128 - return 'PhabricatorPhragmentApplication'; 129 - } 130 - }
-123
src/applications/phragment/query/PhragmentFragmentVersionQuery.php
··· 1 - <?php 2 - 3 - final class PhragmentFragmentVersionQuery 4 - extends PhabricatorCursorPagedPolicyAwareQuery { 5 - 6 - private $ids; 7 - private $phids; 8 - private $fragmentPHIDs; 9 - private $sequences; 10 - private $sequenceBefore; 11 - 12 - public function withIDs(array $ids) { 13 - $this->ids = $ids; 14 - return $this; 15 - } 16 - 17 - public function withPHIDs(array $phids) { 18 - $this->phids = $phids; 19 - return $this; 20 - } 21 - 22 - public function withFragmentPHIDs(array $fragment_phids) { 23 - $this->fragmentPHIDs = $fragment_phids; 24 - return $this; 25 - } 26 - 27 - public function withSequences(array $sequences) { 28 - $this->sequences = $sequences; 29 - return $this; 30 - } 31 - 32 - public function withSequenceBefore($current) { 33 - $this->sequenceBefore = $current; 34 - return $this; 35 - } 36 - 37 - protected function loadPage() { 38 - $table = new PhragmentFragmentVersion(); 39 - $conn_r = $table->establishConnection('r'); 40 - 41 - $data = queryfx_all( 42 - $conn_r, 43 - 'SELECT * FROM %T %Q %Q %Q', 44 - $table->getTableName(), 45 - $this->buildWhereClause($conn_r), 46 - $this->buildOrderClause($conn_r), 47 - $this->buildLimitClause($conn_r)); 48 - 49 - return $table->loadAllFromArray($data); 50 - } 51 - 52 - protected function buildWhereClause(AphrontDatabaseConnection $conn) { 53 - $where = array(); 54 - 55 - if ($this->ids) { 56 - $where[] = qsprintf( 57 - $conn, 58 - 'id IN (%Ld)', 59 - $this->ids); 60 - } 61 - 62 - if ($this->phids) { 63 - $where[] = qsprintf( 64 - $conn, 65 - 'phid IN (%Ls)', 66 - $this->phids); 67 - } 68 - 69 - if ($this->fragmentPHIDs) { 70 - $where[] = qsprintf( 71 - $conn, 72 - 'fragmentPHID IN (%Ls)', 73 - $this->fragmentPHIDs); 74 - } 75 - 76 - if ($this->sequences) { 77 - $where[] = qsprintf( 78 - $conn, 79 - 'sequence IN (%Ld)', 80 - $this->sequences); 81 - } 82 - 83 - if ($this->sequenceBefore !== null) { 84 - $where[] = qsprintf( 85 - $conn, 86 - 'sequence < %d', 87 - $this->sequenceBefore); 88 - } 89 - 90 - $where[] = $this->buildPagingClause($conn); 91 - 92 - return $this->formatWhereClause($conn, $where); 93 - } 94 - 95 - protected function willFilterPage(array $page) { 96 - $fragments = array(); 97 - 98 - $fragment_phids = array_filter(mpull($page, 'getFragmentPHID')); 99 - if ($fragment_phids) { 100 - $fragments = id(new PhabricatorObjectQuery()) 101 - ->setViewer($this->getViewer()) 102 - ->withPHIDs($fragment_phids) 103 - ->setParentQuery($this) 104 - ->execute(); 105 - $fragments = mpull($fragments, null, 'getPHID'); 106 - } 107 - 108 - foreach ($page as $key => $version) { 109 - $fragment_phid = $version->getFragmentPHID(); 110 - if (empty($fragments[$fragment_phid])) { 111 - unset($page[$key]); 112 - continue; 113 - } 114 - $version->attachFragment($fragments[$fragment_phid]); 115 - } 116 - 117 - return $page; 118 - } 119 - 120 - public function getQueryApplicationClass() { 121 - return 'PhabricatorPhragmentApplication'; 122 - } 123 - }
-174
src/applications/phragment/query/PhragmentSnapshotChildQuery.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotChildQuery 4 - extends PhabricatorCursorPagedPolicyAwareQuery { 5 - 6 - private $ids; 7 - private $snapshotPHIDs; 8 - private $fragmentPHIDs; 9 - private $fragmentVersionPHIDs; 10 - private $needFragments; 11 - private $needFragmentVersions; 12 - 13 - public function withIDs(array $ids) { 14 - $this->ids = $ids; 15 - return $this; 16 - } 17 - 18 - public function withSnapshotPHIDs(array $snapshot_phids) { 19 - $this->snapshotPHIDs = $snapshot_phids; 20 - return $this; 21 - } 22 - 23 - public function withFragmentPHIDs(array $fragment_phids) { 24 - $this->fragmentPHIDs = $fragment_phids; 25 - return $this; 26 - } 27 - 28 - public function withFragmentVersionPHIDs(array $fragment_version_phids) { 29 - $this->fragmentVersionPHIDs = $fragment_version_phids; 30 - return $this; 31 - } 32 - 33 - public function needFragments($need_fragments) { 34 - $this->needFragments = $need_fragments; 35 - return $this; 36 - } 37 - 38 - public function needFragmentVersions($need_fragment_versions) { 39 - $this->needFragmentVersions = $need_fragment_versions; 40 - return $this; 41 - } 42 - 43 - protected function loadPage() { 44 - $table = new PhragmentSnapshotChild(); 45 - $conn_r = $table->establishConnection('r'); 46 - 47 - $data = queryfx_all( 48 - $conn_r, 49 - 'SELECT * FROM %T %Q %Q %Q', 50 - $table->getTableName(), 51 - $this->buildWhereClause($conn_r), 52 - $this->buildOrderClause($conn_r), 53 - $this->buildLimitClause($conn_r)); 54 - 55 - return $table->loadAllFromArray($data); 56 - } 57 - 58 - protected function buildWhereClause(AphrontDatabaseConnection $conn) { 59 - $where = array(); 60 - 61 - if ($this->ids) { 62 - $where[] = qsprintf( 63 - $conn, 64 - 'id IN (%Ld)', 65 - $this->ids); 66 - } 67 - 68 - if ($this->snapshotPHIDs) { 69 - $where[] = qsprintf( 70 - $conn, 71 - 'snapshotPHID IN (%Ls)', 72 - $this->snapshotPHIDs); 73 - } 74 - 75 - if ($this->fragmentPHIDs) { 76 - $where[] = qsprintf( 77 - $conn, 78 - 'fragmentPHID IN (%Ls)', 79 - $this->fragmentPHIDs); 80 - } 81 - 82 - if ($this->fragmentVersionPHIDs) { 83 - $where[] = qsprintf( 84 - $conn, 85 - 'fragmentVersionPHID IN (%Ls)', 86 - $this->fragmentVersionPHIDs); 87 - } 88 - 89 - $where[] = $this->buildPagingClause($conn); 90 - 91 - return $this->formatWhereClause($conn, $where); 92 - } 93 - 94 - protected function willFilterPage(array $page) { 95 - $snapshots = array(); 96 - 97 - $snapshot_phids = array_filter(mpull($page, 'getSnapshotPHID')); 98 - if ($snapshot_phids) { 99 - $snapshots = id(new PhabricatorObjectQuery()) 100 - ->setViewer($this->getViewer()) 101 - ->withPHIDs($snapshot_phids) 102 - ->setParentQuery($this) 103 - ->execute(); 104 - $snapshots = mpull($snapshots, null, 'getPHID'); 105 - } 106 - 107 - foreach ($page as $key => $child) { 108 - $snapshot_phid = $child->getSnapshotPHID(); 109 - if (empty($snapshots[$snapshot_phid])) { 110 - unset($page[$key]); 111 - continue; 112 - } 113 - $child->attachSnapshot($snapshots[$snapshot_phid]); 114 - } 115 - 116 - return $page; 117 - } 118 - 119 - protected function didFilterPage(array $page) { 120 - if ($this->needFragments) { 121 - $fragments = array(); 122 - 123 - $fragment_phids = array_filter(mpull($page, 'getFragmentPHID')); 124 - if ($fragment_phids) { 125 - $fragments = id(new PhabricatorObjectQuery()) 126 - ->setViewer($this->getViewer()) 127 - ->withPHIDs($fragment_phids) 128 - ->setParentQuery($this) 129 - ->execute(); 130 - $fragments = mpull($fragments, null, 'getPHID'); 131 - } 132 - 133 - foreach ($page as $key => $child) { 134 - $fragment_phid = $child->getFragmentPHID(); 135 - if (empty($fragments[$fragment_phid])) { 136 - unset($page[$key]); 137 - continue; 138 - } 139 - $child->attachFragment($fragments[$fragment_phid]); 140 - } 141 - } 142 - 143 - if ($this->needFragmentVersions) { 144 - $fragment_versions = array(); 145 - 146 - $fragment_version_phids = array_filter(mpull( 147 - $page, 148 - 'getFragmentVersionPHID')); 149 - if ($fragment_version_phids) { 150 - $fragment_versions = id(new PhabricatorObjectQuery()) 151 - ->setViewer($this->getViewer()) 152 - ->withPHIDs($fragment_version_phids) 153 - ->setParentQuery($this) 154 - ->execute(); 155 - $fragment_versions = mpull($fragment_versions, null, 'getPHID'); 156 - } 157 - 158 - foreach ($page as $key => $child) { 159 - $fragment_version_phid = $child->getFragmentVersionPHID(); 160 - if (empty($fragment_versions[$fragment_version_phid])) { 161 - continue; 162 - } 163 - $child->attachFragmentVersion( 164 - $fragment_versions[$fragment_version_phid]); 165 - } 166 - } 167 - 168 - return $page; 169 - } 170 - 171 - public function getQueryApplicationClass() { 172 - return 'PhabricatorPhragmentApplication'; 173 - } 174 - }
-111
src/applications/phragment/query/PhragmentSnapshotQuery.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotQuery 4 - extends PhabricatorCursorPagedPolicyAwareQuery { 5 - 6 - private $ids; 7 - private $phids; 8 - private $primaryFragmentPHIDs; 9 - private $names; 10 - 11 - public function withIDs(array $ids) { 12 - $this->ids = $ids; 13 - return $this; 14 - } 15 - 16 - public function withPHIDs(array $phids) { 17 - $this->phids = $phids; 18 - return $this; 19 - } 20 - 21 - public function withPrimaryFragmentPHIDs(array $primary_fragment_phids) { 22 - $this->primaryFragmentPHIDs = $primary_fragment_phids; 23 - return $this; 24 - } 25 - 26 - public function withNames(array $names) { 27 - $this->names = $names; 28 - return $this; 29 - } 30 - 31 - protected function loadPage() { 32 - $table = new PhragmentSnapshot(); 33 - $conn_r = $table->establishConnection('r'); 34 - 35 - $data = queryfx_all( 36 - $conn_r, 37 - 'SELECT * FROM %T %Q %Q %Q', 38 - $table->getTableName(), 39 - $this->buildWhereClause($conn_r), 40 - $this->buildOrderClause($conn_r), 41 - $this->buildLimitClause($conn_r)); 42 - 43 - return $table->loadAllFromArray($data); 44 - } 45 - 46 - protected function buildWhereClause(AphrontDatabaseConnection $conn) { 47 - $where = array(); 48 - 49 - if ($this->ids !== null) { 50 - $where[] = qsprintf( 51 - $conn, 52 - 'id IN (%Ld)', 53 - $this->ids); 54 - } 55 - 56 - if ($this->phids !== null) { 57 - $where[] = qsprintf( 58 - $conn, 59 - 'phid IN (%Ls)', 60 - $this->phids); 61 - } 62 - 63 - if ($this->primaryFragmentPHIDs !== null) { 64 - $where[] = qsprintf( 65 - $conn, 66 - 'primaryFragmentPHID IN (%Ls)', 67 - $this->primaryFragmentPHIDs); 68 - } 69 - 70 - if ($this->names !== null) { 71 - $where[] = qsprintf( 72 - $conn, 73 - 'name IN (%Ls)', 74 - $this->names); 75 - } 76 - 77 - $where[] = $this->buildPagingClause($conn); 78 - 79 - return $this->formatWhereClause($conn, $where); 80 - } 81 - 82 - protected function willFilterPage(array $page) { 83 - $fragments = array(); 84 - 85 - $fragment_phids = array_filter(mpull($page, 'getPrimaryFragmentPHID')); 86 - if ($fragment_phids) { 87 - $fragments = id(new PhabricatorObjectQuery()) 88 - ->setViewer($this->getViewer()) 89 - ->withPHIDs($fragment_phids) 90 - ->setParentQuery($this) 91 - ->execute(); 92 - $fragments = mpull($fragments, null, 'getPHID'); 93 - } 94 - 95 - foreach ($page as $key => $snapshot) { 96 - $fragment_phid = $snapshot->getPrimaryFragmentPHID(); 97 - if (empty($fragments[$fragment_phid])) { 98 - unset($page[$key]); 99 - continue; 100 - } 101 - $snapshot->attachPrimaryFragment($fragments[$fragment_phid]); 102 - } 103 - 104 - return $page; 105 - } 106 - 107 - public function getQueryApplicationClass() { 108 - return 'PhabricatorPhragmentApplication'; 109 - } 110 - 111 - }
-9
src/applications/phragment/storage/PhragmentDAO.php
··· 1 - <?php 2 - 3 - abstract class PhragmentDAO extends PhabricatorLiskDAO { 4 - 5 - public function getApplicationName() { 6 - return 'phragment'; 7 - } 8 - 9 - }
-349
src/applications/phragment/storage/PhragmentFragment.php
··· 1 - <?php 2 - 3 - final class PhragmentFragment extends PhragmentDAO 4 - implements PhabricatorPolicyInterface { 5 - 6 - protected $path; 7 - protected $depth; 8 - protected $latestVersionPHID; 9 - protected $viewPolicy; 10 - protected $editPolicy; 11 - 12 - private $latestVersion = self::ATTACHABLE; 13 - private $file = self::ATTACHABLE; 14 - 15 - protected function getConfiguration() { 16 - return array( 17 - self::CONFIG_AUX_PHID => true, 18 - self::CONFIG_COLUMN_SCHEMA => array( 19 - 'path' => 'text128', 20 - 'depth' => 'uint32', 21 - 'latestVersionPHID' => 'phid?', 22 - ), 23 - self::CONFIG_KEY_SCHEMA => array( 24 - 'key_path' => array( 25 - 'columns' => array('path'), 26 - 'unique' => true, 27 - ), 28 - ), 29 - ) + parent::getConfiguration(); 30 - } 31 - 32 - public function generatePHID() { 33 - return PhabricatorPHID::generateNewPHID( 34 - PhragmentFragmentPHIDType::TYPECONST); 35 - } 36 - 37 - public function getURI() { 38 - return '/phragment/browse/'.$this->getPath(); 39 - } 40 - 41 - public function getName() { 42 - return basename($this->path); 43 - } 44 - 45 - public function getFile() { 46 - return $this->assertAttached($this->file); 47 - } 48 - 49 - public function attachFile(PhabricatorFile $file) { 50 - return $this->file = $file; 51 - } 52 - 53 - public function isDirectory() { 54 - return $this->latestVersionPHID === null; 55 - } 56 - 57 - public function isDeleted() { 58 - return $this->getLatestVersion()->getFilePHID() === null; 59 - } 60 - 61 - public function getLatestVersion() { 62 - if ($this->latestVersionPHID === null) { 63 - return null; 64 - } 65 - return $this->assertAttached($this->latestVersion); 66 - } 67 - 68 - public function attachLatestVersion(PhragmentFragmentVersion $version) { 69 - return $this->latestVersion = $version; 70 - } 71 - 72 - 73 - /* -( Updating ) --------------------------------------------------------- */ 74 - 75 - 76 - /** 77 - * Create a new fragment from a file. 78 - */ 79 - public static function createFromFile( 80 - PhabricatorUser $viewer, 81 - PhabricatorFile $file = null, 82 - $path = null, 83 - $view_policy = null, 84 - $edit_policy = null) { 85 - 86 - $fragment = id(new PhragmentFragment()); 87 - $fragment->setPath($path); 88 - $fragment->setDepth(count(explode('/', $path))); 89 - $fragment->setLatestVersionPHID(null); 90 - $fragment->setViewPolicy($view_policy); 91 - $fragment->setEditPolicy($edit_policy); 92 - $fragment->save(); 93 - 94 - // Directory fragments have no versions associated with them, so we 95 - // just return the fragment at this point. 96 - if ($file === null) { 97 - return $fragment; 98 - } 99 - 100 - if ($file->getMimeType() === 'application/zip') { 101 - $fragment->updateFromZIP($viewer, $file); 102 - } else { 103 - $fragment->updateFromFile($viewer, $file); 104 - } 105 - 106 - return $fragment; 107 - } 108 - 109 - 110 - /** 111 - * Set the specified file as the next version for the fragment. 112 - */ 113 - public function updateFromFile( 114 - PhabricatorUser $viewer, 115 - PhabricatorFile $file) { 116 - 117 - $existing = id(new PhragmentFragmentVersionQuery()) 118 - ->setViewer($viewer) 119 - ->withFragmentPHIDs(array($this->getPHID())) 120 - ->execute(); 121 - $sequence = count($existing); 122 - 123 - $this->openTransaction(); 124 - $version = id(new PhragmentFragmentVersion()); 125 - $version->setSequence($sequence); 126 - $version->setFragmentPHID($this->getPHID()); 127 - $version->setFilePHID($file->getPHID()); 128 - $version->save(); 129 - 130 - $this->setLatestVersionPHID($version->getPHID()); 131 - $this->save(); 132 - $this->saveTransaction(); 133 - 134 - $file->attachToObject($version->getPHID()); 135 - } 136 - 137 - /** 138 - * Apply the specified ZIP archive onto the fragment, removing 139 - * and creating fragments as needed. 140 - */ 141 - public function updateFromZIP( 142 - PhabricatorUser $viewer, 143 - PhabricatorFile $file) { 144 - 145 - if ($file->getMimeType() !== 'application/zip') { 146 - throw new Exception( 147 - pht("File must have mimetype '%s'.", 'application/zip')); 148 - } 149 - 150 - // First apply the ZIP as normal. 151 - $this->updateFromFile($viewer, $file); 152 - 153 - // Ensure we have ZIP support. 154 - $zip = null; 155 - try { 156 - $zip = new ZipArchive(); 157 - } catch (Exception $e) { 158 - // The server doesn't have php5-zip, so we can't do recursive updates. 159 - return; 160 - } 161 - 162 - $temp = new TempFile(); 163 - Filesystem::writeFile($temp, $file->loadFileData()); 164 - if (!$zip->open($temp)) { 165 - throw new Exception(pht('Unable to open ZIP.')); 166 - } 167 - 168 - // Get all of the paths and their data from the ZIP. 169 - $mappings = array(); 170 - for ($i = 0; $i < $zip->numFiles; $i++) { 171 - $path = trim($zip->getNameIndex($i), '/'); 172 - $stream = $zip->getStream($path); 173 - $data = null; 174 - // If the stream is false, then it is a directory entry. We leave 175 - // $data set to null for directories so we know not to create a 176 - // version entry for them. 177 - if ($stream !== false) { 178 - $data = stream_get_contents($stream); 179 - fclose($stream); 180 - } 181 - $mappings[$path] = $data; 182 - } 183 - 184 - // We need to detect any directories that are in the ZIP folder that 185 - // aren't explicitly noted in the ZIP. This can happen if the file 186 - // entries in the ZIP look like: 187 - // 188 - // * something/blah.png 189 - // * something/other.png 190 - // * test.png 191 - // 192 - // Where there is no explicit "something/" entry. 193 - foreach ($mappings as $path_key => $data) { 194 - if ($data === null) { 195 - continue; 196 - } 197 - $directory = dirname($path_key); 198 - while ($directory !== '.') { 199 - if (!array_key_exists($directory, $mappings)) { 200 - $mappings[$directory] = null; 201 - } 202 - if (dirname($directory) === $directory) { 203 - // dirname() will not reduce this directory any further; to 204 - // prevent infinite loop we just break out here. 205 - break; 206 - } 207 - $directory = dirname($directory); 208 - } 209 - } 210 - 211 - // Adjust the paths relative to this fragment so we can look existing 212 - // fragments up in the DB. 213 - $base_path = $this->getPath(); 214 - $paths = array(); 215 - foreach ($mappings as $p => $data) { 216 - $paths[] = $base_path.'/'.$p; 217 - } 218 - 219 - // FIXME: What happens when a child exists, but the current user 220 - // can't see it. We're going to create a new child with the exact 221 - // same path and then bad things will happen. 222 - $children = id(new PhragmentFragmentQuery()) 223 - ->setViewer($viewer) 224 - ->needLatestVersion(true) 225 - ->withLeadingPath($this->getPath().'/') 226 - ->execute(); 227 - $children = mpull($children, null, 'getPath'); 228 - 229 - // Iterate over the existing fragments. 230 - foreach ($children as $full_path => $child) { 231 - $path = substr($full_path, strlen($base_path) + 1); 232 - if (array_key_exists($path, $mappings)) { 233 - if ($child->isDirectory() && $mappings[$path] === null) { 234 - // Don't create a version entry for a directory 235 - // (unless it's been converted into a file). 236 - continue; 237 - } 238 - 239 - // The file is being updated. 240 - $file = PhabricatorFile::newFromFileData( 241 - $mappings[$path], 242 - array('name' => basename($path))); 243 - $child->updateFromFile($viewer, $file); 244 - } else { 245 - // The file is being deleted. 246 - $child->deleteFile($viewer); 247 - } 248 - } 249 - 250 - // Iterate over the mappings to find new files. 251 - foreach ($mappings as $path => $data) { 252 - if (!array_key_exists($base_path.'/'.$path, $children)) { 253 - // The file is being created. If the data is null, 254 - // then this is explicitly a directory being created. 255 - $file = null; 256 - if ($mappings[$path] !== null) { 257 - $file = PhabricatorFile::newFromFileData( 258 - $mappings[$path], 259 - array('name' => basename($path))); 260 - } 261 - self::createFromFile( 262 - $viewer, 263 - $file, 264 - $base_path.'/'.$path, 265 - $this->getViewPolicy(), 266 - $this->getEditPolicy()); 267 - } 268 - } 269 - } 270 - 271 - /** 272 - * Delete the contents of the specified fragment. 273 - */ 274 - public function deleteFile(PhabricatorUser $viewer) { 275 - $existing = id(new PhragmentFragmentVersionQuery()) 276 - ->setViewer($viewer) 277 - ->withFragmentPHIDs(array($this->getPHID())) 278 - ->execute(); 279 - $sequence = count($existing); 280 - 281 - $this->openTransaction(); 282 - $version = id(new PhragmentFragmentVersion()); 283 - $version->setSequence($sequence); 284 - $version->setFragmentPHID($this->getPHID()); 285 - $version->setFilePHID(null); 286 - $version->save(); 287 - 288 - $this->setLatestVersionPHID($version->getPHID()); 289 - $this->save(); 290 - $this->saveTransaction(); 291 - } 292 - 293 - 294 - /* -( Utility ) ---------------------------------------------------------- */ 295 - 296 - 297 - public function getFragmentMappings( 298 - PhabricatorUser $viewer, 299 - $base_path) { 300 - 301 - $children = id(new PhragmentFragmentQuery()) 302 - ->setViewer($viewer) 303 - ->needLatestVersion(true) 304 - ->withLeadingPath($this->getPath().'/') 305 - ->withDepths(array($this->getDepth() + 1)) 306 - ->execute(); 307 - 308 - if (count($children) === 0) { 309 - $path = substr($this->getPath(), strlen($base_path) + 1); 310 - return array($path => $this); 311 - } else { 312 - $mappings = array(); 313 - foreach ($children as $child) { 314 - $child_mappings = $child->getFragmentMappings( 315 - $viewer, 316 - $base_path); 317 - foreach ($child_mappings as $key => $value) { 318 - $mappings[$key] = $value; 319 - } 320 - } 321 - return $mappings; 322 - } 323 - } 324 - 325 - 326 - /* -( Policy Interface )--------------------------------------------------- */ 327 - 328 - 329 - public function getCapabilities() { 330 - return array( 331 - PhabricatorPolicyCapability::CAN_VIEW, 332 - PhabricatorPolicyCapability::CAN_EDIT, 333 - ); 334 - } 335 - 336 - public function getPolicy($capability) { 337 - switch ($capability) { 338 - case PhabricatorPolicyCapability::CAN_VIEW: 339 - return $this->getViewPolicy(); 340 - case PhabricatorPolicyCapability::CAN_EDIT: 341 - return $this->getEditPolicy(); 342 - } 343 - } 344 - 345 - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 346 - return false; 347 - } 348 - 349 - }
-72
src/applications/phragment/storage/PhragmentFragmentVersion.php
··· 1 - <?php 2 - 3 - final class PhragmentFragmentVersion extends PhragmentDAO 4 - implements PhabricatorPolicyInterface { 5 - 6 - protected $sequence; 7 - protected $fragmentPHID; 8 - protected $filePHID; 9 - 10 - private $fragment = self::ATTACHABLE; 11 - private $file = self::ATTACHABLE; 12 - 13 - protected function getConfiguration() { 14 - return array( 15 - self::CONFIG_AUX_PHID => true, 16 - self::CONFIG_COLUMN_SCHEMA => array( 17 - 'sequence' => 'uint32', 18 - 'filePHID' => 'phid?', 19 - ), 20 - self::CONFIG_KEY_SCHEMA => array( 21 - 'key_version' => array( 22 - 'columns' => array('fragmentPHID', 'sequence'), 23 - 'unique' => true, 24 - ), 25 - ), 26 - ) + parent::getConfiguration(); 27 - } 28 - 29 - public function generatePHID() { 30 - return PhabricatorPHID::generateNewPHID( 31 - PhragmentFragmentVersionPHIDType::TYPECONST); 32 - } 33 - 34 - public function getURI() { 35 - return '/phragment/version/'.$this->getID().'/'; 36 - } 37 - 38 - public function getFragment() { 39 - return $this->assertAttached($this->fragment); 40 - } 41 - 42 - public function attachFragment(PhragmentFragment $fragment) { 43 - return $this->fragment = $fragment; 44 - } 45 - 46 - public function getFile() { 47 - return $this->assertAttached($this->file); 48 - } 49 - 50 - public function attachFile(PhabricatorFile $file) { 51 - return $this->file = $file; 52 - } 53 - 54 - public function getCapabilities() { 55 - return array( 56 - PhabricatorPolicyCapability::CAN_VIEW, 57 - ); 58 - } 59 - 60 - public function getPolicy($capability) { 61 - return $this->getFragment()->getPolicy($capability); 62 - } 63 - 64 - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 65 - return $this->getFragment()->hasAutomaticCapability($capability, $viewer); 66 - } 67 - 68 - public function describeAutomaticCapability($capability) { 69 - return $this->getFragment()->describeAutomaticCapability($capability); 70 - } 71 - 72 - }
-9
src/applications/phragment/storage/PhragmentSchemaSpec.php
··· 1 - <?php 2 - 3 - final class PhragmentSchemaSpec extends PhabricatorConfigSchemaSpec { 4 - 5 - public function buildSchemata() { 6 - $this->buildEdgeSchemata(new PhragmentFragment()); 7 - } 8 - 9 - }
-76
src/applications/phragment/storage/PhragmentSnapshot.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshot extends PhragmentDAO 4 - implements PhabricatorPolicyInterface { 5 - 6 - protected $primaryFragmentPHID; 7 - protected $name; 8 - 9 - private $primaryFragment = self::ATTACHABLE; 10 - 11 - protected function getConfiguration() { 12 - return array( 13 - self::CONFIG_AUX_PHID => true, 14 - self::CONFIG_COLUMN_SCHEMA => array( 15 - 'name' => 'text128', 16 - ), 17 - self::CONFIG_KEY_SCHEMA => array( 18 - 'key_name' => array( 19 - 'columns' => array('primaryFragmentPHID', 'name'), 20 - 'unique' => true, 21 - ), 22 - ), 23 - ) + parent::getConfiguration(); 24 - } 25 - 26 - public function generatePHID() { 27 - return PhabricatorPHID::generateNewPHID( 28 - PhragmentSnapshotPHIDType::TYPECONST); 29 - } 30 - 31 - public function getURI() { 32 - return '/phragment/snapshot/view/'.$this->getID().'/'; 33 - } 34 - 35 - public function getPrimaryFragment() { 36 - return $this->assertAttached($this->primaryFragment); 37 - } 38 - 39 - public function attachPrimaryFragment(PhragmentFragment $fragment) { 40 - return $this->primaryFragment = $fragment; 41 - } 42 - 43 - public function delete() { 44 - $children = id(new PhragmentSnapshotChild()) 45 - ->loadAllWhere('snapshotPHID = %s', $this->getPHID()); 46 - $this->openTransaction(); 47 - foreach ($children as $child) { 48 - $child->delete(); 49 - } 50 - $result = parent::delete(); 51 - $this->saveTransaction(); 52 - return $result; 53 - } 54 - 55 - 56 - /* -( Policy Interface )--------------------------------------------------- */ 57 - 58 - 59 - public function getCapabilities() { 60 - return $this->getPrimaryFragment()->getCapabilities(); 61 - } 62 - 63 - public function getPolicy($capability) { 64 - return $this->getPrimaryFragment()->getPolicy($capability); 65 - } 66 - 67 - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 68 - return $this->getPrimaryFragment() 69 - ->hasAutomaticCapability($capability, $viewer); 70 - } 71 - 72 - public function describeAutomaticCapability($capability) { 73 - return $this->getPrimaryFragment() 74 - ->describeAutomaticCapability($capability); 75 - } 76 - }
-82
src/applications/phragment/storage/PhragmentSnapshotChild.php
··· 1 - <?php 2 - 3 - final class PhragmentSnapshotChild extends PhragmentDAO 4 - implements PhabricatorPolicyInterface { 5 - 6 - protected $snapshotPHID; 7 - protected $fragmentPHID; 8 - protected $fragmentVersionPHID; 9 - 10 - private $snapshot = self::ATTACHABLE; 11 - private $fragment = self::ATTACHABLE; 12 - private $fragmentVersion = self::ATTACHABLE; 13 - 14 - protected function getConfiguration() { 15 - return array( 16 - self::CONFIG_COLUMN_SCHEMA => array( 17 - 'fragmentVersionPHID' => 'phid?', 18 - ), 19 - self::CONFIG_KEY_SCHEMA => array( 20 - 'key_child' => array( 21 - 'columns' => array( 22 - 'snapshotPHID', 23 - 'fragmentPHID', 24 - 'fragmentVersionPHID', 25 - ), 26 - 'unique' => true, 27 - ), 28 - ), 29 - ) + parent::getConfiguration(); 30 - } 31 - 32 - public function getSnapshot() { 33 - return $this->assertAttached($this->snapshot); 34 - } 35 - 36 - public function attachSnapshot(PhragmentSnapshot $snapshot) { 37 - return $this->snapshot = $snapshot; 38 - } 39 - 40 - public function getFragment() { 41 - return $this->assertAttached($this->fragment); 42 - } 43 - 44 - public function attachFragment(PhragmentFragment $fragment) { 45 - return $this->fragment = $fragment; 46 - } 47 - 48 - public function getFragmentVersion() { 49 - if ($this->fragmentVersionPHID === null) { 50 - return null; 51 - } 52 - return $this->assertAttached($this->fragmentVersion); 53 - } 54 - 55 - public function attachFragmentVersion(PhragmentFragmentVersion $version) { 56 - return $this->fragmentVersion = $version; 57 - } 58 - 59 - 60 - /* -( Policy Interface )--------------------------------------------------- */ 61 - 62 - 63 - public function getCapabilities() { 64 - return array( 65 - PhabricatorPolicyCapability::CAN_VIEW, 66 - ); 67 - } 68 - 69 - public function getPolicy($capability) { 70 - return $this->getSnapshot()->getPolicy($capability); 71 - } 72 - 73 - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 74 - return $this->getSnapshot() 75 - ->hasAutomaticCapability($capability, $viewer); 76 - } 77 - 78 - public function describeAutomaticCapability($capability) { 79 - return $this->getSnapshot() 80 - ->describeAutomaticCapability($capability); 81 - } 82 - }
-53
src/applications/phragment/util/PhragmentPatchUtil.php
··· 1 - <?php 2 - 3 - final class PhragmentPatchUtil extends Phobject { 4 - 5 - const EMPTY_HASH = '0000000000000000000000000000000000000000'; 6 - 7 - /** 8 - * Calculate the DiffMatchPatch patch between two Phabricator files. 9 - * 10 - * @phutil-external-symbol class diff_match_patch 11 - */ 12 - public static function calculatePatch( 13 - PhabricatorFile $old = null, 14 - PhabricatorFile $new = null) { 15 - 16 - $root = dirname(phutil_get_library_root('phabricator')); 17 - require_once $root.'/externals/diff_match_patch/diff_match_patch.php'; 18 - 19 - $old_hash = self::EMPTY_HASH; 20 - $new_hash = self::EMPTY_HASH; 21 - 22 - if ($old !== null) { 23 - $old_hash = $old->getContentHash(); 24 - } 25 - if ($new !== null) { 26 - $new_hash = $new->getContentHash(); 27 - } 28 - 29 - $old_content = ''; 30 - $new_content = ''; 31 - 32 - if ($old_hash === $new_hash) { 33 - return null; 34 - } 35 - 36 - if ($old_hash !== self::EMPTY_HASH) { 37 - $old_content = $old->loadFileData(); 38 - } else { 39 - $old_content = ''; 40 - } 41 - 42 - if ($new_hash !== self::EMPTY_HASH) { 43 - $new_content = $new->loadFileData(); 44 - } else { 45 - $new_content = ''; 46 - } 47 - 48 - $dmp = new diff_match_patch(); 49 - $dmp_patches = $dmp->patch_make($old_content, $new_content); 50 - return $dmp->patch_toText($dmp_patches); 51 - } 52 - 53 - }
-4
src/docs/book/phabricator.book
··· 244 244 "name": "PHPAST", 245 245 "include": "(^src/applications/phpast/)" 246 246 }, 247 - "phragment": { 248 - "name": "Phragment", 249 - "include": "(^src/applications/phragment/)" 250 - }, 251 247 "phrequent": { 252 248 "name": "Phrequent", 253 249 "include": "(^src/applications/phrequent/)"
-1
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 103 103 'db.policy' => array(), 104 104 'db.nuance' => array(), 105 105 'db.passphrase' => array(), 106 - 'db.phragment' => array(), 107 106 'db.dashboard' => array(), 108 107 'db.system' => array(), 109 108 'db.fund' => array(),