2010-09-23 23:23:56 +01:00
|
|
|
<?php
|
|
|
|
/*
|
|
|
|
* StatusNet - the distributed open-source microblogging tool
|
|
|
|
* Copyright (C) 2010, StatusNet, Inc.
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!defined('STATUSNET')) {
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* State machine for running through Yammer import.
|
|
|
|
*
|
|
|
|
* @package YammerImportPlugin
|
|
|
|
* @author Brion Vibber <brion@status.net>
|
|
|
|
*/
|
|
|
|
class YammerRunner
|
|
|
|
{
|
|
|
|
private $state;
|
|
|
|
private $client;
|
|
|
|
private $importer;
|
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
/**
|
|
|
|
* Normalize our singleton state and give us a YammerRunner object to play with!
|
|
|
|
*
|
|
|
|
* @return YammerRunner
|
|
|
|
*/
|
2010-09-24 00:40:22 +01:00
|
|
|
public static function init()
|
2010-09-23 23:23:56 +01:00
|
|
|
{
|
|
|
|
$state = Yammer_state::staticGet('id', 1);
|
|
|
|
if (!$state) {
|
2010-09-24 01:55:13 +01:00
|
|
|
$state = self::initState();
|
2010-09-23 23:23:56 +01:00
|
|
|
}
|
2010-09-24 00:40:22 +01:00
|
|
|
return new YammerRunner($state);
|
|
|
|
}
|
2010-09-23 23:23:56 +01:00
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
private static function initState()
|
|
|
|
{
|
|
|
|
$state = new Yammer_state();
|
|
|
|
$state->id = 1;
|
|
|
|
$state->state = 'init';
|
|
|
|
$state->created = common_sql_now();
|
|
|
|
$state->modified = common_sql_now();
|
|
|
|
$state->insert();
|
|
|
|
return $state;
|
|
|
|
}
|
|
|
|
|
2010-09-24 00:40:22 +01:00
|
|
|
private function __construct($state)
|
|
|
|
{
|
2010-09-23 23:23:56 +01:00
|
|
|
$this->state = $state;
|
2010-09-24 00:40:22 +01:00
|
|
|
|
2010-09-23 23:23:56 +01:00
|
|
|
$this->client = new SN_YammerClient(
|
|
|
|
common_config('yammer', 'consumer_key'),
|
|
|
|
common_config('yammer', 'consumer_secret'),
|
|
|
|
$this->state->oauth_token,
|
|
|
|
$this->state->oauth_secret);
|
2010-09-24 00:40:22 +01:00
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->importer = new YammerImporter($this->client);
|
2010-09-23 23:23:56 +01:00
|
|
|
}
|
|
|
|
|
2010-09-24 00:40:22 +01:00
|
|
|
/**
|
|
|
|
* Check which state we're in
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function state()
|
|
|
|
{
|
|
|
|
return $this->state->state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the import done, finished, complete, finito?
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function isDone()
|
|
|
|
{
|
|
|
|
$workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
|
|
|
|
return ($this->state() == 'done');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if we have work to do in iterate().
|
2010-09-24 01:55:13 +01:00
|
|
|
*
|
|
|
|
* @return boolean
|
2010-09-24 00:40:22 +01:00
|
|
|
*/
|
|
|
|
public function hasWork()
|
|
|
|
{
|
|
|
|
$workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
|
|
|
|
return in_array($this->state(), $workStates);
|
|
|
|
}
|
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
/**
|
|
|
|
* Blow away any current state!
|
|
|
|
*/
|
|
|
|
public function reset()
|
|
|
|
{
|
|
|
|
$this->state->delete();
|
|
|
|
$this->state = self::initState();
|
|
|
|
}
|
|
|
|
|
2010-09-24 00:40:22 +01:00
|
|
|
/**
|
|
|
|
* Start the authentication process! If all goes well, we'll get back a URL.
|
|
|
|
* Have the user visit that URL, log in on Yammer and verify the importer's
|
|
|
|
* permissions. They'll get back a verification code, which needs to be passed
|
|
|
|
* on to saveAuthToken().
|
|
|
|
*
|
|
|
|
* @return string URL
|
|
|
|
*/
|
|
|
|
public function requestAuth()
|
|
|
|
{
|
|
|
|
if ($this->state->state != 'init') {
|
2010-09-27 20:24:10 +01:00
|
|
|
throw new ServerException("Cannot request Yammer auth; already there!");
|
2010-09-24 00:40:22 +01:00
|
|
|
}
|
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
$data = $this->client->requestToken();
|
|
|
|
|
2010-09-24 00:40:22 +01:00
|
|
|
$old = clone($this->state);
|
|
|
|
$this->state->state = 'requesting-auth';
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->state->oauth_token = $data['oauth_token'];
|
|
|
|
$this->state->oauth_secret = $data['oauth_token_secret'];
|
|
|
|
$this->state->modified = common_sql_now();
|
2010-09-24 00:40:22 +01:00
|
|
|
$this->state->update($old);
|
|
|
|
|
2010-09-27 20:24:10 +01:00
|
|
|
return $this->getAuthUrl();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When already in requesting-auth state, grab the URL to send the user to
|
|
|
|
* to complete OAuth setup.
|
|
|
|
*
|
|
|
|
* @return string URL
|
|
|
|
*/
|
|
|
|
function getAuthUrl()
|
|
|
|
{
|
|
|
|
if ($this->state() == 'requesting-auth') {
|
|
|
|
return $this->client->authorizeUrl($this->state->oauth_token);
|
|
|
|
} else {
|
|
|
|
throw new ServerException('Cannot get Yammer auth URL when not in requesting-auth state!');
|
|
|
|
}
|
2010-09-24 00:40:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Now that the user's given us this verification code from Yammer, we can
|
|
|
|
* request a final OAuth token/secret pair which we can use to access the
|
|
|
|
* API.
|
|
|
|
*
|
|
|
|
* After success here, we'll be ready to move on and run through iterate()
|
|
|
|
* until the import is complete.
|
|
|
|
*
|
|
|
|
* @param string $verifier
|
|
|
|
* @return boolean success
|
|
|
|
*/
|
|
|
|
public function saveAuthToken($verifier)
|
|
|
|
{
|
|
|
|
if ($this->state->state != 'requesting-auth') {
|
2010-09-27 20:24:10 +01:00
|
|
|
throw new ServerException("Cannot save auth token in Yammer import state {$this->state->state}");
|
2010-09-24 00:40:22 +01:00
|
|
|
}
|
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
$data = $this->client->accessToken($verifier);
|
2010-09-24 00:40:22 +01:00
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
$old = clone($this->state);
|
|
|
|
$this->state->state = 'import-users';
|
|
|
|
$this->state->oauth_token = $data['oauth_token'];
|
|
|
|
$this->state->oauth_secret = $data['oauth_token_secret'];
|
|
|
|
$this->state->modified = common_sql_now();
|
2010-09-24 00:40:22 +01:00
|
|
|
$this->state->update($old);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Once authentication is complete, we need to call iterate() a bunch of times
|
|
|
|
* until state() returns 'done'.
|
|
|
|
*
|
|
|
|
* @return boolean success
|
|
|
|
*/
|
2010-09-23 23:23:56 +01:00
|
|
|
public function iterate()
|
|
|
|
{
|
2010-09-24 01:55:13 +01:00
|
|
|
switch($this->state())
|
2010-09-23 23:23:56 +01:00
|
|
|
{
|
2010-09-24 00:40:22 +01:00
|
|
|
case 'init':
|
2010-09-23 23:23:56 +01:00
|
|
|
case 'requesting-auth':
|
|
|
|
// Neither of these should reach our background state!
|
|
|
|
common_log(LOG_ERR, "Non-background YammerImport state '$state->state' during import run!");
|
|
|
|
return false;
|
|
|
|
case 'import-users':
|
|
|
|
return $this->iterateUsers();
|
|
|
|
case 'import-groups':
|
|
|
|
return $this->iterateGroups();
|
|
|
|
case 'fetch-messages':
|
|
|
|
return $this->iterateFetchMessages();
|
|
|
|
case 'save-messages':
|
|
|
|
return $this->iterateSaveMessages();
|
|
|
|
default:
|
|
|
|
common_log(LOG_ERR, "Invalid YammerImport state '$state->state' during import run!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trundle through one 'page' return of up to 50 user accounts retrieved
|
|
|
|
* from the Yammer API, importing them as we go.
|
|
|
|
*
|
|
|
|
* When we run out of users, move on to groups.
|
|
|
|
*
|
|
|
|
* @return boolean success
|
|
|
|
*/
|
|
|
|
private function iterateUsers()
|
|
|
|
{
|
|
|
|
$old = clone($this->state);
|
|
|
|
|
|
|
|
$page = intval($this->state->users_page) + 1;
|
|
|
|
$data = $this->client->users(array('page' => $page));
|
|
|
|
|
|
|
|
if (count($data) == 0) {
|
|
|
|
common_log(LOG_INFO, "Finished importing Yammer users; moving on to groups.");
|
|
|
|
$this->state->state = 'import-groups';
|
|
|
|
} else {
|
|
|
|
foreach ($data as $item) {
|
2010-09-24 01:55:13 +01:00
|
|
|
$user = $this->importer->importUser($item);
|
2010-09-23 23:23:56 +01:00
|
|
|
common_log(LOG_INFO, "Imported Yammer user " . $item['id'] . " as $user->nickname ($user->id)");
|
|
|
|
}
|
|
|
|
$this->state->users_page = $page;
|
|
|
|
}
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->state->modified = common_sql_now();
|
2010-09-23 23:23:56 +01:00
|
|
|
$this->state->update($old);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trundle through one 'page' return of up to 20 user groups retrieved
|
|
|
|
* from the Yammer API, importing them as we go.
|
|
|
|
*
|
|
|
|
* When we run out of groups, move on to messages.
|
|
|
|
*
|
|
|
|
* @return boolean success
|
|
|
|
*/
|
|
|
|
private function iterateGroups()
|
|
|
|
{
|
|
|
|
$old = clone($this->state);
|
|
|
|
|
|
|
|
$page = intval($this->state->groups_page) + 1;
|
|
|
|
$data = $this->client->groups(array('page' => $page));
|
|
|
|
|
|
|
|
if (count($data) == 0) {
|
|
|
|
common_log(LOG_INFO, "Finished importing Yammer groups; moving on to messages.");
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->state->state = 'fetch-messages';
|
2010-09-23 23:23:56 +01:00
|
|
|
} else {
|
|
|
|
foreach ($data as $item) {
|
2010-09-24 01:55:13 +01:00
|
|
|
$group = $this->importer->importGroup($item);
|
2010-09-23 23:23:56 +01:00
|
|
|
common_log(LOG_INFO, "Imported Yammer group " . $item['id'] . " as $group->nickname ($group->id)");
|
|
|
|
}
|
|
|
|
$this->state->groups_page = $page;
|
|
|
|
}
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->state->modified = common_sql_now();
|
2010-09-23 23:23:56 +01:00
|
|
|
$this->state->update($old);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trundle through one 'page' return of up to 20 public messages retrieved
|
|
|
|
* from the Yammer API, saving them to our stub table for future import in
|
|
|
|
* correct chronological order.
|
|
|
|
*
|
|
|
|
* When we run out of messages to fetch, move on to saving the messages.
|
|
|
|
*
|
|
|
|
* @return boolean success
|
|
|
|
*/
|
|
|
|
private function iterateFetchMessages()
|
|
|
|
{
|
|
|
|
$old = clone($this->state);
|
|
|
|
|
|
|
|
$oldest = intval($this->state->messages_oldest);
|
|
|
|
if ($oldest) {
|
|
|
|
$params = array('older_than' => $oldest);
|
|
|
|
} else {
|
|
|
|
$params = array();
|
|
|
|
}
|
|
|
|
$data = $this->client->messages($params);
|
|
|
|
$messages = $data['messages'];
|
|
|
|
|
2010-09-24 01:55:13 +01:00
|
|
|
if (count($messages) == 0) {
|
2010-09-23 23:23:56 +01:00
|
|
|
common_log(LOG_INFO, "Finished fetching Yammer messages; moving on to save messages.");
|
|
|
|
$this->state->state = 'save-messages';
|
|
|
|
} else {
|
2010-09-24 01:55:13 +01:00
|
|
|
foreach ($messages as $item) {
|
2010-09-28 23:45:00 +01:00
|
|
|
$stub = Yammer_notice_stub::staticGet($item['id']);
|
|
|
|
if (!$stub) {
|
|
|
|
Yammer_notice_stub::record($item['id'], $item);
|
|
|
|
}
|
2010-09-23 23:23:56 +01:00
|
|
|
$oldest = $item['id'];
|
|
|
|
}
|
|
|
|
$this->state->messages_oldest = $oldest;
|
|
|
|
}
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->state->modified = common_sql_now();
|
2010-09-23 23:23:56 +01:00
|
|
|
$this->state->update($old);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function iterateSaveMessages()
|
|
|
|
{
|
|
|
|
$old = clone($this->state);
|
|
|
|
|
|
|
|
$newest = intval($this->state->messages_newest);
|
2010-09-24 01:55:13 +01:00
|
|
|
|
|
|
|
$stub = new Yammer_notice_stub();
|
2010-09-23 23:23:56 +01:00
|
|
|
if ($newest) {
|
2010-09-24 01:55:13 +01:00
|
|
|
$stub->whereAdd('id > ' . $newest);
|
2010-09-23 23:23:56 +01:00
|
|
|
}
|
|
|
|
$stub->limit(20);
|
2010-09-24 01:55:13 +01:00
|
|
|
$stub->orderBy('id');
|
2010-09-23 23:23:56 +01:00
|
|
|
$stub->find();
|
|
|
|
|
|
|
|
if ($stub->N == 0) {
|
|
|
|
common_log(LOG_INFO, "Finished saving Yammer messages; import complete!");
|
|
|
|
$this->state->state = 'done';
|
|
|
|
} else {
|
|
|
|
while ($stub->fetch()) {
|
2010-09-24 01:55:13 +01:00
|
|
|
$item = $stub->getData();
|
2010-09-23 23:23:56 +01:00
|
|
|
$notice = $this->importer->importNotice($item);
|
|
|
|
common_log(LOG_INFO, "Imported Yammer notice " . $item['id'] . " as $notice->id");
|
|
|
|
$newest = $item['id'];
|
|
|
|
}
|
|
|
|
$this->state->messages_newest = $newest;
|
|
|
|
}
|
2010-09-24 01:55:13 +01:00
|
|
|
$this->state->modified = common_sql_now();
|
2010-09-23 23:23:56 +01:00
|
|
|
$this->state->update($old);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-09-24 22:52:51 +01:00
|
|
|
/**
|
|
|
|
* Count the number of Yammer users we've mapped into our system!
|
|
|
|
*
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function countUsers()
|
|
|
|
{
|
|
|
|
$map = new Yammer_user();
|
|
|
|
return $map->count();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Count the number of Yammer groups we've mapped into our system!
|
|
|
|
*
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function countGroups()
|
|
|
|
{
|
|
|
|
$map = new Yammer_group();
|
|
|
|
return $map->count();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Count the number of Yammer notices we've pulled down for pending import...
|
|
|
|
*
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function countFetchedNotices()
|
|
|
|
{
|
|
|
|
$map = new Yammer_notice_stub();
|
|
|
|
return $map->count();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Count the number of Yammer notices we've mapped into our system!
|
|
|
|
*
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function countSavedNotices()
|
|
|
|
{
|
|
|
|
$map = new Yammer_notice();
|
|
|
|
return $map->count();
|
|
|
|
}
|
|
|
|
|
2010-09-27 21:34:35 +01:00
|
|
|
/**
|
|
|
|
* Start running import work in the background queues...
|
|
|
|
*/
|
|
|
|
public function startBackgroundImport()
|
|
|
|
{
|
|
|
|
$qm = QueueManager::get();
|
|
|
|
$qm->enqueue('YammerImport', 'yammer');
|
|
|
|
}
|
|
|
|
|
2010-09-28 23:45:00 +01:00
|
|
|
/**
|
|
|
|
* Record an error condition from a background run, which we should
|
|
|
|
* display in progress state for the admin.
|
|
|
|
*
|
|
|
|
* @param string $msg
|
|
|
|
*/
|
|
|
|
public function recordError($msg)
|
|
|
|
{
|
|
|
|
// HACK HACK HACK
|
|
|
|
try {
|
|
|
|
$temp = new Yammer_state();
|
|
|
|
$temp->query('ROLLBACK');
|
|
|
|
} catch (Exception $e) {
|
|
|
|
common_log(LOG_ERR, 'Exception while confirming rollback while recording error: ' . $e->getMessage());
|
|
|
|
}
|
|
|
|
$old = clone($this->state);
|
|
|
|
$this->state->last_error = $msg;
|
|
|
|
$this->state->update($old);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the error state.
|
|
|
|
*/
|
|
|
|
public function clearError()
|
|
|
|
{
|
|
|
|
$this->recordError('');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the last recorded background error message, if any.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function lastError()
|
|
|
|
{
|
|
|
|
return $this->state->last_error;
|
|
|
|
}
|
2010-09-23 23:23:56 +01:00
|
|
|
}
|