@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.

Double-write file attachment to old "edge" storage and new "attachment" storage

Summary: Ref T13603. This adds a second write to new "attachment" storage to all writers except one in Paste, which creates the file inline.

Test Plan:
- Updated a macro image, confirmed a write to "attachment" storage (transaction pathway).
- Updated a blog profile image, confirmed a write to "attachment" storage (legacy pathway).

Maniphest Tasks: T13603

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

+334 -1
+20
src/applications/files/storage/PhabricatorFile.php
··· 1422 1422 ->addEdge($phid, $edge_type, $this->getPHID()) 1423 1423 ->save(); 1424 1424 1425 + $attachment_table = new PhabricatorFileAttachment(); 1426 + $attachment_conn = $attachment_table->establishConnection('w'); 1427 + 1428 + queryfx( 1429 + $attachment_conn, 1430 + 'INSERT INTO %R (objectPHID, filePHID, attachmentMode, 1431 + attacherPHID, dateCreated, dateModified) 1432 + VALUES (%s, %s, %s, %ns, %d, %d) 1433 + ON DUPLICATE KEY UPDATE 1434 + attachmentMode = VALUES(attachmentMode), 1435 + attacherPHID = VALUES(attacherPHID), 1436 + dateModified = VALUES(dateModified)', 1437 + $attachment_table, 1438 + $phid, 1439 + $this->getPHID(), 1440 + PhabricatorFileAttachment::MODE_ATTACH, 1441 + null, 1442 + PhabricatorTime::getNow(), 1443 + PhabricatorTime::getNow()); 1444 + 1425 1445 return $this; 1426 1446 } 1427 1447
+12
src/applications/files/storage/PhabricatorFileAttachment.php
··· 8 8 protected $attacherPHID; 9 9 protected $attachmentMode; 10 10 11 + const MODE_ATTACH = 'attach'; 12 + const MODE_REFERENCE = 'reference'; 13 + const MODE_DETACH = 'detach'; 14 + 11 15 protected function getConfiguration() { 12 16 return array( 13 17 self::CONFIG_COLUMN_SCHEMA => array( ··· 26 30 ), 27 31 ), 28 32 ) + parent::getConfiguration(); 33 + } 34 + 35 + public static function getModeList() { 36 + return array( 37 + self::MODE_ATTACH, 38 + self::MODE_REFERENCE, 39 + self::MODE_DETACH, 40 + ); 29 41 } 30 42 31 43 }
+1
src/applications/transactions/constants/PhabricatorTransactions.php
··· 18 18 const TYPE_SUBTYPE = 'core:subtype'; 19 19 const TYPE_HISTORY = 'core:history'; 20 20 const TYPE_MFA = 'core:mfa'; 21 + const TYPE_FILE = 'core:file'; 21 22 22 23 const COLOR_RED = 'red'; 23 24 const COLOR_ORANGE = 'orange';
+295
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 332 332 $types[] = PhabricatorTransactions::TYPE_CREATE; 333 333 $types[] = PhabricatorTransactions::TYPE_HISTORY; 334 334 335 + $types[] = PhabricatorTransactions::TYPE_FILE; 336 + 335 337 if ($this->object instanceof PhabricatorEditEngineSubtypeInterface) { 336 338 $types[] = PhabricatorTransactions::TYPE_SUBTYPE; 337 339 } ··· 388 390 389 391 $new = $this->getTransactionNewValue($object, $xaction); 390 392 $xaction->setNewValue($new); 393 + 394 + // Apply an optional transformation to convert "external" tranaction 395 + // values (provided by APIs) into "internal" values. 396 + 397 + $old = $xaction->getOldValue(); 398 + $new = $xaction->getNewValue(); 399 + 400 + $type = $xaction->getTransactionType(); 401 + $xtype = $this->getModularTransactionType($type); 402 + if ($xtype) { 403 + $xtype = clone $xtype; 404 + $xtype->setStorage($xaction); 405 + 406 + 407 + // TODO: Provide a modular hook for modern transactions to do a 408 + // transformation. 409 + list($old, $new) = array($old, $new); 410 + 411 + return; 412 + } else { 413 + switch ($type) { 414 + case PhabricatorTransactions::TYPE_FILE: 415 + list($old, $new) = $this->newFileTransactionInternalValues( 416 + $object, 417 + $xaction, 418 + $old, 419 + $new); 420 + break; 421 + } 422 + } 423 + 424 + $xaction->setOldValue($old); 425 + $xaction->setNewValue($new); 426 + } 427 + 428 + private function newFileTransactionInternalValues( 429 + PhabricatorLiskDAO $object, 430 + PhabricatorApplicationTransaction $xaction, 431 + $old, 432 + $new) { 433 + 434 + $old_map = array(); 435 + 436 + if (!$this->getIsNewObject()) { 437 + $phid = $object->getPHID(); 438 + 439 + $attachment_table = new PhabricatorFileAttachment(); 440 + $attachment_conn = $attachment_table->establishConnection('w'); 441 + 442 + $rows = queryfx_all( 443 + $attachment_conn, 444 + 'SELECT filePHID, attachmentMode FROM %R WHERE objectPHID = %s', 445 + $attachment_table, 446 + $phid); 447 + $old_map = ipull($rows, 'attachmentMode', 'filePHID'); 448 + } 449 + 450 + $new_map = $old_map; 451 + 452 + foreach ($new as $file_phid => $attachment_mode) { 453 + if ($attachment_mode == PhabricatorFileAttachment::MODE_DETACH) { 454 + unset($new_map[$file_phid]); 455 + continue; 456 + } 457 + 458 + $new_map[$file_phid] = $attachment_mode; 459 + } 460 + 461 + foreach (array_keys($old_map + $new_map) as $key) { 462 + if (isset($old_map[$key]) && isset($new_map[$key])) { 463 + if ($old_map[$key] === $new_map[$key]) { 464 + unset($old_map[$key]); 465 + unset($new_map[$key]); 466 + } 467 + } 468 + } 469 + 470 + return array($old_map, $new_map); 391 471 } 392 472 393 473 private function getTransactionOldValue( ··· 481 561 return $xaction->getOldValue(); 482 562 case PhabricatorTransactions::TYPE_COMMENT: 483 563 return null; 564 + case PhabricatorTransactions::TYPE_FILE: 565 + return null; 484 566 default: 485 567 return $this->getCustomTransactionOldValue($object, $xaction); 486 568 } ··· 512 594 case PhabricatorTransactions::TYPE_INLINESTATE: 513 595 case PhabricatorTransactions::TYPE_SUBTYPE: 514 596 case PhabricatorTransactions::TYPE_HISTORY: 597 + case PhabricatorTransactions::TYPE_FILE: 515 598 return $xaction->getNewValue(); 516 599 case PhabricatorTransactions::TYPE_MFA: 517 600 return true; ··· 670 753 case PhabricatorTransactions::TYPE_EDGE: 671 754 case PhabricatorTransactions::TYPE_SPACE: 672 755 case PhabricatorTransactions::TYPE_COMMENT: 756 + case PhabricatorTransactions::TYPE_FILE: 673 757 return $this->applyBuiltinInternalTransaction($object, $xaction); 674 758 } 675 759 ··· 733 817 case PhabricatorTransactions::TYPE_INLINESTATE: 734 818 case PhabricatorTransactions::TYPE_SPACE: 735 819 case PhabricatorTransactions::TYPE_COMMENT: 820 + case PhabricatorTransactions::TYPE_FILE: 736 821 return $this->applyBuiltinExternalTransaction($object, $xaction); 737 822 } 738 823 ··· 857 942 case PhabricatorTransactions::TYPE_HISTORY: 858 943 $this->sendHistory = true; 859 944 break; 945 + case PhabricatorTransactions::TYPE_FILE: 946 + $this->applyFileTransaction($object, $xaction); 947 + break; 948 + } 949 + } 950 + 951 + private function applyFileTransaction( 952 + PhabricatorLiskDAO $object, 953 + PhabricatorApplicationTransaction $xaction) { 954 + 955 + $old_map = $xaction->getOldValue(); 956 + $new_map = $xaction->getNewValue(); 957 + 958 + $add_phids = array(); 959 + $rem_phids = array(); 960 + 961 + foreach ($new_map as $phid => $mode) { 962 + $add_phids[$phid] = $mode; 963 + } 964 + 965 + foreach ($old_map as $phid => $mode) { 966 + if (!isset($new_map[$phid])) { 967 + $rem_phids[] = $phid; 968 + } 969 + } 970 + 971 + $now = PhabricatorTime::getNow(); 972 + $object_phid = $object->getPHID(); 973 + $attacher_phid = $this->getActingAsPHID(); 974 + 975 + $attachment_table = new PhabricatorFileAttachment(); 976 + $attachment_conn = $attachment_table->establishConnection('w'); 977 + 978 + $add_sql = array(); 979 + foreach ($add_phids as $add_phid => $add_mode) { 980 + $add_sql[] = qsprintf( 981 + $attachment_conn, 982 + '(%s, %s, %s, %ns, %d, %d)', 983 + $object_phid, 984 + $add_phid, 985 + $add_mode, 986 + $attacher_phid, 987 + $now, 988 + $now); 989 + } 990 + 991 + $rem_sql = array(); 992 + foreach ($rem_phids as $rem_phid) { 993 + $rem_sql[] = qsprintf( 994 + $attachment_conn, 995 + '%s', 996 + $rem_phid); 997 + } 998 + 999 + foreach (PhabricatorLiskDAO::chunkSQL($add_sql) as $chunk) { 1000 + queryfx( 1001 + $attachment_conn, 1002 + 'INSERT INTO %R (objectPHID, filePHID, attachmentMode, 1003 + attacherPHID, dateCreated, dateModified) 1004 + VALUES %LQ 1005 + ON DUPLICATE KEY UPDATE 1006 + attachmentMode = VALUES(attachmentMode), 1007 + attacherPHID = VALUES(attacherPHID), 1008 + dateModified = VALUES(dateModified)', 1009 + $attachment_table, 1010 + $chunk); 1011 + } 1012 + 1013 + foreach (PhabricatorLiskDAO::chunkSQL($rem_sql) as $chunk) { 1014 + queryfx( 1015 + $attachment_conn, 1016 + 'DELETE FROM %R WHERE objectPHID = %s AND filePHID in (%LQ)', 1017 + $attachment_table, 1018 + $object_phid, 1019 + $chunk); 860 1020 } 861 1021 } 862 1022 ··· 1790 1950 // Signing a transaction group with MFA does not require permissions 1791 1951 // on its own. 1792 1952 return null; 1953 + case PhabricatorTransactions::TYPE_FILE: 1954 + return null; 1793 1955 case PhabricatorTransactions::TYPE_EDGE: 1794 1956 return $this->getLegacyRequiredEdgeCapabilities($xaction); 1795 1957 default: ··· 2066 2228 $xactions[] = $xaction; 2067 2229 } 2068 2230 2231 + $file_xaction = $this->newFileTransaction( 2232 + $object, 2233 + $xactions); 2234 + if ($file_xaction) { 2235 + $xactions[] = $file_xaction; 2236 + } 2237 + 2069 2238 return $xactions; 2070 2239 } 2071 2240 2241 + 2242 + private function newFileTransaction( 2243 + PhabricatorLiskDAO $object, 2244 + array $xactions) { 2245 + 2246 + $new_map = array(); 2247 + 2248 + $file_phids = $this->extractFilePHIDs($object, $xactions); 2249 + if (!$file_phids) { 2250 + return null; 2251 + } 2252 + 2253 + foreach ($file_phids as $file_phid) { 2254 + $new_map[$file_phid] = PhabricatorFileAttachment::MODE_ATTACH; 2255 + } 2256 + 2257 + $xaction = $object->getApplicationTransactionTemplate() 2258 + ->setTransactionType(PhabricatorTransactions::TYPE_FILE) 2259 + ->setNewValue($new_map); 2260 + 2261 + return $xaction; 2262 + } 2263 + 2264 + 2072 2265 private function getRemarkupChanges(array $xactions) { 2073 2266 $changes = array(); 2074 2267 ··· 2667 2860 idx($groups, $field->getFieldKey(), array())); 2668 2861 } 2669 2862 break; 2863 + case PhabricatorTransactions::TYPE_FILE: 2864 + $errors[] = $this->validateFileTransactions( 2865 + $object, 2866 + $xactions, 2867 + $type); 2868 + break; 2670 2869 } 2671 2870 2672 2871 return array_mergev($errors); 2673 2872 } 2873 + 2874 + private function validateFileTransactions( 2875 + PhabricatorLiskDAO $object, 2876 + array $xactions, 2877 + $transaction_type) { 2878 + 2879 + $errors = array(); 2880 + 2881 + $mode_map = PhabricatorFileAttachment::getModeList(); 2882 + $mode_map = array_fuse($mode_map); 2883 + 2884 + $file_phids = array(); 2885 + foreach ($xactions as $xaction) { 2886 + $new = $xaction->getNewValue(); 2887 + 2888 + if (!is_array($new)) { 2889 + $errors[] = new PhabricatorApplicationTransactionValidationError( 2890 + $transaction_type, 2891 + pht('Invalid'), 2892 + pht( 2893 + 'File attachment transaction must have a map of files to '. 2894 + 'attachment modes, found "%s".', 2895 + phutil_describe_type($new)), 2896 + $xaction); 2897 + continue; 2898 + } 2899 + 2900 + foreach ($new as $file_phid => $attachment_mode) { 2901 + $file_phids[$file_phid] = $file_phid; 2902 + 2903 + if (is_string($attachment_mode) && isset($mode_map[$attachment_mode])) { 2904 + continue; 2905 + } 2906 + 2907 + if (!is_string($attachment_mode)) { 2908 + $errors[] = new PhabricatorApplicationTransactionValidationError( 2909 + $transaction_type, 2910 + pht('Invalid'), 2911 + pht( 2912 + 'File attachment mode (for file "%s") is invalid. Expected '. 2913 + 'a string, found "%s".', 2914 + $file_phid, 2915 + phutil_describe_type($attachment_mode)), 2916 + $xaction); 2917 + } else { 2918 + $errors[] = new PhabricatorApplicationTransactionValidationError( 2919 + $transaction_type, 2920 + pht('Invalid'), 2921 + pht( 2922 + 'File attachment mode "%s" (for file "%s") is invalid. Valid '. 2923 + 'modes are: %s.', 2924 + $attachment_mode, 2925 + $file_phid, 2926 + pht_list($mode_map)), 2927 + $xaction); 2928 + } 2929 + } 2930 + } 2931 + 2932 + if ($file_phids) { 2933 + $file_map = id(new PhabricatorFileQuery()) 2934 + ->setViewer($this->getActor()) 2935 + ->withPHIDs($file_phids) 2936 + ->execute(); 2937 + $file_map = mpull($file_map, null, 'getPHID'); 2938 + } else { 2939 + $file_map = array(); 2940 + } 2941 + 2942 + foreach ($xactions as $xaction) { 2943 + $new = $xaction->getNewValue(); 2944 + 2945 + if (!is_array($new)) { 2946 + continue; 2947 + } 2948 + 2949 + foreach ($new as $file_phid => $attachment_mode) { 2950 + if (isset($file_map[$file_phid])) { 2951 + continue; 2952 + } 2953 + 2954 + $errors[] = new PhabricatorApplicationTransactionValidationError( 2955 + $transaction_type, 2956 + pht('Invalid'), 2957 + pht( 2958 + 'File "%s" is invalid: it could not be loaded, or you do not '. 2959 + 'have permission to view it. You must be able to see a file to '. 2960 + 'attach it to an object.', 2961 + $file_phid), 2962 + $xaction); 2963 + } 2964 + } 2965 + 2966 + return $errors; 2967 + } 2968 + 2674 2969 2675 2970 public function validatePolicyTransaction( 2676 2971 PhabricatorLiskDAO $object,
+6 -1
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 583 583 return true; 584 584 } 585 585 586 + // Always hide file attach/detach transactions. 587 + if ($xaction_type === PhabricatorTransactions::TYPE_FILE) { 588 + return true; 589 + } 590 + 586 591 // Hide creation transactions if the old value is empty. These are 587 592 // transactions like "alice set the task title to: ...", which are 588 593 // essentially never interesting. ··· 711 716 switch ($this->getTransactionType()) { 712 717 case PhabricatorTransactions::TYPE_TOKEN: 713 718 return true; 714 - case PhabricatorTransactions::TYPE_EDGE: 719 + case PhabricatorTransactions::TYPE_EDGE: 715 720 $edge_type = $this->getMetadataValue('edge:type'); 716 721 switch ($edge_type) { 717 722 case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: