@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

Pholio - support editing images - fixes T3489

Summary:
Nice title. We add three new transactions - IMAGE_FILE, IMAGE_NAME, and IMAGE_DESCRIPTION. The first is a bit like subscribers as it is a list of file phids. The latter have values of the form ($file_phid => $data), where $data is $name or $description respectively. This is because we need to collate transactions based on $file_phid...

Overall, this uses the _underyling files_ and not the "PholioImage" to determine if things are unique or not. That said, simply mark PholioImages as obsolete so inline comments about no-longer applicable PholioImages don't break.

Does a reasonable job implementing the mock. Note you can't "update" an image at this time, though you can delete and add at will.

Test Plan: played with pholio a ton.

Reviewers: epriestley

Reviewed By: epriestley

CC: chad, aran, Korvin

Maniphest Tasks: T3489

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

+922 -184
+23
resources/sql/patches/20130711.pholioimageobsolete.php
··· 1 + <?php 2 + 3 + echo "Giving pholio images PHIDs"; 4 + $table = new PholioImage(); 5 + $table->openTransaction(); 6 + 7 + foreach (new LiskMigrationIterator($table) as $image) { 8 + if ($image->getPHID()) { 9 + continue; 10 + } 11 + 12 + echo "."; 13 + 14 + queryfx( 15 + $image->establishConnection('w'), 16 + 'UPDATE %T SET phid = %s WHERE id = %d', 17 + $image->getTableName(), 18 + $image->generatePHID(), 19 + $image->getID()); 20 + } 21 + 22 + $table->saveTransaction(); 23 + echo "\nDone.\n";
+14
resources/sql/patches/20130711.pholioimageobsolete.sql
··· 1 + ALTER TABLE {$NAMESPACE}_pholio.pholio_image 2 + ADD `isObsolete` TINYINT(1) NOT NULL DEFAULT '0'; 3 + 4 + ALTER TABLE {$NAMESPACE}_pholio.pholio_image 5 + DROP KEY `mockID`; 6 + 7 + ALTER TABLE {$NAMESPACE}_pholio.pholio_image 8 + ADD KEY `mockID` (`mockID`, `isObsolete`, `sequence`); 9 + 10 + ALTER TABLE {$NAMESPACE}_pholio.pholio_image 11 + ADD `phid` VARCHAR(64) NOT NULL COLLATE utf8_bin AFTER `id`; 12 + 13 + ALTER TABLE {$NAMESPACE}_pholio.pholio_image 14 + CHANGE `mockID` `mockID` INT(10) UNSIGNED;
+2
resources/sql/patches/20130711.pholioimageobsolete2.sql
··· 1 + ALTER TABLE {$NAMESPACE}_pholio.pholio_image 2 + ADD UNIQUE KEY `keyPHID` (`phid`);
+32 -23
src/__celerity_resource_map__.php
··· 1270 1270 ), 1271 1271 'javelin-behavior-aphront-drag-and-drop' => 1272 1272 array( 1273 - 'uri' => '/res/36335362/rsrc/js/core/behavior-drag-and-drop.js', 1273 + 'uri' => '/res/fde3763f/rsrc/js/core/behavior-drag-and-drop.js', 1274 1274 'type' => 'js', 1275 1275 'requires' => 1276 1276 array( ··· 3727 3727 ), 3728 3728 'disk' => '/rsrc/css/application/pholio/pholio.css', 3729 3729 ), 3730 + 'pholio-edit-css' => 3731 + array( 3732 + 'uri' => '/res/c4a1f392/rsrc/css/application/pholio/pholio-edit.css', 3733 + 'type' => 'css', 3734 + 'requires' => 3735 + array( 3736 + ), 3737 + 'disk' => '/rsrc/css/application/pholio/pholio-edit.css', 3738 + ), 3730 3739 'pholio-inline-comments-css' => 3731 3740 array( 3732 3741 'uri' => '/res/006fc575/rsrc/css/application/pholio/pholio-inline-comments.css', ··· 4279 4288 'uri' => '/res/pkg/dd27a69b/differential.pkg.css', 4280 4289 'type' => 'css', 4281 4290 ), 4282 - '504ca7d2' => 4291 + 'bb59a901' => 4283 4292 array( 4284 4293 'name' => 'differential.pkg.js', 4285 4294 'symbols' => ··· 4305 4314 18 => 'javelin-behavior-differential-toggle-files', 4306 4315 19 => 'javelin-behavior-differential-user-select', 4307 4316 ), 4308 - 'uri' => '/res/pkg/504ca7d2/differential.pkg.js', 4317 + 'uri' => '/res/pkg/bb59a901/differential.pkg.js', 4309 4318 'type' => 'js', 4310 4319 ), 4311 4320 'c8ce2d88' => ··· 4403 4412 'aphront-typeahead-control-css' => 'f32a863a', 4404 4413 'differential-changeset-view-css' => 'dd27a69b', 4405 4414 'differential-core-view-css' => 'dd27a69b', 4406 - 'differential-inline-comment-editor' => '504ca7d2', 4415 + 'differential-inline-comment-editor' => 'bb59a901', 4407 4416 'differential-local-commits-view-css' => 'dd27a69b', 4408 4417 'differential-results-table-css' => 'dd27a69b', 4409 4418 'differential-revision-add-comment-css' => 'dd27a69b', ··· 4421 4430 'javelin-behavior-aphlict-dropdown' => '75ccea43', 4422 4431 'javelin-behavior-aphlict-listen' => '75ccea43', 4423 4432 'javelin-behavior-aphront-basic-tokenizer' => '75ccea43', 4424 - 'javelin-behavior-aphront-drag-and-drop' => '504ca7d2', 4425 - 'javelin-behavior-aphront-drag-and-drop-textarea' => '504ca7d2', 4433 + 'javelin-behavior-aphront-drag-and-drop' => 'bb59a901', 4434 + 'javelin-behavior-aphront-drag-and-drop-textarea' => 'bb59a901', 4426 4435 'javelin-behavior-aphront-form-disable-on-submit' => '75ccea43', 4427 4436 'javelin-behavior-audit-preview' => '96909266', 4428 4437 'javelin-behavior-dark-console' => '4ccfeb47', 4429 4438 'javelin-behavior-device' => '75ccea43', 4430 - 'javelin-behavior-differential-accept-with-errors' => '504ca7d2', 4431 - 'javelin-behavior-differential-add-reviewers-and-ccs' => '504ca7d2', 4432 - 'javelin-behavior-differential-comment-jump' => '504ca7d2', 4433 - 'javelin-behavior-differential-diff-radios' => '504ca7d2', 4434 - 'javelin-behavior-differential-dropdown-menus' => '504ca7d2', 4435 - 'javelin-behavior-differential-edit-inline-comments' => '504ca7d2', 4436 - 'javelin-behavior-differential-feedback-preview' => '504ca7d2', 4437 - 'javelin-behavior-differential-keyboard-navigation' => '504ca7d2', 4438 - 'javelin-behavior-differential-populate' => '504ca7d2', 4439 - 'javelin-behavior-differential-show-more' => '504ca7d2', 4440 - 'javelin-behavior-differential-toggle-files' => '504ca7d2', 4441 - 'javelin-behavior-differential-user-select' => '504ca7d2', 4439 + 'javelin-behavior-differential-accept-with-errors' => 'bb59a901', 4440 + 'javelin-behavior-differential-add-reviewers-and-ccs' => 'bb59a901', 4441 + 'javelin-behavior-differential-comment-jump' => 'bb59a901', 4442 + 'javelin-behavior-differential-diff-radios' => 'bb59a901', 4443 + 'javelin-behavior-differential-dropdown-menus' => 'bb59a901', 4444 + 'javelin-behavior-differential-edit-inline-comments' => 'bb59a901', 4445 + 'javelin-behavior-differential-feedback-preview' => 'bb59a901', 4446 + 'javelin-behavior-differential-keyboard-navigation' => 'bb59a901', 4447 + 'javelin-behavior-differential-populate' => 'bb59a901', 4448 + 'javelin-behavior-differential-show-more' => 'bb59a901', 4449 + 'javelin-behavior-differential-toggle-files' => 'bb59a901', 4450 + 'javelin-behavior-differential-user-select' => 'bb59a901', 4442 4451 'javelin-behavior-diffusion-commit-graph' => '96909266', 4443 4452 'javelin-behavior-diffusion-pull-lastmodified' => '96909266', 4444 4453 'javelin-behavior-error-log' => '4ccfeb47', ··· 4446 4455 'javelin-behavior-history-install' => '75ccea43', 4447 4456 'javelin-behavior-konami' => '75ccea43', 4448 4457 'javelin-behavior-lightbox-attachments' => '75ccea43', 4449 - 'javelin-behavior-load-blame' => '504ca7d2', 4458 + 'javelin-behavior-load-blame' => 'bb59a901', 4450 4459 'javelin-behavior-maniphest-batch-selector' => '98f64f07', 4451 4460 'javelin-behavior-maniphest-subpriority-editor' => '98f64f07', 4452 4461 'javelin-behavior-maniphest-transaction-controls' => '98f64f07', ··· 4458 4467 'javelin-behavior-phabricator-hovercards' => '75ccea43', 4459 4468 'javelin-behavior-phabricator-keyboard-shortcuts' => '75ccea43', 4460 4469 'javelin-behavior-phabricator-nav' => '75ccea43', 4461 - 'javelin-behavior-phabricator-object-selector' => '504ca7d2', 4470 + 'javelin-behavior-phabricator-object-selector' => 'bb59a901', 4462 4471 'javelin-behavior-phabricator-oncopy' => '75ccea43', 4463 4472 'javelin-behavior-phabricator-remarkup-assist' => '75ccea43', 4464 4473 'javelin-behavior-phabricator-reveal-content' => '75ccea43', ··· 4466 4475 'javelin-behavior-phabricator-tooltips' => '75ccea43', 4467 4476 'javelin-behavior-phabricator-watch-anchor' => '75ccea43', 4468 4477 'javelin-behavior-refresh-csrf' => '75ccea43', 4469 - 'javelin-behavior-repository-crossreference' => '504ca7d2', 4478 + 'javelin-behavior-repository-crossreference' => 'bb59a901', 4470 4479 'javelin-behavior-toggle-class' => '75ccea43', 4471 4480 'javelin-behavior-workflow' => '75ccea43', 4472 4481 'javelin-dom' => 'a9f14d76', ··· 4497 4506 'phabricator-content-source-view-css' => 'dd27a69b', 4498 4507 'phabricator-core-css' => 'f32a863a', 4499 4508 'phabricator-crumbs-view-css' => 'f32a863a', 4500 - 'phabricator-drag-and-drop-file-upload' => '504ca7d2', 4509 + 'phabricator-drag-and-drop-file-upload' => 'bb59a901', 4501 4510 'phabricator-dropdown-menu' => '75ccea43', 4502 4511 'phabricator-file-upload' => '75ccea43', 4503 4512 'phabricator-filetree-view-css' => 'f32a863a', ··· 4521 4530 'phabricator-project-tag-css' => 'adc3c36d', 4522 4531 'phabricator-property-list-view-css' => 'f32a863a', 4523 4532 'phabricator-remarkup-css' => 'f32a863a', 4524 - 'phabricator-shaped-request' => '504ca7d2', 4533 + 'phabricator-shaped-request' => 'bb59a901', 4525 4534 'phabricator-side-menu-view-css' => 'f32a863a', 4526 4535 'phabricator-standard-page-view' => 'f32a863a', 4527 4536 'phabricator-tag-view-css' => 'f32a863a',
+12 -2
src/__phutil_library_map__.php
··· 14 14 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 15 15 'Aphront403Response' => 'aphront/response/Aphront403Response.php', 16 16 'Aphront404Response' => 'aphront/response/Aphront404Response.php', 17 + 'AphrontAbstractAttachedFileView' => 'view/control/AphrontAbstractAttachedFileView.php', 18 + 'AphrontAbstractFormDragAndDropUploadControl' => 'view/form/control/AphrontAbstractFormDragAndDropUploadControl.php', 17 19 'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php', 18 20 'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php', 19 21 'AphrontAttachedFileView' => 'view/control/AphrontAttachedFileView.php', ··· 1713 1715 'PholioConstants' => 'applications/pholio/constants/PholioConstants.php', 1714 1716 'PholioController' => 'applications/pholio/controller/PholioController.php', 1715 1717 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 1718 + 'PholioDragAndDropUploadControl' => 'applications/pholio/view/PholioDragAndDropUploadControl.php', 1719 + 'PholioDropUploadController' => 'applications/pholio/controller/PholioDropUploadController.php', 1716 1720 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 1717 1721 'PholioInlineCommentEditView' => 'applications/pholio/view/PholioInlineCommentEditView.php', 1718 1722 'PholioInlineCommentSaveView' => 'applications/pholio/view/PholioInlineCommentSaveView.php', ··· 1741 1745 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 1742 1746 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 1743 1747 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 1748 + 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 1744 1749 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 1745 1750 'PhortuneAccountBuyController' => 'applications/phortune/controller/PhortuneAccountBuyController.php', 1746 1751 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', ··· 1967 1972 'Aphront400Response' => 'AphrontResponse', 1968 1973 'Aphront403Response' => 'AphrontHTMLResponse', 1969 1974 'Aphront404Response' => 'AphrontHTMLResponse', 1975 + 'AphrontAbstractAttachedFileView' => 'AphrontView', 1976 + 'AphrontAbstractFormDragAndDropUploadControl' => 'AphrontFormControl', 1970 1977 'AphrontAjaxResponse' => 'AphrontResponse', 1971 - 'AphrontAttachedFileView' => 'AphrontView', 1978 + 'AphrontAttachedFileView' => 'AphrontAbstractAttachedFileView', 1972 1979 'AphrontBarView' => 'AphrontView', 1973 1980 'AphrontCSRFException' => 'AphrontException', 1974 1981 'AphrontCalendarEventView' => 'AphrontView', ··· 1988 1995 'AphrontFormCropControl' => 'AphrontFormControl', 1989 1996 'AphrontFormDateControl' => 'AphrontFormControl', 1990 1997 'AphrontFormDividerControl' => 'AphrontFormControl', 1991 - 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', 1998 + 'AphrontFormDragAndDropUploadControl' => 'AphrontAbstractFormDragAndDropUploadControl', 1992 1999 'AphrontFormFileControl' => 'AphrontFormControl', 1993 2000 'AphrontFormImageControl' => 'AphrontFormControl', 1994 2001 'AphrontFormInsetView' => 'AphrontView', ··· 3699 3706 'PhluxViewController' => 'PhluxController', 3700 3707 'PholioController' => 'PhabricatorController', 3701 3708 'PholioDAO' => 'PhabricatorLiskDAO', 3709 + 'PholioDragAndDropUploadControl' => 'AphrontAbstractFormDragAndDropUploadControl', 3710 + 'PholioDropUploadController' => 'PholioController', 3702 3711 'PholioImage' => 3703 3712 array( 3704 3713 0 => 'PholioDAO', ··· 3739 3748 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3740 3749 'PholioTransactionType' => 'PholioConstants', 3741 3750 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 3751 + 'PholioUploadedImageView' => 'AphrontAbstractAttachedFileView', 3742 3752 'PhortuneAccount' => 3743 3753 array( 3744 3754 0 => 'PhortuneDAO',
+15 -1
src/applications/files/controller/PhabricatorFileDropUploadController.php
··· 3 3 final class PhabricatorFileDropUploadController 4 4 extends PhabricatorFileController { 5 5 6 + private $viewObject; 7 + 8 + public function setViewObject(AphrontAbstractAttachedFileView $view) { 9 + $this->viewObject = $view; 10 + return $this; 11 + } 12 + 13 + public function getViewObject() { 14 + if (!$this->viewObject) { 15 + $this->viewObject = new AphrontAttachedFileView(); 16 + } 17 + return $this->viewObject; 18 + } 19 + 6 20 /** 7 21 * @phutil-external-symbol class PhabricatorStartup 8 22 */ ··· 24 38 'isExplicitUpload' => true, 25 39 )); 26 40 27 - $view = new AphrontAttachedFileView(); 41 + $view = $this->getViewObject(); 28 42 $view->setFile($file); 29 43 30 44 return id(new AphrontAjaxResponse())->setContent(
+1
src/applications/phid/PhabricatorObjectHandle.php
··· 107 107 PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'Phriction Document', 108 108 PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'Image Macro', 109 109 PhabricatorPHIDConstants::PHID_TYPE_MOCK => 'Pholio Mock', 110 + PhabricatorPHIDConstants::PHID_TYPE_PIMG => 'Pholio Image', 110 111 PhabricatorPHIDConstants::PHID_TYPE_FILE => 'File', 111 112 PhabricatorPHIDConstants::PHID_TYPE_BLOG => 'Blog', 112 113 PhabricatorPHIDConstants::PHID_TYPE_POST => 'Post',
+1
src/applications/phid/PhabricatorPHIDConstants.php
··· 29 29 const PHID_TYPE_QUES = 'QUES'; 30 30 const PHID_TYPE_ANSW = 'ANSW'; 31 31 const PHID_TYPE_MOCK = 'MOCK'; 32 + const PHID_TYPE_PIMG = 'PIMG'; 32 33 const PHID_TYPE_MCRO = 'MCRO'; 33 34 const PHID_TYPE_CONF = 'CONF'; 34 35 const PHID_TYPE_CONP = 'CONP';
+25
src/applications/phid/handle/PhabricatorObjectHandleData.php
··· 137 137 ->execute(); 138 138 return mpull($mocks, null, 'getPHID'); 139 139 140 + case PhabricatorPHIDConstants::PHID_TYPE_PIMG: 141 + $images = id(new PholioImage()) 142 + ->loadAllWhere('phid IN (%Ls)', $phids); 143 + return mpull($images, null, 'getPHID'); 144 + 140 145 case PhabricatorPHIDConstants::PHID_TYPE_POLL: 141 146 $polls = id(new PhabricatorSlowvotePoll()) 142 147 ->loadAllWhere('phid IN (%Ls)', $phids); ··· 648 653 $handles[$phid] = $handle; 649 654 } 650 655 break; 656 + 657 + case PhabricatorPHIDConstants::PHID_TYPE_PIMG: 658 + foreach ($phids as $phid) { 659 + $handle = new PhabricatorObjectHandle(); 660 + $handle->setPHID($phid); 661 + $handle->setType($type); 662 + if (empty($objects[$phid])) { 663 + $handle->setName('Unknown Image'); 664 + } else { 665 + $image = $objects[$phid]; 666 + $handle->setName($image->getName()); 667 + $handle->setFullName($image->getName()); 668 + $handle->setURI( 669 + '/M'.$image->getMockID().'/'.$image->getID().'/'); 670 + $handle->setComplete(true); 671 + } 672 + $handles[$phid] = $handle; 673 + } 674 + break; 675 + 651 676 652 677 case PhabricatorPHIDConstants::PHID_TYPE_POLL: 653 678 foreach ($phids as $phid) {
+1
src/applications/pholio/application/PhabricatorApplicationPholio.php
··· 57 57 'edit/(?P<id>\d+)/' => 'PholioInlineEditController', 58 58 'thumb/(?P<imageid>\d+)/' => 'PholioInlineThumbController' 59 59 ), 60 + 'image/upload/' => 'PholioDropUploadController', 60 61 ), 61 62 ); 62 63 }
+3
src/applications/pholio/config/PhabricatorPholioConfigOptions.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PhabricatorPholioConfigOptions 4 7 extends PhabricatorApplicationConfigOptions { 5 8
+3
src/applications/pholio/constants/PholioConstants.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 abstract class PholioConstants { 4 7 5 8 }
+11 -1
src/applications/pholio/constants/PholioTransactionType.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioTransactionType extends PholioConstants { 4 7 8 + /* edits to the high level mock */ 5 9 const TYPE_NAME = 'name'; 6 10 const TYPE_DESCRIPTION = 'description'; 7 - const TYPE_INLINE = 'inline'; 8 11 12 + /* edits to images within the mock */ 13 + const TYPE_IMAGE_FILE = 'image-file'; 14 + const TYPE_IMAGE_NAME= 'image-name'; 15 + const TYPE_IMAGE_DESCRIPTION = 'image-description'; 16 + 17 + /* your witty commentary at the mock : image : x,y level */ 18 + const TYPE_INLINE = 'inline'; 9 19 }
+14
src/applications/pholio/controller/PholioDropUploadController.php
··· 1 + <?php 2 + 3 + /** 4 + * @group pholio 5 + */ 6 + final class PholioDropUploadController extends PholioController { 7 + 8 + public function processRequest() { 9 + return $this->delegateToController( 10 + id(new PhabricatorFileDropUploadController($this->getRequest())) 11 + ->setViewObject(new PholioUploadedImageView())); 12 + } 13 + 14 + }
+81 -57
src/applications/pholio/controller/PholioMockEditController.php
··· 15 15 $request = $this->getRequest(); 16 16 $user = $request->getUser(); 17 17 18 - 19 18 if ($this->id) { 20 19 $mock = id(new PholioMockQuery()) 21 20 ->setViewer($user) 21 + ->needImages(true) 22 22 ->requireCapabilities( 23 23 array( 24 24 PhabricatorPolicyCapability::CAN_VIEW, ··· 34 34 $title = pht('Edit Mock'); 35 35 36 36 $is_new = false; 37 + $mock_images = $mock->getImages(); 38 + $files = mpull($mock_images, 'getFile'); 39 + $mock_images = mpull($mock_images, null, 'getFilePHID'); 37 40 } else { 38 - $mock = new PholioMock(); 39 - $mock->setAuthorPHID($user->getPHID()); 40 - $mock->setViewPolicy(PhabricatorPolicies::POLICY_USER); 41 + $mock = id(new PholioMock()) 42 + ->setAuthorPHID($user->getPHID()) 43 + ->attachImages(array()) 44 + ->setViewPolicy(PhabricatorPolicies::POLICY_USER); 41 45 42 46 $title = pht('Create Mock'); 43 47 44 48 $is_new = true; 49 + $files = array(); 50 + $mock_images = array(); 45 51 } 46 52 47 53 $e_name = true; ··· 53 59 $v_view = $mock->getViewPolicy(); 54 60 $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( 55 61 $mock->getPHID()); 56 - $files = array(); 57 62 58 63 if ($request->isFormPost()) { 59 64 $xactions = array(); ··· 68 73 $v_view = $request->getStr('can_view'); 69 74 $v_cc = $request->getArr('cc'); 70 75 71 - $xactions[$type_name] = $v_name; 72 - $xactions[$type_desc] = $v_desc; 73 - $xactions[$type_view] = $v_view; 74 - $xactions[$type_cc] = array('=' => $v_cc); 76 + $mock_xactions = array(); 77 + $mock_xactions[$type_name] = $v_name; 78 + $mock_xactions[$type_desc] = $v_desc; 79 + $mock_xactions[$type_view] = $v_view; 80 + $mock_xactions[$type_cc] = array('=' => $v_cc); 75 81 76 82 if (!strlen($request->getStr('name'))) { 77 83 $e_name = 'Required'; 78 - $errors[] = pht('You must name the mock.'); 79 84 } 80 85 81 - $images = array(); 82 - if ($is_new) { 83 - // TODO: Make this transactional and allow edits? 86 + $file_phids = $request->getArr('file_phids'); 87 + if ($file_phids) { 88 + $files = id(new PhabricatorFileQuery()) 89 + ->setViewer($user) 90 + ->withPHIDs($file_phids) 91 + ->execute(); 92 + $files = mpull($files, null, 'getPHID'); 93 + $files = array_select_keys($files, $file_phids); 94 + } 84 95 85 - 86 - $file_phids = $request->getArr('file_phids'); 87 - if ($file_phids) { 88 - $files = id(new PhabricatorFileQuery()) 89 - ->setViewer($user) 90 - ->withPHIDs($file_phids) 91 - ->execute(); 92 - } 93 - 94 - if (!$files) { 95 - $e_images = pht('Required'); 96 - $errors[] = pht('You must add at least one image to the mock.'); 97 - } else { 98 - $mock->setCoverPHID(head($files)->getPHID()); 99 - } 100 - 101 - $sequence = 0; 102 - 103 - foreach ($files as $file) { 104 - $image = new PholioImage(); 105 - $image->setFilePHID($file->getPHID()); 106 - $image->setSequence($sequence++); 107 - 108 - $images[] = $image; 109 - } 96 + if (!$files) { 97 + $e_images = pht('Required'); 98 + $errors[] = pht('You must add at least one image to the mock.'); 99 + } else { 100 + $mock->setCoverPHID(head($files)->getPHID()); 110 101 } 111 102 112 103 if (!$errors) { 113 - foreach ($xactions as $type => $value) { 104 + foreach ($mock_xactions as $type => $value) { 114 105 $xactions[$type] = id(new PholioTransaction()) 115 106 ->setTransactionType($type) 116 107 ->setNewValue($value); 117 108 } 118 109 110 + $sequence = 0; 111 + foreach ($files as $file_phid => $file) { 112 + $mock_image = idx($mock_images, $file_phid); 113 + $title = $request->getStr('title_'.$file_phid); 114 + $description = $request->getStr('description_'.$file_phid); 115 + if (!$mock_image) { 116 + // this is an add 117 + $add_image = id(new PholioImage()) 118 + ->setFilePhid($file_phid) 119 + ->setName(strlen($title) ? $title : $file->getName()) 120 + ->setDescription($description) 121 + ->setSequence($sequence); 122 + $xactions[] = id(new PholioTransaction()) 123 + ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) 124 + ->setNewValue( 125 + array('+' => array($add_image))); 126 + } else { 127 + // update (maybe) 128 + $xactions[] = id(new PholioTransaction()) 129 + ->setTransactionType(PholioTransactionType::TYPE_IMAGE_NAME) 130 + ->setNewValue( 131 + array($mock_image->getPHID() => $title)); 132 + $xactions[] = id(new PholioTransaction()) 133 + ->setTransactionType( 134 + PholioTransactionType::TYPE_IMAGE_DESCRIPTION) 135 + ->setNewValue(array($mock_image->getPHID() => $description)); 136 + $mock_image->setSequence($sequence); 137 + } 138 + $sequence++; 139 + } 140 + foreach ($mock_images as $file_phid => $mock_image) { 141 + if (!isset($files[$file_phid])) { 142 + // this is a delete 143 + $xactions[] = id(new PholioTransaction()) 144 + ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) 145 + ->setNewValue( 146 + array('-' => array($mock_image))); 147 + } 148 + } 149 + 119 150 $mock->openTransaction(); 120 151 $editor = id(new PholioMockEditor()) 121 152 ->setContentSourceFromRequest($request) ··· 124 155 125 156 $xactions = $editor->applyTransactions($mock, $xactions); 126 157 127 - if ($images) { 128 - foreach ($images as $image) { 129 - // TODO: Move into editor? 130 - $image->setMockID($mock->getID()); 131 - $image->save(); 132 - } 133 - } 134 158 $mock->saveTransaction(); 135 159 136 160 return id(new AphrontRedirectResponse()) ··· 170 194 171 195 $cc_tokens = mpull($handles, 'getFullName', 'getPHID'); 172 196 173 - $images_controller = ''; 174 - if ($is_new) { 175 - $images_controller = 176 - id(new AphrontFormDragAndDropUploadControl($request)) 177 - ->setValue($files) 178 - ->setName('file_phids') 179 - ->setLabel(pht('Images')) 180 - ->setActivatedClass('aphront-textarea-drag-and-drop') 181 - ->setError($e_images); 182 - } 197 + $images_controller = 198 + id(new PholioDragAndDropUploadControl($request)) 199 + ->setUploadURI($this->getApplicationURI('image/upload/')) 200 + ->setValue($files) 201 + ->setImages($mock_images) 202 + ->setName('file_phids') 203 + ->setLabel(pht('Images')) 204 + ->setActivatedClass('aphront-textarea-drag-and-drop') 205 + ->setError($e_images); 183 206 207 + require_celerity_resource('pholio-edit-css'); 184 208 $form = id(new AphrontFormView()) 185 209 ->setUser($user) 186 210 ->setFlexible(true) ··· 196 220 ->setValue($v_desc) 197 221 ->setLabel(pht('Description')) 198 222 ->setUser($user)) 199 - ->appendChild($images_controller) 200 223 ->appendChild( 201 224 id(new AphrontFormTokenizerControl()) 202 225 ->setLabel(pht('CC')) ··· 211 234 ->setPolicyObject($mock) 212 235 ->setPolicies($policies) 213 236 ->setName('can_view')) 237 + ->appendChild($images_controller) 214 238 ->appendChild($submit); 215 239 216 240 $crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
+154 -2
src/applications/pholio/editor/PholioMockEditor.php
··· 5 5 */ 6 6 final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { 7 7 8 + private $newImages = array(); 9 + private function setNewImages(array $new_images) { 10 + assert_instances_of($new_images, 'PholioImage'); 11 + $this->newImages = $new_images; 12 + return $this; 13 + } 14 + private function getNewImages() { 15 + return $this->newImages; 16 + } 17 + 8 18 public function getTransactionTypes() { 9 19 $types = parent::getTransactionTypes(); 10 20 ··· 15 25 $types[] = PholioTransactionType::TYPE_NAME; 16 26 $types[] = PholioTransactionType::TYPE_DESCRIPTION; 17 27 $types[] = PholioTransactionType::TYPE_INLINE; 28 + 29 + $types[] = PholioTransactionType::TYPE_IMAGE_FILE; 30 + $types[] = PholioTransactionType::TYPE_IMAGE_NAME; 31 + $types[] = PholioTransactionType::TYPE_IMAGE_DESCRIPTION; 32 + 18 33 return $types; 19 34 } 20 35 ··· 27 42 return $object->getName(); 28 43 case PholioTransactionType::TYPE_DESCRIPTION: 29 44 return $object->getDescription(); 45 + case PholioTransactionType::TYPE_IMAGE_FILE: 46 + $images = $object->getImages(); 47 + return mpull($images, 'getPHID'); 48 + case PholioTransactionType::TYPE_IMAGE_NAME: 49 + $name = null; 50 + $phid = null; 51 + $image = $this->getImageForXaction($object, $xaction); 52 + if ($image && $image->getName()) { 53 + $name = $image->getName(); 54 + $phid = $image->getPHID(); 55 + } 56 + return array ($phid => $name); 57 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 58 + $description = null; 59 + $phid = null; 60 + $image = $this->getImageForXaction($object, $xaction); 61 + if ($image && $image->getDescription()) { 62 + $description = $image->getDescription(); 63 + $phid = $image->getPHID(); 64 + } 65 + return array($phid => $description); 30 66 } 31 67 } 32 68 ··· 37 73 switch ($xaction->getTransactionType()) { 38 74 case PholioTransactionType::TYPE_NAME: 39 75 case PholioTransactionType::TYPE_DESCRIPTION: 76 + case PholioTransactionType::TYPE_IMAGE_NAME: 77 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 40 78 return $xaction->getNewValue(); 79 + case PholioTransactionType::TYPE_IMAGE_FILE: 80 + $raw_new_value = $xaction->getNewValue(); 81 + $new_value = array(); 82 + foreach ($raw_new_value as $key => $images) { 83 + $new_value[$key] = mpull($images, 'getPHID'); 84 + } 85 + $xaction->setNewValue($new_value); 86 + return $this->getPHIDTransactionNewValue($xaction); 41 87 } 42 88 } 43 89 ··· 53 99 return parent::transactionHasEffect($object, $xaction); 54 100 } 55 101 102 + protected function shouldApplyInitialEffects( 103 + PhabricatorLiskDAO $object, 104 + array $xactions) { 105 + 106 + foreach ($xactions as $xaction) { 107 + switch ($xaction->getTransactionType()) { 108 + case PholioTransactionType::TYPE_IMAGE_FILE: 109 + return true; 110 + break; 111 + } 112 + } 113 + return false; 114 + } 115 + 116 + protected function applyInitialEffects( 117 + PhabricatorLiskDAO $object, 118 + array $xactions) { 119 + 120 + $new_images = array(); 121 + foreach ($xactions as $xaction) { 122 + switch ($xaction->getTransactionType()) { 123 + case PholioTransactionType::TYPE_IMAGE_FILE: 124 + $new_value = $xaction->getNewValue(); 125 + foreach ($new_value as $key => $txn_images) { 126 + if ($key != '+') { 127 + continue; 128 + } 129 + foreach ($txn_images as $image) { 130 + $image->save(); 131 + $new_images[] = $image; 132 + } 133 + } 134 + break; 135 + } 136 + } 137 + $this->setNewImages($new_images); 138 + } 139 + 56 140 protected function applyCustomInternalTransaction( 57 141 PhabricatorLiskDAO $object, 58 142 PhabricatorApplicationTransaction $xaction) { ··· 68 152 $object->setDescription($xaction->getNewValue()); 69 153 break; 70 154 } 155 + } 156 + 157 + private function getImageForXaction( 158 + PholioMock $mock, 159 + PhabricatorApplicationTransaction $xaction) { 160 + $raw_new_value = $xaction->getNewValue(); 161 + $image_phid = key($raw_new_value); 162 + $images = $mock->getImages(); 163 + foreach ($images as $image) { 164 + if ($image->getPHID() == $image_phid) { 165 + return $image; 166 + } 167 + } 168 + return null; 71 169 } 72 170 73 171 protected function applyCustomExternalTransaction( 74 172 PhabricatorLiskDAO $object, 75 173 PhabricatorApplicationTransaction $xaction) { 76 - return; 174 + 175 + switch ($xaction->getTransactionType()) { 176 + case PholioTransactionType::TYPE_IMAGE_FILE: 177 + $old_map = array_fuse($xaction->getOldValue()); 178 + $new_map = array_fuse($xaction->getNewValue()); 179 + 180 + $obsolete_map = array_diff_key($old_map, $new_map); 181 + $images = $object->getImages(); 182 + foreach ($images as $seq => $image) { 183 + if (isset($obsolete_map[$image->getPHID()])) { 184 + $image->setIsObsolete(1); 185 + $image->save(); 186 + unset($images[$seq]); 187 + } 188 + } 189 + $object->attachImages($images); 190 + break; 191 + case PholioTransactionType::TYPE_IMAGE_NAME: 192 + $image = $this->getImageForXaction($object, $xaction); 193 + $value = (string) head($xaction->getNewValue()); 194 + $image->setName($value); 195 + $image->save(); 196 + break; 197 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 198 + $image = $this->getImageForXaction($object, $xaction); 199 + $value = (string) head($xaction->getNewValue()); 200 + $image->setDescription($value); 201 + $image->save(); 202 + break; 203 + } 204 + } 205 + 206 + protected function applyFinalEffects( 207 + PhabricatorLiskDAO $object, 208 + array $xactions) { 209 + 210 + $images = $this->getNewImages(); 211 + foreach ($images as $image) { 212 + $image->setMockID($object->getID()); 213 + $image->save(); 214 + } 77 215 } 78 216 79 217 protected function mergeTransactions( ··· 85 223 case PholioTransactionType::TYPE_NAME: 86 224 case PholioTransactionType::TYPE_DESCRIPTION: 87 225 return $v; 226 + case PholioTransactionType::TYPE_IMAGE_FILE: 227 + return $this->mergePHIDOrEdgeTransactions($u, $v); 228 + case PholioTransactionType::TYPE_IMAGE_NAME: 229 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 230 + $raw_new_value_u = $u->getNewValue(); 231 + $raw_new_value_v = $v->getNewValue(); 232 + $phid_u = key($raw_new_value_u); 233 + $phid_v = key($raw_new_value_v); 234 + if ($phid_u == $phid_v) { 235 + return $v; 236 + } 237 + break; 88 238 } 89 239 90 240 return parent::mergeTransactions($u, $v); ··· 126 276 $inline_comments = array(); 127 277 128 278 foreach ($xactions as $xaction) { 279 + if ($xaction->shouldHide()) { 280 + continue; 281 + } 129 282 $comment = $xaction->getComment(); 130 283 switch ($xaction->getTransactionType()) { 131 284 case PholioTransactionType::TYPE_INLINE: ··· 198 351 199 352 return array_values(array_merge($head, $tail)); 200 353 } 201 - 202 354 203 355 protected function shouldImplyCC( 204 356 PhabricatorLiskDAO $object,
+3
src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PhabricatorPholioMockTestDataGenerator 4 7 extends PhabricatorTestDataGenerator { 5 8
+3
src/applications/pholio/mail/PholioMockMailReceiver.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioMockMailReceiver extends PhabricatorObjectMailReceiver { 4 7 5 8 public function isEnabled() {
+5 -3
src/applications/pholio/query/PholioMockQuery.php
··· 113 113 114 114 $mock_ids = mpull($mocks, 'getID'); 115 115 $all_images = id(new PholioImage())->loadAllWhere( 116 - 'mockID IN (%Ld)', 117 - $mock_ids); 116 + 'mockID IN (%Ld) AND isObsolete = %d', 117 + $mock_ids, 118 + 0); 118 119 119 120 $file_phids = mpull($all_images, 'getFilePHID'); 120 121 $all_files = mpull(id(new PhabricatorFile())->loadAllWhere( ··· 143 144 $image_groups = mgroup($all_images, 'getMockID'); 144 145 145 146 foreach ($mocks as $mock) { 146 - $mock->attachImages($image_groups[$mock->getID()]); 147 + $mock_images = $image_groups[$mock->getID()]; 148 + $mock->attachImages($mock_images); 147 149 } 148 150 } 149 151
+3
src/applications/pholio/remarkup/PholioRemarkupRule.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioRemarkupRule 4 7 extends PhabricatorRemarkupRuleObject { 5 8
+11
src/applications/pholio/storage/PholioImage.php
··· 13 13 protected $name = ''; 14 14 protected $description = ''; 15 15 protected $sequence; 16 + protected $isObsolete = 0; 16 17 17 18 private $inlineComments; 18 19 private $file; 20 + 21 + public function getConfiguration() { 22 + return array( 23 + self::CONFIG_AUX_PHID => true, 24 + ) + parent::getConfiguration(); 25 + } 26 + 27 + public function generatePHID() { 28 + return PhabricatorPHID::generateNewPHID('PIMG'); 29 + } 19 30 20 31 public function attachInlineComments(array $inline_comments) { 21 32 assert_instances_of($inline_comments, 'PholioTransactionComment');
+121
src/applications/pholio/storage/PholioTransaction.php
··· 25 25 return pht('mock'); 26 26 } 27 27 28 + public function getRequiredHandlePHIDs() { 29 + $phids = parent::getRequiredHandlePHIDs(); 30 + $phids[] = $this->getObjectPHID(); 31 + 32 + $new = $this->getNewValue(); 33 + $old = $this->getOldValue(); 34 + 35 + switch ($this->getTransactionType()) { 36 + case PholioTransactionType::TYPE_IMAGE_FILE: 37 + $phids = array_merge($phids, $new, $old); 38 + break; 39 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 40 + case PholioTransactionType::TYPE_IMAGE_NAME: 41 + $phids[] = key($new); 42 + break; 43 + } 44 + 45 + return $phids; 46 + } 47 + 28 48 public function shouldHide() { 29 49 $old = $this->getOldValue(); 30 50 ··· 32 52 case PholioTransactionType::TYPE_NAME: 33 53 case PholioTransactionType::TYPE_DESCRIPTION: 34 54 return ($old === null); 55 + case PholioTransactionType::TYPE_IMAGE_NAME: 56 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 57 + $old_value = reset($old); 58 + return ($old_value === null); 35 59 } 36 60 37 61 return parent::shouldHide(); ··· 41 65 switch ($this->getTransactionType()) { 42 66 case PholioTransactionType::TYPE_INLINE: 43 67 return 'comment'; 68 + case PholioTransactionType::TYPE_NAME: 69 + case PholioTransactionType::TYPE_DESCRIPTION: 70 + case PholioTransactionType::TYPE_IMAGE_NAME: 71 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 72 + return 'edit'; 73 + case PholioTransactionType::TYPE_IMAGE_FILE: 74 + return 'attach'; 44 75 } 76 + 45 77 return parent::getIcon(); 46 78 } 47 79 ··· 77 109 '%s added %d inline comment(s).', 78 110 $this->renderHandleLink($author_phid), 79 111 $count); 112 + break; 113 + case PholioTransactionType::TYPE_IMAGE_FILE: 114 + $add = array_diff($new, $old); 115 + $rem = array_diff($old, $new); 116 + 117 + if ($add && $rem) { 118 + return pht( 119 + '%s edited image(s), added %d: %s; removed %d: %s.', 120 + $this->renderHandleLink($author_phid), 121 + count($add), 122 + $this->renderHandleList($add), 123 + count($rem), 124 + $this->renderHandleList($rem)); 125 + } else if ($add) { 126 + return pht( 127 + '%s added %d image(s): %s.', 128 + $this->renderHandleLink($author_phid), 129 + count($add), 130 + $this->renderHandleList($add)); 131 + } else { 132 + return pht( 133 + '%s removed %d image(s): %s.', 134 + $this->renderHandleLink($author_phid), 135 + count($rem), 136 + $this->renderHandleList($rem)); 137 + } 138 + break; 139 + 140 + case PholioTransactionType::TYPE_IMAGE_NAME: 141 + return pht( 142 + '%s renamed an image (%s) from "%s" to "%s".', 143 + $this->renderHandleLink($author_phid), 144 + $this->renderHandleLink(key($new)), 145 + reset($old), 146 + reset($new)); 147 + break; 148 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 149 + return pht( 150 + '%s updated an image\'s (%s) description.', 151 + $this->renderHandleLink($author_phid), 152 + $this->renderHandleLink(key($new))); 153 + break; 80 154 } 81 155 82 156 return parent::getTitle(); ··· 118 192 $this->renderHandleLink($author_phid), 119 193 $this->renderHandleLink($object_phid)); 120 194 break; 195 + case PholioTransactionType::TYPE_IMAGE_FILE: 196 + return pht( 197 + '%s updated images of %s.', 198 + $this->renderHandleLink($author_phid), 199 + $this->renderHandleLink($object_phid)); 200 + break; 201 + case PholioTransactionType::TYPE_IMAGE_NAME: 202 + return pht( 203 + '%s updated the image names of %s.', 204 + $this->renderHandleLink($author_phid), 205 + $this->renderHandleLink($object_phid)); 206 + break; 207 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 208 + return pht( 209 + '%s updated image descriptions of %s.', 210 + $this->renderHandleLink($author_phid), 211 + $this->renderHandleLink($object_phid)); 212 + break; 121 213 } 122 214 123 215 return parent::getTitleForFeed(); ··· 126 218 public function hasChangeDetails() { 127 219 switch ($this->getTransactionType()) { 128 220 case PholioTransactionType::TYPE_DESCRIPTION: 221 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 129 222 return true; 130 223 } 131 224 return parent::hasChangeDetails(); ··· 134 227 public function renderChangeDetails(PhabricatorUser $viewer) { 135 228 $old = $this->getOldValue(); 136 229 $new = $this->getNewValue(); 230 + if ($this->getTransactionType() == 231 + PholioTransactionType::TYPE_IMAGE_DESCRIPTION) { 232 + $old = reset($old); 233 + $new = reset($new); 234 + } 137 235 138 236 $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) 139 237 ->setUser($viewer) ··· 143 241 return $view->render(); 144 242 } 145 243 244 + public function getColor() { 245 + $old = $this->getOldValue(); 246 + $new = $this->getNewValue(); 146 247 248 + switch ($this->getTransactionType()) { 249 + case PholioTransactionType::TYPE_NAME: 250 + case PholioTransactionType::TYPE_DESCRIPTION: 251 + case PholioTransactionType::TYPE_IMAGE_NAME: 252 + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: 253 + return PhabricatorTransactions::COLOR_BLUE; 254 + case PholioTransactionType::TYPE_IMAGE_FILE: 255 + $add = array_diff($new, $old); 256 + $rem = array_diff($old, $new); 257 + if ($add && $rem) { 258 + return PhabricatorTransactions::COLOR_YELLOW; 259 + } else if ($add) { 260 + return PhabricatorTransactions::COLOR_GREEN; 261 + } else { 262 + return PhabricatorTransactions::COLOR_RED; 263 + } 264 + } 265 + 266 + return parent::getColor(); 267 + } 147 268 }
+25
src/applications/pholio/view/PholioDragAndDropUploadControl.php
··· 1 + <?php 2 + 3 + /** 4 + * @group pholio 5 + */ 6 + final class PholioDragAndDropUploadControl 7 + extends AphrontAbstractFormDragAndDropUploadControl { 8 + 9 + private $images; 10 + 11 + public function setImages(array $images) { 12 + assert_instances_of($images, 'PholioImage'); 13 + $this->images = $images; 14 + return $this; 15 + } 16 + public function getImages() { 17 + return $this->images; 18 + } 19 + 20 + protected function getFileView() { 21 + return id(new PholioUploadedImageView()) 22 + ->setImages($this->getImages()); 23 + } 24 + 25 + }
+3
src/applications/pholio/view/PholioInlineCommentView.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioInlineCommentView extends AphrontView { 4 7 5 8 private $engine;
+3
src/applications/pholio/view/PholioMockEmbedView.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioMockEmbedView extends AphrontView { 4 7 5 8 private $mock;
+3
src/applications/pholio/view/PholioMockImagesView.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioMockImagesView extends AphrontView { 4 7 5 8 private $mock;
+3
src/applications/pholio/view/PholioTransactionView.php
··· 1 1 <?php 2 2 3 + /** 4 + * @group pholio 5 + */ 3 6 final class PholioTransactionView 4 7 extends PhabricatorApplicationTransactionView { 5 8
+75
src/applications/pholio/view/PholioUploadedImageView.php
··· 1 + <?php 2 + 3 + /** 4 + * @group pholio 5 + */ 6 + final class PholioUploadedImageView extends AphrontAbstractAttachedFileView { 7 + 8 + private $images = array(); 9 + 10 + public function setImages(array $images) { 11 + assert_instances_of($images, 'PholioImage'); 12 + $this->images = $images; 13 + return $this; 14 + } 15 + public function getImages() { 16 + return $this->images; 17 + } 18 + public function getImage($phid) { 19 + $images = $this->getImages(); 20 + return idx($images, $phid, new PholioImage()); 21 + } 22 + 23 + public function render() { 24 + require_celerity_resource('pholio-edit-css'); 25 + 26 + $file = $this->getFile(); 27 + $phid = $file->getPHID(); 28 + $image = $this->getImage($phid); 29 + 30 + $thumb = phutil_tag( 31 + 'img', 32 + array( 33 + 'src' => $file->getThumb280x210URI(), 34 + 'width' => 280, 35 + 'height' => 210, 36 + )); 37 + 38 + $file_link = $this->getName(); 39 + if (!$image->getName()) { 40 + $image->setName($this->getFile()->getName()); 41 + } 42 + $remove = $this->getRemoveElement(); 43 + 44 + $title = id(new AphrontFormTextControl()) 45 + ->setName('title_'.$phid) 46 + ->setValue($image->getName()) 47 + ->setLabel(pht('Title')); 48 + 49 + $description = id(new AphrontFormTextAreaControl()) 50 + ->setName('description_'.$phid) 51 + ->setValue($image->getDescription()) 52 + ->setLabel(pht('Description')); 53 + 54 + return hsprintf( 55 + '<div class="pholio-uploaded-image"> 56 + <div class="thumb-box"> 57 + <div class="title"> 58 + <div class="text">%s</div> 59 + <div class="remove">%s</div> 60 + </div> 61 + <div class="thumb">%s</div> 62 + </div> 63 + <div class="image-data"> 64 + <div class="title">%s</title> 65 + <div class="description">%s</description> 66 + </div>', 67 + $file_link, 68 + $remove, 69 + $thumb, 70 + $title, 71 + $description); 72 + 73 + } 74 + 75 + }
+44 -13
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 179 179 return ($xaction->getOldValue() !== $xaction->getNewValue()); 180 180 } 181 181 182 + protected function shouldApplyInitialEffects( 183 + PhabricatorLiskDAO $object, 184 + array $xactions) { 185 + return false; 186 + 187 + } 188 + 189 + protected function applyInitialEffects( 190 + PhabricatorLiskDAO $object, 191 + array $xactions) { 192 + throw new Exception('Not implemented.'); 193 + } 194 + 182 195 private function applyInternalEffects( 183 196 PhabricatorLiskDAO $object, 184 197 PhabricatorApplicationTransaction $xaction) { ··· 338 351 339 352 $is_preview = $this->getIsPreview(); 340 353 $read_locking = false; 354 + $transaction_open = false; 341 355 342 - if (!$is_preview && $object->getID()) { 343 - foreach ($xactions as $xaction) { 356 + if (!$is_preview) { 357 + if ($object->getID()) { 358 + foreach ($xactions as $xaction) { 344 359 345 - // If any of the transactions require a read lock, hold one and reload 346 - // the object. We need to do this fairly early so that the call to 347 - // `adjustTransactionValues()` (which populates old values) is based 348 - // on the synchronized state of the object, which may differ from the 349 - // state when it was originally loaded. 360 + // If any of the transactions require a read lock, hold one and 361 + // reload the object. We need to do this fairly early so that the 362 + // call to `adjustTransactionValues()` (which populates old values) 363 + // is based on the synchronized state of the object, which may differ 364 + // from the state when it was originally loaded. 350 365 351 - if ($this->shouldReadLock($object, $xaction)) { 366 + if ($this->shouldReadLock($object, $xaction)) { 367 + $object->openTransaction(); 368 + $object->beginReadLocking(); 369 + $transaction_open = true; 370 + $read_locking = true; 371 + $object->reload(); 372 + break; 373 + } 374 + } 375 + } 376 + 377 + if ($this->shouldApplyInitialEffects($object, $xactions)) { 378 + if (!$transaction_open) { 352 379 $object->openTransaction(); 353 - $object->beginReadLocking(); 354 - $read_locking = true; 355 - $object->reload(); 356 - break; 380 + $transaction_open = true; 357 381 } 358 382 } 383 + } 384 + 385 + if ($this->shouldApplyInitialEffects($object, $xactions)) { 386 + $this->applyInitialEffects($object, $xactions); 359 387 } 360 388 361 389 foreach ($xactions as $xaction) { ··· 368 396 if ($read_locking) { 369 397 $object->endReadLocking(); 370 398 $read_locking = false; 399 + } 400 + if ($transaction_open) { 371 401 $object->killTransaction(); 402 + $transaction_open = false; 372 403 } 373 404 return array(); 374 405 } ··· 384 415 ->setActor($actor) 385 416 ->setContentSource($this->getContentSource()); 386 417 387 - if (!$read_locking) { 418 + if (!$transaction_open) { 388 419 $object->openTransaction(); 389 420 } 390 421
+14
src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php
··· 222 222 ), 223 223 ), 224 224 225 + '%s added %d image(s): %s.' => array( 226 + array( 227 + '%s added an image: %3$s.', 228 + '%s added images: %3$s.', 229 + ), 230 + ), 231 + 232 + '%s removed %d image(s): %s.' => array( 233 + array( 234 + '%s removed an image: %3$s.', 235 + '%s removed images: %3$s.', 236 + ), 237 + ), 238 + 225 239 '%d people(s)' => array( 226 240 array( 227 241 '%d person',
+12
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1454 1454 'type' => 'sql', 1455 1455 'name' => $this->getPatchPath('20130715.voteedges.sql'), 1456 1456 ), 1457 + '20130711.pholioimageobsolete.sql' => array( 1458 + 'type' => 'sql', 1459 + 'name' => $this->getPatchPath('20130711.pholioimageobsolete.sql'), 1460 + ), 1461 + '20130711.pholioimageobsolete.php' => array( 1462 + 'type' => 'php', 1463 + 'name' => $this->getPatchPath('20130711.pholioimageobsolete.php'), 1464 + ), 1465 + '20130711.pholioimageobsolete2.sql' => array( 1466 + 'type' => 'sql', 1467 + 'name' => $this->getPatchPath('20130711.pholioimageobsolete2.sql'), 1468 + ), 1457 1469 ); 1458 1470 } 1459 1471 }
+41
src/view/control/AphrontAbstractAttachedFileView.php
··· 1 + <?php 2 + 3 + abstract class AphrontAbstractAttachedFileView extends AphrontView { 4 + 5 + private $file; 6 + 7 + final public function setFile(PhabricatorFile $file) { 8 + $this->file = $file; 9 + return $this; 10 + } 11 + 12 + final protected function getFile() { 13 + return $this->file; 14 + } 15 + 16 + final protected function getName() { 17 + $file = $this->getFile(); 18 + return phutil_tag( 19 + 'a', 20 + array( 21 + 'href' => $file->getViewURI(), 22 + 'target' => '_blank', 23 + ), 24 + $file->getName()); 25 + } 26 + 27 + final protected function getRemoveElement() { 28 + $file = $this->getFile(); 29 + return javelin_tag( 30 + 'a', 31 + array( 32 + 'class' => 'button grey', 33 + 'sigil' => 'aphront-attached-file-view-remove', 34 + // NOTE: Using 'ref' here instead of 'meta' because the file upload 35 + // endpoint doesn't receive request metadata and thus can't generate 36 + // a valid response with node metadata. 37 + 'ref' => $file->getPHID(), 38 + ), 39 + "\xE2\x9C\x96"); // "Heavy Multiplication X" 40 + } 41 + }
+4 -27
src/view/control/AphrontAttachedFileView.php
··· 1 1 <?php 2 2 3 - final class AphrontAttachedFileView extends AphrontView { 4 - 5 - private $file; 6 - 7 - public function setFile(PhabricatorFile $file) { 8 - $this->file = $file; 9 - return $this; 10 - } 3 + final class AphrontAttachedFileView extends AphrontAbstractAttachedFileView { 11 4 12 5 public function render() { 13 6 require_celerity_resource('aphront-attached-file-view-css'); 14 7 15 - $file = $this->file; 8 + $file = $this->getFile(); 16 9 $phid = $file->getPHID(); 17 10 18 11 $thumb = phutil_tag( ··· 23 16 'height' => 45, 24 17 )); 25 18 26 - $name = phutil_tag( 27 - 'a', 28 - array( 29 - 'href' => $file->getViewURI(), 30 - 'target' => '_blank', 31 - ), 32 - $file->getName()); 19 + $name = $this->getName(); 33 20 $size = number_format($file->getByteSize()).' ' .pht('bytes'); 34 21 35 - $remove = javelin_tag( 36 - 'a', 37 - array( 38 - 'class' => 'button grey', 39 - 'sigil' => 'aphront-attached-file-view-remove', 40 - // NOTE: Using 'ref' here instead of 'meta' because the file upload 41 - // endpoint doesn't receive request metadata and thus can't generate 42 - // a valid response with node metadata. 43 - 'ref' => $file->getPHID(), 44 - ), 45 - "\xE2\x9C\x96"); // "Heavy Multiplication X" 22 + $remove = $this->getRemoveElement(); 46 23 47 24 return hsprintf( 48 25 '<table class="aphront-attached-file-view">
+73
src/view/form/control/AphrontAbstractFormDragAndDropUploadControl.php
··· 1 + <?php 2 + 3 + abstract class AphrontAbstractFormDragAndDropUploadControl 4 + extends AphrontFormControl { 5 + 6 + private $activatedClass; 7 + private $uploadURI = '/file/dropupload/'; 8 + 9 + public function __construct() { 10 + $this->setControlID(celerity_generate_unique_node_id()); 11 + $this->setControlStyle('display: none;'); 12 + } 13 + 14 + protected function getCustomControlClass() { 15 + return 'aphront-form-drag-and-drop-upload'; 16 + } 17 + 18 + public function setUploadURI($upload_uri) { 19 + $this->uploadURI = $upload_uri; 20 + return $this; 21 + } 22 + 23 + public function getUploadURI() { 24 + return $this->uploadURI; 25 + } 26 + 27 + public function setActivatedClass($class) { 28 + $this->activatedClass = $class; 29 + return $this; 30 + } 31 + 32 + protected function getFileView() { 33 + return new AphrontAttachedFileView(); 34 + } 35 + 36 + protected function renderInput() { 37 + require_celerity_resource('aphront-attached-file-view-css'); 38 + $list_id = celerity_generate_unique_node_id(); 39 + 40 + $files = $this->getValue(); 41 + $value = array(); 42 + if ($files) { 43 + foreach ($files as $file) { 44 + $view = $this->getFileView(); 45 + $view->setFile($file); 46 + $value[$file->getPHID()] = array( 47 + 'phid' => $file->getPHID(), 48 + 'html' => $view->render(), 49 + ); 50 + } 51 + } 52 + 53 + Javelin::initBehavior( 54 + 'aphront-drag-and-drop', 55 + array( 56 + 'control' => $this->getControlID(), 57 + 'name' => $this->getName(), 58 + 'value' => nonempty($value, null), 59 + 'list' => $list_id, 60 + 'uri' => $this->getUploadURI(), 61 + 'activatedClass' => $this->activatedClass, 62 + )); 63 + 64 + return phutil_tag( 65 + 'div', 66 + array( 67 + 'id' => $list_id, 68 + 'class' => 'aphront-form-drag-and-drop-file-list', 69 + ), 70 + ''); 71 + } 72 + 73 + }
+2 -54
src/view/form/control/AphrontFormDragAndDropUploadControl.php
··· 1 1 <?php 2 2 3 - final class AphrontFormDragAndDropUploadControl extends AphrontFormControl { 4 - 5 - private $activatedClass; 6 - 7 - public function __construct() { 8 - $this->setControlID(celerity_generate_unique_node_id()); 9 - $this->setControlStyle('display: none;'); 10 - } 11 - 12 - protected function getCustomControlClass() { 13 - return 'aphront-form-drag-and-drop-upload'; 14 - } 15 - 16 - public function setActivatedClass($class) { 17 - $this->activatedClass = $class; 18 - return $this; 19 - } 20 - 21 - protected function renderInput() { 22 - require_celerity_resource('aphront-attached-file-view-css'); 23 - $list_id = celerity_generate_unique_node_id(); 24 - 25 - $files = $this->getValue(); 26 - $value = array(); 27 - if ($files) { 28 - foreach ($files as $file) { 29 - $view = new AphrontAttachedFileView(); 30 - $view->setFile($file); 31 - $value[$file->getPHID()] = array( 32 - 'phid' => $file->getPHID(), 33 - 'html' => $view->render(), 34 - ); 35 - } 36 - } 37 - 38 - Javelin::initBehavior( 39 - 'aphront-drag-and-drop', 40 - array( 41 - 'control' => $this->getControlID(), 42 - 'name' => $this->getName(), 43 - 'value' => nonempty($value, null), 44 - 'list' => $list_id, 45 - 'uri' => '/file/dropupload/', 46 - 'activatedClass' => $this->activatedClass, 47 - )); 48 - 49 - return phutil_tag( 50 - 'div', 51 - array( 52 - 'id' => $list_id, 53 - 'class' => 'aphront-form-drag-and-drop-file-list', 54 - ), 55 - ''); 56 - } 3 + final class AphrontFormDragAndDropUploadControl 4 + extends AphrontAbstractFormDragAndDropUploadControl { 57 5 58 6 }
+81
webroot/rsrc/css/application/pholio/pholio-edit.css
··· 1 + /** 2 + * @provides pholio-edit-css 3 + */ 4 + 5 + .pholio-uploaded-image { 6 + float: left; 7 + margin: 0px 0px 12px 0px; 8 + } 9 + 10 + .pholio-uploaded-image .thumb-box { 11 + border: 1px solid #D5D9DF; 12 + border-radius: 3px; 13 + min-width: 280px; 14 + width: 48%; 15 + float: left; 16 + } 17 + 18 + .pholio-uploaded-image .image-data { 19 + width: 48%; 20 + float: right; 21 + } 22 + 23 + .pholio-uploaded-image .thumb-box .title { 24 + width: 100%; 25 + float: left; 26 + background: #EDF0F4; 27 + border-top-left-radius: 3px; 28 + border-top-right-radius: 3px; 29 + border-bottom: 1px solid #D5D9DF; 30 + } 31 + 32 + .pholio-uploaded-image .thumb-box .thumb { 33 + float: left; 34 + background: white; 35 + padding: 12px 0px 0px 0px; 36 + width: 100%; 37 + } 38 + 39 + .pholio-uploaded-image .thumb-box .title .text { 40 + width: 220px; 41 + float: left; 42 + text-overflow: ellipsis; 43 + overflow: hidden; 44 + white-space: nowrap; 45 + padding: 6px 0px 0px 8px; 46 + } 47 + 48 + .pholio-uploaded-image .thumb-box .title .remove { 49 + float: right; 50 + } 51 + 52 + .pholio-uploaded-image .thumb-box .title .remove .button { 53 + background: #EDF0F4; 54 + border: none; 55 + } 56 + 57 + .pholio-uploaded-image .thumb-box .title .remove .button:hover { 58 + box-shadow: inset 0 0 2px rgba(0,0,0,.2); 59 + } 60 + 61 + .pholio-uploaded-image .image-data .aphront-form-control { 62 + padding: 0px 0px 4px 4px; 63 + } 64 + .pholio-uploaded-image .image-data .aphront-form-label { 65 + text-align: left; 66 + margin: 0px 0px 5px 0px; 67 + } 68 + .pholio-uploaded-image .image-data .aphront-form-input { 69 + margin: 0; 70 + width: 100%; 71 + } 72 + 73 + .aphront-form-input .aphront-form-drag-and-drop-file-list { 74 + float: left; 75 + width: 100%; 76 + } 77 + .aphront-form-input .aphront-form-drag-and-drop-file-list 78 + .drag-and-drop-file-target { 79 + padding: 20px; 80 + clear: both; 81 + }
+1 -1
webroot/rsrc/js/core/behavior-drag-and-drop.js
··· 53 53 pending--; 54 54 redraw(true); 55 55 56 - // This redraws the instructions. 56 + // This redraws the instructions and clears "Upload complete!" 57 57 setTimeout(redraw, 1000); 58 58 }); 59 59