diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index cf193f4419..1f984529c2 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -1,187 +1,168 @@ conpherenceID = $conpherence_id; return $this; } public function getConpherenceID() { return $this->conpherenceID; } public function willProcessRequest(array $data) { $this->setConpherenceID(idx($data, 'id')); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $conpherence_id = $this->getConpherenceID(); if (!$conpherence_id) { return new Aphront404Response(); } $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withIDs(array($conpherence_id)) ->executeOne(); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $updated = false; $error_view = null; $e_file = array(); $errors = array(); if ($request->isFormPost()) { $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr() )); $editor = id(new ConpherenceEditor()) + ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSource($content_source) ->setActor($user); $action = $request->getStr('action'); switch ($action) { case 'message': $message = $request->getStr('text'); $xactions = $editor->generateTransactionsFromText( $conpherence, $message ); - $time = time(); - $conpherence->openTransaction(); - $xactions = $editor->applyTransactions($conpherence, $xactions); - $last_xaction = end($xactions); - $xaction_phid = $last_xaction->getPHID(); - $behind = ConpherenceParticipationStatus::BEHIND; - $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; - $participants = $conpherence->getParticipants(); - foreach ($participants as $phid => $participant) { - if ($phid != $user->getPHID()) { - if ($participant->getParticipationStatus() != $behind) { - $participant->setBehindTransactionPHID($xaction_phid); - } - $participant->setParticipationStatus($behind); - $participant->setDateTouched($time); - } else { - $participant->setParticipationStatus($up_to_date); - $participant->setDateTouched($time); - } - $participant->save(); - } - $updated = $conpherence->saveTransaction(); break; case 'metadata': $xactions = array(); $images = $request->getArr('image'); if ($images) { // just take the first one $file_phid = reset($images); $file = id(new PhabricatorFileQuery()) ->setViewer($user) ->withPHIDs(array($file_phid)) ->executeOne(); $okay = $file->isTransformableImage(); if ($okay) { $xformer = new PhabricatorImageTransformer(); $xformed = $xformer->executeThumbTransform( $file, $x = 50, $y = 50); $image_phid = $xformed->getPHID(); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(ConpherenceTransactionType::TYPE_PICTURE) ->setNewValue($image_phid); } else { $e_file[] = $file; $errors[] = pht('This server only supports these image formats: %s.', implode(', ', $supported_formats)); } } $title = $request->getStr('title'); if ($title != $conpherence->getTitle()) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(ConpherenceTransactionType::TYPE_TITLE) ->setNewValue($title); } - - if ($xactions) { - $conpherence->openTransaction(); - $xactions = $editor - ->setContinueOnNoEffect(true) - ->applyTransactions($conpherence, $xactions); - $updated = $conpherence->saveTransaction(); - } else if (empty($errors)) { - $errors[] = pht( - 'That was a non-update. Try cancel.' - ); - } break; default: throw new Exception('Unknown action: '.$action); break; } + if ($xactions) { + try { + $xactions = $editor->applyTransactions($conpherence, $xactions); + $updated = true; + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { + return id(new PhabricatorApplicationTransactionNoEffectResponse()) + ->setCancelURI($this->getApplicationURI($conpherence_id.'/')) + ->setException($ex); + } + } else if (empty($errors)) { + $errors[] = pht( + 'That was a non-update. Try cancel.' + ); + } } if ($updated) { return id(new AphrontRedirectResponse())->setURI( $this->getApplicationURI($conpherence_id.'/') ); } if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('Errors editing conpherence.')) ->setInsideDialogue(true) ->setErrors($errors); } $form = id(new AphrontFormLayoutView()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) ->setName('title') ->setValue($conpherence->getTitle()) ) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Image')) ->setValue(phutil_render_tag( 'img', array( 'src' => $conpherence->loadImageURI(), )) ) ) ->appendChild( id(new AphrontFormDragAndDropUploadControl()) ->setLabel(pht('Change Image')) ->setName('image') ->setValue($e_file) ->setCaption('Supported formats: '.implode(', ', $supported_formats)) ); require_celerity_resource('conpherence-update-css'); return id(new AphrontDialogResponse()) ->setDialog( id(new AphrontDialogView()) ->setUser($user) ->setTitle(pht('Update Conpherence')) ->setWidth(AphrontDialogView::WIDTH_FORM) ->setSubmitURI($this->getApplicationURI('update/'.$conpherence_id.'/')) ->addHiddenInput('action', 'metadata') ->appendChild($error_view) ->appendChild($form) ->addSubmitButton() ->addCancelButton($this->getApplicationURI($conpherence->getID().'/')) ); } } diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index d615edac50..11cdc8d586 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -1,297 +1,298 @@ conpherence = $conpherence; return $this; } public function getConpherence() { return $this->conpherence; } public function setConpherenceID($conpherence_id) { $this->conpherenceID = $conpherence_id; return $this; } public function getConpherenceID() { return $this->conpherenceID; } public function willProcessRequest(array $data) { $this->setConpherenceID(idx($data, 'id')); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $conpherence_id = $this->getConpherenceID(); if (!$conpherence_id) { return new Aphront404Response(); } if (!$request->isAjax()) { return id(new AphrontRedirectResponse()) ->setURI($this->getApplicationURI($conpherence_id.'/')); } $conpherence = id(new ConpherenceThreadQuery()) ->setViewer($user) ->withIDs(array($conpherence_id)) ->needWidgetData(true) ->executeOne(); $this->setConpherence($conpherence); $participant = $conpherence->getParticipant($user->getPHID()); $transactions = $conpherence->getTransactions(); $latest_transaction = end($transactions); $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); $participant->markUpToDate($latest_transaction); unset($write_guard); $header = $this->renderHeaderPaneContent(); $messages = $this->renderMessagePaneContent(); $widgets = $this->renderWidgetPaneContent(); $content = $header + $widgets + $messages; return id(new AphrontAjaxResponse())->setContent($content); } private function renderHeaderPaneContent() { require_celerity_resource('conpherence-header-pane-css'); $user = $this->getRequest()->getUser(); $conpherence = $this->getConpherence(); $display_data = $conpherence->getDisplayData($user); $edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/'); $header = javelin_render_tag( 'a', array( 'class' => 'edit', 'href' => $edit_href, 'sigil' => 'workflow', ), '' ). phutil_render_tag( 'div', array( 'class' => 'header-image', 'style' => 'background-image: url('.$display_data['image'].');' ), '' ). phutil_render_tag( 'div', array( 'class' => 'title', ), phutil_escape_html($display_data['title']) ). phutil_render_tag( 'div', array( 'class' => 'subtitle', ), phutil_escape_html($display_data['subtitle']) ); return array('header' => $header); } private function renderMessagePaneContent() { require_celerity_resource('conpherence-message-pane-css'); $user = $this->getRequest()->getUser(); $conpherence = $this->getConpherence(); $handles = $conpherence->getHandles(); $rendered_transactions = array(); $transactions = $conpherence->getTransactions(); foreach ($transactions as $transaction) { if ($transaction->shouldHide()) { continue; } $rendered_transactions[] = id(new ConpherenceTransactionView()) ->setUser($user) ->setConpherenceTransaction($transaction) ->setHandles($handles) ->render(); } $transactions = implode(' ', $rendered_transactions); $form = id(new AphrontFormView()) + ->setWorkflow(true) ->setAction($this->getApplicationURI('update/'.$conpherence->getID().'/')) ->setFlexible(true) ->setUser($user) ->addHiddenInput('action', 'message') ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($user) ->setName('text') ) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Pontificate')) )->render(); return array( 'messages' => $transactions, 'form' => $form ); } private function renderWidgetPaneContent() { require_celerity_resource('conpherence-widget-pane-css'); Javelin::initBehavior( 'conpherence-widget-pane', array( 'widgetRegistery' => array( 'widgets-files' => 1, 'widgets-tasks' => 1, 'widgets-calendar' => 1, ) ) ); $conpherence = $this->getConpherence(); $widgets = phutil_render_tag( 'div', array( 'class' => 'widgets-header' ), javelin_render_tag( 'a', array( 'sigil' => 'conpherence-change-widget', 'meta' => array('widget' => 'widgets-files') ), pht('Files') ).' | '. javelin_render_tag( 'a', array( 'sigil' => 'conpherence-change-widget', 'meta' => array('widget' => 'widgets-tasks') ), pht('Tasks') ).' | '. javelin_render_tag( 'a', array( 'sigil' => 'conpherence-change-widget', 'meta' => array('widget' => 'widgets-calendar') ), pht('Calendar') ) ). phutil_render_tag( 'div', array( 'class' => 'widgets-body', 'id' => 'widgets-files', 'style' => 'display: none;' ), $this->renderFilesWidgetPaneContent() ). phutil_render_tag( 'div', array( 'class' => 'widgets-body', 'id' => 'widgets-tasks', ), $this->renderTaskWidgetPaneContent() ). phutil_render_tag( 'div', array( 'class' => 'widgets-body', 'id' => 'widgets-calendar', 'style' => 'display: none;' ), $this->renderCalendarWidgetPaneContent() ); return array('widgets' => $widgets); } private function renderFilesWidgetPaneContent() { $conpherence = $this->getConpherence(); $widget_data = $conpherence->getWidgetData(); $files = $widget_data['files']; $table_data = array(); foreach ($files as $file) { $thumb = $file->getThumb60x45URI(); $table_data[] = array( phutil_render_tag( 'img', array( 'src' => $thumb ), '' ), $file->getName() ); } $header = id(new PhabricatorHeaderView()) ->setHeader(pht('Attached Files')); $table = id(new AphrontTableView($table_data)) ->setNoDataString(pht('No files attached to conpherence.')) ->setHeaders(array('', pht('Name'))) ->setColumnClasses(array('', 'wide')); return $header->render() . $table->render(); } private function renderTaskWidgetPaneContent() { $conpherence = $this->getConpherence(); $widget_data = $conpherence->getWidgetData(); $tasks = $widget_data['tasks']; $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); $handles = $conpherence->getHandles(); $content = array(); foreach ($tasks as $owner_phid => $actual_tasks) { $handle = $handles[$owner_phid]; $content[] = id(new PhabricatorHeaderView()) ->setHeader($handle->getName()) ->render(); $actual_tasks = msort($actual_tasks, 'getPriority'); $actual_tasks = array_reverse($actual_tasks); $data = array(); foreach ($actual_tasks as $task) { $data[] = array( idx($priority_map, $task->getPriority(), pht('???')), phutil_render_tag( 'a', array( 'href' => '/T'.$task->getID() ), phutil_escape_html($task->getTitle()) ) ); } $table = id(new AphrontTableView($data)) ->setNoDataString(pht('No open tasks.')) ->setHeaders(array(pht('Pri'), pht('Title'))) ->setColumnClasses(array('', 'wide')); $content[] = $table->render(); } return implode('', $content); } private function renderCalendarWidgetPaneContent() { $header = id(new PhabricatorHeaderView()) ->setHeader(pht('Calendar')); return $header->render() . 'TODO'; } } diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 9304e0f14e..a5b42b93ce 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -1,217 +1,238 @@ getFilePHIDs(); $file_phids = array_diff($file_phids, $existing_file_phids); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($this->getActor()) ->withPHIDs($file_phids) ->execute(); } $xactions = array(); if ($files) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(ConpherenceTransactionType::TYPE_FILES) ->setNewValue(array('+' => mpull($files, 'getPHID'))); } $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new ConpherenceTransactionComment()) ->setContent($text) ->setConpherencePHID($conpherence->getPHID()) ); return $xactions; } public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = ConpherenceTransactionType::TYPE_TITLE; $types[] = ConpherenceTransactionType::TYPE_PICTURE; $types[] = ConpherenceTransactionType::TYPE_PARTICIPANTS; $types[] = ConpherenceTransactionType::TYPE_FILES; return $types; } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case ConpherenceTransactionType::TYPE_TITLE: return $object->getTitle(); case ConpherenceTransactionType::TYPE_PICTURE: return $object->getImagePHID(); case ConpherenceTransactionType::TYPE_PARTICIPANTS: return $object->getParticipantPHIDs(); case ConpherenceTransactionType::TYPE_FILES: return $object->getFilePHIDs(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case ConpherenceTransactionType::TYPE_TITLE: case ConpherenceTransactionType::TYPE_PICTURE: return $xaction->getNewValue(); case ConpherenceTransactionType::TYPE_PARTICIPANTS: case ConpherenceTransactionType::TYPE_FILES: return $this->getPHIDTransactionNewValue($xaction); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case ConpherenceTransactionType::TYPE_TITLE: $object->setTitle($xaction->getNewValue()); break; case ConpherenceTransactionType::TYPE_PICTURE: $object->setImagePHID($xaction->getNewValue()); break; } } /** * For now this only supports adding more files and participants. */ protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case ConpherenceTransactionType::TYPE_FILES: $editor = id(new PhabricatorEdgeEditor()) ->setActor($this->getActor()); $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_FILE; foreach ($xaction->getNewValue() as $file_phid) { $editor->addEdge( $object->getPHID(), $edge_type, $file_phid ); } $editor->save(); + // fallthrough + case PhabricatorTransactions::TYPE_COMMENT: + $xaction_phid = $xaction->getPHID(); + $behind = ConpherenceParticipationStatus::BEHIND; + $up_to_date = ConpherenceParticipationStatus::UP_TO_DATE; + $participants = $object->getParticipants(); + $user = $this->getActor(); + $time = time(); + foreach ($participants as $phid => $participant) { + if ($phid != $user->getPHID()) { + if ($participant->getParticipationStatus() != $behind) { + $participant->setBehindTransactionPHID($xaction_phid); + } + $participant->setParticipationStatus($behind); + $participant->setDateTouched($time); + } else { + $participant->setParticipationStatus($up_to_date); + $participant->setDateTouched($time); + } + $participant->save(); + } break; case ConpherenceTransactionType::TYPE_PARTICIPANTS: foreach ($xaction->getNewValue() as $participant) { if ($participant == $this->getActor()->getPHID()) { $status = ConpherenceParticipationStatus::UP_TO_DATE; } else { $status = ConpherenceParticipationStatus::BEHIND; } id(new ConpherenceParticipant()) ->setConpherencePHID($object->getPHID()) ->setParticipantPHID($participant) ->setParticipationStatus($status) ->setDateTouched(time()) ->setBehindTransactionPHID($xaction->getPHID()) ->save(); } break; } } protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $type = $u->getTransactionType(); switch ($type) { case ConpherenceTransactionType::TYPE_TITLE: case ConpherenceTransactionType::TYPE_PICTURE: return $v; case ConpherenceTransactionType::TYPE_FILES: case ConpherenceTransactionType::TYPE_PARTICIPANTS: return $this->mergePHIDTransactions($u, $v); } return parent::mergeTransactions($u, $v); } protected function supportsMail() { return true; } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new ConpherenceReplyHandler()) ->setActor($this->getActor()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $title = $object->getTitle(); if (!$title) { $title = pht( '%s sent you a message.', $this->getActor()->getUserName() ); } $phid = $object->getPHID(); return id(new PhabricatorMetaMTAMail()) ->setSubject("E{$id}: {$title}") ->addHeader('Thread-Topic', "E{$id}: {$phid}"); } protected function getMailTo(PhabricatorLiskDAO $object) { $participants = $object->getParticipants(); return array_keys($participants); } protected function getMailCC(PhabricatorLiskDAO $object) { return array(); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); $body->addTextSection( pht('CONPHERENCE DETAIL'), PhabricatorEnv::getProductionURI('/conpherence/'.$object->getID().'/')); return $body; } protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.conpherence.subject-prefix'); } protected function supportsFeed() { return false; } protected function supportsSearch() { return false; } }