1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-13 10:22:42 +01:00
phorge-phorge/src/applications/feed/PhabricatorFeedStoryPublisher.php
John-Ashton Allen 3a6ee79190 Adds base notification application
Summary: First diff in a series of diffs to add notifications to Phabricator. This is the notification application ONLY. This commit does not include the changes to other applications that makes them add notifications. As such, no notifications will be generated beyond the initial database import.

Test Plan: This is part of the notifications architecture which has been running on http://theoryphabricator.com for the past several months.

Reviewers: epriestley, btrahan, ddfisher

Reviewed By: epriestley

CC: allenjohnashton, keebuhm, aran, Korvin, jungejason, nh

Maniphest Tasks: T974

Differential Revision: https://secure.phabricator.com/D2571
2012-06-08 06:32:02 -07:00

178 lines
5.2 KiB
PHP

<?php
/*
* Copyright 2012 Facebook, Inc.
*
* 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;
private $primaryObjectPHID;
private $subscribedPHIDs;
public function setRelatedPHIDs(array $phids) {
$this->relatedPHIDs = $phids;
return $this;
}
public function setSubscribedPHIDs(array $phids) {
$this->subscribedPHIDs = $phids;
return $this;
}
public function setPrimaryObjectPHID($phid) {
$this->primaryObjectPHID = $phid;
return $this;
}
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');
foreach (array_unique($this->relatedPHIDs) as $phid) {
$sql[] = qsprintf(
$conn,
'(%s, %s)',
$phid,
$chrono_key);
}
queryfx(
$conn,
'INSERT INTO %T (objectPHID, chronologicalKey) VALUES %Q',
$ref->getTableName(),
implode(', ', $sql));
if (PhabricatorEnv::getEnvConfig('notification.enabled')) {
$this->insertNotifications($chrono_key);
}
return $story;
}
private function insertNotifications($chrono_key) {
if (!$this->primaryObjectPHID) {
throw
new Exception("Call setPrimaryObjectPHID() before Publishing!");
}
if ($this->subscribedPHIDs) {
$notif = new PhabricatorFeedStoryNotification();
$sql = array();
$conn = $notif->establishConnection('w');
foreach (array_unique($this->subscribedPHIDs) as $user_phid) {
$sql[] = qsprintf(
$conn,
'(%s, %s, %s, %d)',
$this->primaryObjectPHID,
$user_phid,
$chrono_key,
0);
}
queryfx(
$conn,
'INSERT INTO %T
(primaryObjectPHID, userPHID, chronologicalKey, hasViewed)
VALUES %Q',
$notif->getTableName(),
implode(', ', $sql));
}
}
/**
* 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)));
// 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;
}
}
}