2011-07-05 17:35:18 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2012-06-08 15:31:30 +02:00
|
|
|
* Copyright 2012 Facebook, Inc.
|
2011-07-05 17:35:18 +02:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
final class PhabricatorFeedStoryPublisher {
|
|
|
|
|
|
|
|
private $relatedPHIDs;
|
|
|
|
private $storyType;
|
|
|
|
private $storyData;
|
|
|
|
private $storyTime;
|
|
|
|
private $storyAuthorPHID;
|
2012-06-08 15:31:30 +02:00
|
|
|
private $primaryObjectPHID;
|
|
|
|
private $subscribedPHIDs;
|
2011-07-05 17:35:18 +02:00
|
|
|
|
|
|
|
public function setRelatedPHIDs(array $phids) {
|
|
|
|
$this->relatedPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-06-08 15:31:30 +02:00
|
|
|
public function setSubscribedPHIDs(array $phids) {
|
|
|
|
$this->subscribedPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
public function setPrimaryObjectPHID($phid) {
|
|
|
|
$this->primaryObjectPHID = $phid;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-07-05 17:35:18 +02:00
|
|
|
public function setStoryType($story_type) {
|
|
|
|
$this->storyType = $story_type;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setStoryData(array $data) {
|
|
|
|
$this->storyData = $data;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setStoryTime($time) {
|
|
|
|
$this->storyTime = $time;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setStoryAuthorPHID($phid) {
|
|
|
|
$this->storyAuthorPHID = $phid;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function publish() {
|
|
|
|
if (!$this->relatedPHIDs) {
|
|
|
|
throw new Exception("There are no PHIDs related to this story!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->storyType) {
|
|
|
|
throw new Exception("Call setStoryType() before publishing!");
|
|
|
|
}
|
|
|
|
|
|
|
|
$chrono_key = $this->generateChronologicalKey();
|
|
|
|
|
|
|
|
$story = new PhabricatorFeedStoryData();
|
|
|
|
$story->setStoryType($this->storyType);
|
|
|
|
$story->setStoryData($this->storyData);
|
|
|
|
$story->setAuthorPHID($this->storyAuthorPHID);
|
|
|
|
$story->setChronologicalKey($chrono_key);
|
|
|
|
$story->save();
|
|
|
|
|
|
|
|
$ref = new PhabricatorFeedStoryReference();
|
|
|
|
|
|
|
|
$sql = array();
|
|
|
|
$conn = $ref->establishConnection('w');
|
2011-07-10 00:44:49 +02:00
|
|
|
foreach (array_unique($this->relatedPHIDs) as $phid) {
|
2011-07-05 17:35:18 +02:00
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'(%s, %s)',
|
|
|
|
$phid,
|
|
|
|
$chrono_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
queryfx(
|
|
|
|
$conn,
|
|
|
|
'INSERT INTO %T (objectPHID, chronologicalKey) VALUES %Q',
|
|
|
|
$ref->getTableName(),
|
|
|
|
implode(', ', $sql));
|
|
|
|
|
2012-06-08 15:31:30 +02:00
|
|
|
if (PhabricatorEnv::getEnvConfig('notification.enabled')) {
|
|
|
|
$this->insertNotifications($chrono_key);
|
|
|
|
}
|
2011-07-05 17:35:18 +02:00
|
|
|
return $story;
|
|
|
|
}
|
|
|
|
|
2012-06-08 15:31:30 +02:00
|
|
|
|
|
|
|
private function insertNotifications($chrono_key) {
|
2012-06-09 04:15:42 +02:00
|
|
|
if (!$this->subscribedPHIDs) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-06-08 15:31:30 +02:00
|
|
|
if (!$this->primaryObjectPHID) {
|
2012-06-09 04:15:42 +02:00
|
|
|
throw new Exception(
|
|
|
|
"You must call setPrimaryObjectPHID() if you setSubscribedPHIDs()!");
|
2012-06-08 15:31:30 +02:00
|
|
|
}
|
2012-06-09 04:15:42 +02:00
|
|
|
|
|
|
|
$notif = new PhabricatorFeedStoryNotification();
|
|
|
|
$sql = array();
|
|
|
|
$conn = $notif->establishConnection('w');
|
|
|
|
|
|
|
|
foreach (array_unique($this->subscribedPHIDs) as $user_phid) {
|
|
|
|
$sql[] = qsprintf(
|
2012-06-08 15:31:30 +02:00
|
|
|
$conn,
|
2012-06-09 04:15:42 +02:00
|
|
|
'(%s, %s, %s, %d)',
|
|
|
|
$this->primaryObjectPHID,
|
|
|
|
$user_phid,
|
|
|
|
$chrono_key,
|
|
|
|
0);
|
2012-06-08 15:31:30 +02:00
|
|
|
}
|
|
|
|
|
2012-06-09 04:15:42 +02:00
|
|
|
queryfx(
|
|
|
|
$conn,
|
|
|
|
'INSERT INTO %T
|
|
|
|
(primaryObjectPHID, userPHID, chronologicalKey, hasViewed)
|
|
|
|
VALUES %Q',
|
|
|
|
$notif->getTableName(),
|
|
|
|
implode(', ', $sql));
|
2012-06-08 15:31:30 +02:00
|
|
|
}
|
2012-06-09 04:15:42 +02:00
|
|
|
|
|
|
|
|
2011-07-05 17:35:18 +02:00
|
|
|
/**
|
|
|
|
* We generate a unique chronological key for each story type because we want
|
|
|
|
* to be able to page through the stream with a cursor (i.e., select stories
|
|
|
|
* after ID = X) so we can efficiently perform filtering after selecting data,
|
|
|
|
* and multiple stories with the same ID make this cumbersome without putting
|
|
|
|
* a bunch of logic in the client. We could use the primary key, but that
|
|
|
|
* would prevent publishing stories which happened in the past. Since it's
|
|
|
|
* potentially useful to do that (e.g., if you're importing another data
|
|
|
|
* source) build a unique key for each story which has chronological ordering.
|
|
|
|
*
|
|
|
|
* @return string A unique, time-ordered key which identifies the story.
|
|
|
|
*/
|
|
|
|
private function generateChronologicalKey() {
|
|
|
|
// Use the epoch timestamp for the upper 32 bits of the key. Default to
|
|
|
|
// the current time if the story doesn't have an explicit timestamp.
|
|
|
|
$time = nonempty($this->storyTime, time());
|
|
|
|
|
|
|
|
// Generate a random number for the lower 32 bits of the key.
|
|
|
|
$rand = head(unpack('L', Filesystem::readRandomBytes(4)));
|
|
|
|
|
2011-09-08 23:28:17 +02:00
|
|
|
// On 32-bit machines, we have to get creative.
|
|
|
|
if (PHP_INT_SIZE < 8) {
|
|
|
|
// We're on a 32-bit machine.
|
|
|
|
if (function_exists('bcadd')) {
|
|
|
|
// Try to use the 'bc' extension.
|
|
|
|
return bcadd(bcmul($time, bcpow(2, 32)), $rand);
|
|
|
|
} else {
|
|
|
|
// Do the math in MySQL. TODO: If we formalize a bc dependency, get
|
|
|
|
// rid of this.
|
|
|
|
$conn_r = id(new PhabricatorFeedStoryData())->establishConnection('r');
|
|
|
|
$result = queryfx_one(
|
|
|
|
$conn_r,
|
|
|
|
'SELECT (%d << 32) + %d as N',
|
|
|
|
$time,
|
|
|
|
$rand);
|
|
|
|
return $result['N'];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// This is a 64 bit machine, so we can just do the math.
|
|
|
|
return ($time << 32) + $rand;
|
|
|
|
}
|
2011-07-05 17:35:18 +02:00
|
|
|
}
|
|
|
|
}
|