diff --git a/resources/builtin/merchant.png b/resources/builtin/merchant.png new file mode 100644 index 0000000000..c8381eb00f Binary files /dev/null and b/resources/builtin/merchant.png differ diff --git a/resources/sql/autopatches/20161025.phortune.merchant.image.1.sql b/resources/sql/autopatches/20161025.phortune.merchant.image.1.sql new file mode 100644 index 0000000000..995a4a8fd6 --- /dev/null +++ b/resources/sql/autopatches/20161025.phortune.merchant.image.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant + ADD profileImagePHID VARBINARY(64); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cb4a6c15df..dcb61c6342 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4185,6 +4185,7 @@ phutil_register_library_map(array( 'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', + 'PhortuneMerchantPictureController' => 'applications/phortune/controller/PhortuneMerchantPictureController.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', 'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php', 'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php', @@ -9434,6 +9435,7 @@ phutil_register_library_map(array( 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController', 'PhortuneMerchantListController' => 'PhortuneMerchantController', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', + 'PhortuneMerchantPictureController' => 'PhortuneMerchantController', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneMerchantTransaction' => 'PhabricatorApplicationTransaction', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 470ceb10ab..313328e41e 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -81,6 +81,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { ), 'merchant/' => array( '(?:query/(?P[^/]+)/)?' => 'PhortuneMerchantListController', + 'picture/(?:(?P\d+)/)?' => 'PhortuneMerchantPictureController', 'edit/(?:(?P\d+)/)?' => 'PhortuneMerchantEditController', 'orders/(?P\d+)/(?:query/(?P[^/]+)/)?' => 'PhortuneCartListController', diff --git a/src/applications/phortune/controller/PhortuneMerchantPictureController.php b/src/applications/phortune/controller/PhortuneMerchantPictureController.php new file mode 100644 index 0000000000..74d7ea061c --- /dev/null +++ b/src/applications/phortune/controller/PhortuneMerchantPictureController.php @@ -0,0 +1,234 @@ +getViewer(); + $id = $request->getURIData('id'); + + $merchant = id(new PhortuneMerchantQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needProfileImage(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$merchant) { + return new Aphront404Response(); + } + + $uri = $merchant->getViewURI(); + + $supported_formats = PhabricatorFile::getTransformableImageFormats(); + $e_file = true; + $errors = array(); + + if ($request->isFormPost()) { + $phid = $request->getStr('phid'); + $is_default = false; + if ($phid == PhabricatorPHIDConstants::PHID_VOID) { + $phid = null; + $is_default = true; + } else if ($phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + } else { + if ($request->getFileExists('picture')) { + $file = PhabricatorFile::newFromPHPUpload( + $_FILES['picture'], + array( + 'authorPHID' => $viewer->getPHID(), + 'canCDN' => true, + )); + } else { + $e_file = pht('Required'); + $errors[] = pht( + 'You must choose a file when uploading a merchant logo.'); + } + } + + if (!$errors && !$is_default) { + if (!$file->isTransformableImage()) { + $e_file = pht('Not Supported'); + $errors[] = pht( + 'This server only supports these image formats: %s.', + implode(', ', $supported_formats)); + } else { + $xform = PhabricatorFileTransform::getTransformByKey( + PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); + $xformed = $xform->executeTransform($file); + } + } + + if (!$errors) { + if ($is_default) { + $new_value = null; + } else { + $xformed->attachToObject($merchant->getPHID()); + $new_value = $xformed->getPHID(); + } + + $xactions = array(); + $xactions[] = id(new PhortuneMerchantTransaction()) + ->setTransactionType(PhortuneMerchantTransaction::TYPE_PICTURE) + ->setNewValue($new_value); + + $editor = id(new PhortuneMerchantEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($merchant, $xactions); + + return id(new AphrontRedirectResponse())->setURI($uri); + } + } + + $title = pht('Edit Merchant Picture'); + + $form = id(new PHUIFormLayoutView()) + ->setUser($viewer); + + $default_image = PhabricatorFile::loadBuiltin($viewer, 'merchant.png'); + + $images = array(); + + $current = $merchant->getProfileImagePHID(); + $has_current = false; + if ($current) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current)) + ->executeOne(); + if ($file) { + if ($file->isTransformableImage()) { + $has_current = true; + $images[$current] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Current Picture'), + ); + } + } + } + + $images[PhabricatorPHIDConstants::PHID_VOID] = array( + 'uri' => $default_image->getBestURI(), + 'tip' => pht('Default Picture'), + ); + + require_celerity_resource('people-profile-css'); + Javelin::initBehavior('phabricator-tooltips', array()); + + $buttons = array(); + foreach ($images as $phid => $spec) { + $button = javelin_tag( + 'button', + array( + 'class' => 'grey profile-image-button', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $spec['tip'], + 'size' => 300, + ), + ), + phutil_tag( + 'img', + array( + 'height' => 50, + 'width' => 50, + 'src' => $spec['uri'], + ))); + + $button = array( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'phid', + 'value' => $phid, + )), + $button, + ); + + $button = phabricator_form( + $viewer, + array( + 'class' => 'profile-image-form', + 'method' => 'POST', + ), + $button); + + $buttons[] = $button; + } + + if ($has_current) { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Current Logo')) + ->setValue(array_shift($buttons))); + } + + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Use Logo')) + ->setValue($buttons)); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $upload_form = id(new AphrontFormView()) + ->setUser($viewer) + ->setEncType('multipart/form-data') + ->appendChild( + id(new AphrontFormFileControl()) + ->setName('picture') + ->setLabel(pht('Upload Logo')) + ->setError($e_file) + ->setCaption( + pht('Supported formats: %s', implode(', ', $supported_formats)))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($uri) + ->setValue(pht('Upload Logo'))); + + $upload_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Upload New Logo')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($upload_form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($merchant->getName(), $uri); + $crumbs->addTextCrumb(pht('Merchant Logo')); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Merchant Logo')) + ->setHeaderIcon('fa-camera'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + $upload_box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, + )); + + } +} diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index cc94da6f4f..6c74ce8a06 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -10,6 +10,7 @@ final class PhortuneMerchantViewController $merchant = id(new PhortuneMerchantQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->needProfileImage(true) ->executeOne(); if (!$merchant) { return new Aphront404Response(); @@ -28,7 +29,7 @@ final class PhortuneMerchantViewController ->setHeader($merchant->getName()) ->setUser($viewer) ->setPolicyObject($merchant) - ->setHeaderIcon('fa-bank'); + ->setImage($merchant->getProfileImageURI()); $providers = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) @@ -171,6 +172,14 @@ final class PhortuneMerchantViewController ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI("merchant/edit/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Logo')) + ->setIcon('fa-camera') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($this->getApplicationURI("merchant/picture/{$id}/"))); + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Orders')) diff --git a/src/applications/phortune/editor/PhortuneMerchantEditor.php b/src/applications/phortune/editor/PhortuneMerchantEditor.php index 20f329b202..2d35ddabfd 100644 --- a/src/applications/phortune/editor/PhortuneMerchantEditor.php +++ b/src/applications/phortune/editor/PhortuneMerchantEditor.php @@ -17,6 +17,7 @@ final class PhortuneMerchantEditor $types[] = PhortuneMerchantTransaction::TYPE_NAME; $types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION; $types[] = PhortuneMerchantTransaction::TYPE_CONTACTINFO; + $types[] = PhortuneMerchantTransaction::TYPE_PICTURE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDGE; @@ -33,6 +34,8 @@ final class PhortuneMerchantEditor return $object->getDescription(); case PhortuneMerchantTransaction::TYPE_CONTACTINFO: return $object->getContactInfo(); + case PhortuneMerchantTransaction::TYPE_PICTURE: + return $object->getProfileImagePHID(); } return parent::getCustomTransactionOldValue($object, $xaction); @@ -46,6 +49,7 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_CONTACTINFO: + case PhortuneMerchantTransaction::TYPE_PICTURE: return $xaction->getNewValue(); } @@ -66,6 +70,9 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_CONTACTINFO: $object->setContactInfo($xaction->getNewValue()); return; + case PhortuneMerchantTransaction::TYPE_PICTURE: + $object->setProfileImagePHID($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -79,6 +86,7 @@ final class PhortuneMerchantEditor case PhortuneMerchantTransaction::TYPE_NAME: case PhortuneMerchantTransaction::TYPE_DESCRIPTION: case PhortuneMerchantTransaction::TYPE_CONTACTINFO: + case PhortuneMerchantTransaction::TYPE_PICTURE: return; } diff --git a/src/applications/phortune/query/PhortuneMerchantQuery.php b/src/applications/phortune/query/PhortuneMerchantQuery.php index aa730393ff..c91267b73c 100644 --- a/src/applications/phortune/query/PhortuneMerchantQuery.php +++ b/src/applications/phortune/query/PhortuneMerchantQuery.php @@ -6,6 +6,7 @@ final class PhortuneMerchantQuery private $ids; private $phids; private $memberPHIDs; + private $needProfileImage; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +23,11 @@ final class PhortuneMerchantQuery return $this; } + public function needProfileImage($need) { + $this->needProfileImage = $need; + return $this; + } + protected function loadPage() { $table = new PhortuneMerchant(); $conn = $table->establishConnection('r'); @@ -50,6 +56,35 @@ final class PhortuneMerchantQuery $merchant->attachMemberPHIDs($member_phids); } + if ($this->needProfileImage) { + $default = null; + $file_phids = mpull($merchants, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + foreach ($merchants as $merchant) { + $file = idx($files, $merchant->getProfileImagePHID()); + if (!$file) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'merchant.png'); + } + $file = $default; + } + $merchant->attachProfileImageFile($file); + } + } + return $merchants; } diff --git a/src/applications/phortune/query/PhortuneMerchantSearchEngine.php b/src/applications/phortune/query/PhortuneMerchantSearchEngine.php index d37c1455b3..a818f37d28 100644 --- a/src/applications/phortune/query/PhortuneMerchantSearchEngine.php +++ b/src/applications/phortune/query/PhortuneMerchantSearchEngine.php @@ -18,7 +18,8 @@ final class PhortuneMerchantSearchEngine } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhortuneMerchantQuery()); + $query = id(new PhortuneMerchantQuery()) + ->needProfileImage(true); return $query; } @@ -74,7 +75,7 @@ final class PhortuneMerchantSearchEngine ->setHeader($merchant->getName()) ->setHref('/phortune/merchant/'.$merchant->getID().'/') ->setObject($merchant) - ->setImageIcon('fa-bank'); + ->setImageURI($merchant->getProfileImageURI()); $list->addItem($item); } diff --git a/src/applications/phortune/storage/PhortuneMerchant.php b/src/applications/phortune/storage/PhortuneMerchant.php index fef1728014..7d202eeaf0 100644 --- a/src/applications/phortune/storage/PhortuneMerchant.php +++ b/src/applications/phortune/storage/PhortuneMerchant.php @@ -9,8 +9,10 @@ final class PhortuneMerchant extends PhortuneDAO protected $viewPolicy; protected $description; protected $contactInfo; + protected $profileImagePHID; private $memberPHIDs = self::ATTACHABLE; + private $profileImageFile = self::ATTACHABLE; public static function initializeNewMerchant(PhabricatorUser $actor) { return id(new PhortuneMerchant()) @@ -25,6 +27,7 @@ final class PhortuneMerchant extends PhortuneDAO 'name' => 'text255', 'description' => 'text', 'contactInfo' => 'text', + 'profileImagePHID' => 'phid?', ), ) + parent::getConfiguration(); } @@ -43,6 +46,23 @@ final class PhortuneMerchant extends PhortuneDAO return $this; } + public function getViewURI() { + return '/phortune/merchant/'.$this->getID().'/'; + } + + public function getProfileImageURI() { + return $this->getProfileImageFile()->getBestURI(); + } + + public function attachProfileImageFile(PhabricatorFile $file) { + $this->profileImageFile = $file; + return $this; + } + + public function getProfileImageFile() { + return $this->assertAttached($this->profileImageFile); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phortune/storage/PhortuneMerchantTransaction.php b/src/applications/phortune/storage/PhortuneMerchantTransaction.php index 9c284ca7fd..48895d85ec 100644 --- a/src/applications/phortune/storage/PhortuneMerchantTransaction.php +++ b/src/applications/phortune/storage/PhortuneMerchantTransaction.php @@ -6,6 +6,7 @@ final class PhortuneMerchantTransaction const TYPE_NAME = 'merchant:name'; const TYPE_DESCRIPTION = 'merchant:description'; const TYPE_CONTACTINFO = 'merchant:contactinfo'; + const TYPE_PICTURE = 'merchant:picture'; public function getApplicationName() { return 'phortune';