3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2010, StatusNet, Inc.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 if (!defined('STATUSNET')) {
25 * State machine for running through Yammer import.
27 * @package YammerImportPlugin
28 * @author Brion Vibber <brion@status.net>
37 * Normalize our singleton state and give us a YammerRunner object to play with!
39 * @return YammerRunner
41 public static function init()
43 $state = Yammer_state::getKV('id', 1);
45 $state = self::initState();
47 return new YammerRunner($state);
50 private static function initState()
52 $state = new Yammer_state();
54 $state->state = 'init';
55 $state->created = common_sql_now();
56 $state->modified = common_sql_now();
61 private function __construct($state)
63 $this->state = $state;
65 $this->client = new SNYammerClient(
66 common_config('yammer', 'consumer_key'),
67 common_config('yammer', 'consumer_secret'),
68 $this->state->oauth_token,
69 $this->state->oauth_secret);
71 $this->importer = new YammerImporter($this->client);
75 * Check which state we're in
79 public function state()
81 return $this->state->state;
85 * Is the import done, finished, complete, finito?
89 public function isDone()
91 $workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
92 return ($this->state() == 'done');
96 * Check if we have work to do in iterate().
100 public function hasWork()
102 $workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
103 return in_array($this->state(), $workStates);
107 * Blow away any current state!
109 public function reset()
111 $this->state->delete();
112 $this->state = self::initState();
116 * Start the authentication process! If all goes well, we'll get back a URL.
117 * Have the user visit that URL, log in on Yammer and verify the importer's
118 * permissions. They'll get back a verification code, which needs to be passed
119 * on to saveAuthToken().
123 public function requestAuth()
125 if ($this->state->state != 'init') {
126 // TRANS: Server exception thrown if a Yammer authentication request is already present.
127 throw new ServerException(_m('Cannot request Yammer auth; already there!'));
130 $data = $this->client->requestToken();
132 $old = clone($this->state);
133 $this->state->state = 'requesting-auth';
134 $this->state->oauth_token = $data['oauth_token'];
135 $this->state->oauth_secret = $data['oauth_token_secret'];
136 $this->state->modified = common_sql_now();
137 $this->state->update($old);
139 return $this->getAuthUrl();
143 * When already in requesting-auth state, grab the URL to send the user to
144 * to complete OAuth setup.
148 function getAuthUrl()
150 if ($this->state() == 'requesting-auth') {
151 return $this->client->authorizeUrl($this->state->oauth_token);
153 // TRANS: Server exception thrown when requesting a Yammer authentication URL while in an incorrect state.
154 throw new ServerException(_m('Cannot get Yammer auth URL when not in requesting-auth state!'));
159 * Now that the user's given us this verification code from Yammer, we can
160 * request a final OAuth token/secret pair which we can use to access the
163 * After success here, we'll be ready to move on and run through iterate()
164 * until the import is complete.
166 * @param string $verifier
167 * @return boolean success
169 public function saveAuthToken($verifier)
171 if ($this->state->state != 'requesting-auth') {
172 // TRANS: Server exception thrown if a Yammer authentication token could not be saved in a certain import state.
173 // TRANS: %s is the import state in the which the error occurred.
174 throw new ServerException(_m('Cannot save auth token in Yammer import state %s.',$this->state->state));
177 $data = $this->client->accessToken($verifier);
179 $old = clone($this->state);
180 $this->state->state = 'import-users';
181 $this->state->oauth_token = $data['oauth_token'];
182 $this->state->oauth_secret = $data['oauth_token_secret'];
183 $this->state->modified = common_sql_now();
184 $this->state->update($old);
190 * Once authentication is complete, we need to call iterate() a bunch of times
191 * until state() returns 'done'.
193 * @return boolean success
195 public function iterate()
197 switch($this->state())
200 case 'requesting-auth':
201 // Neither of these should reach our background state!
202 common_log(LOG_ERR, "Non-background YammerImport state '$state->state' during import run!");
205 return $this->iterateUsers();
206 case 'import-groups':
207 return $this->iterateGroups();
208 case 'fetch-messages':
209 return $this->iterateFetchMessages();
210 case 'save-messages':
211 return $this->iterateSaveMessages();
213 common_log(LOG_ERR, "Invalid YammerImport state '$state->state' during import run!");
219 * Trundle through one 'page' return of up to 50 user accounts retrieved
220 * from the Yammer API, importing them as we go.
222 * When we run out of users, move on to groups.
224 * @return boolean success
226 private function iterateUsers()
228 $old = clone($this->state);
230 $page = intval($this->state->users_page) + 1;
231 $data = $this->client->users(array('page' => $page));
233 if (count($data) == 0) {
234 common_log(LOG_INFO, "Finished importing Yammer users; moving on to groups.");
235 $this->state->state = 'import-groups';
237 foreach ($data as $item) {
238 $user = $this->importer->importUser($item);
239 common_log(LOG_INFO, "Imported Yammer user " . $item['id'] . " as $user->nickname ($user->id)");
241 $this->state->users_page = $page;
243 $this->state->modified = common_sql_now();
244 $this->state->update($old);
249 * Trundle through one 'page' return of up to 20 user groups retrieved
250 * from the Yammer API, importing them as we go.
252 * When we run out of groups, move on to messages.
254 * @return boolean success
256 private function iterateGroups()
258 $old = clone($this->state);
260 $page = intval($this->state->groups_page) + 1;
261 $data = $this->client->groups(array('page' => $page));
263 if (count($data) == 0) {
264 common_log(LOG_INFO, "Finished importing Yammer groups; moving on to messages.");
265 $this->state->state = 'fetch-messages';
267 foreach ($data as $item) {
268 $group = $this->importer->importGroup($item);
269 common_log(LOG_INFO, "Imported Yammer group " . $item['id'] . " as $group->nickname ($group->id)");
271 $this->state->groups_page = $page;
273 $this->state->modified = common_sql_now();
274 $this->state->update($old);
279 * Trundle through one 'page' return of up to 20 public messages retrieved
280 * from the Yammer API, saving them to our stub table for future import in
281 * correct chronological order.
283 * When we run out of messages to fetch, move on to saving the messages.
285 * @return boolean success
287 private function iterateFetchMessages()
289 $old = clone($this->state);
291 $oldest = intval($this->state->messages_oldest);
293 $params = array('older_than' => $oldest);
297 $data = $this->client->messages($params);
298 $messages = $data['messages'];
300 if (count($messages) == 0) {
301 common_log(LOG_INFO, "Finished fetching Yammer messages; moving on to save messages.");
302 $this->state->state = 'save-messages';
304 foreach ($messages as $item) {
305 $stub = Yammer_notice_stub::getKV($item['id']);
307 Yammer_notice_stub::record($item['id'], $item);
309 $oldest = $item['id'];
311 $this->state->messages_oldest = $oldest;
313 $this->state->modified = common_sql_now();
314 $this->state->update($old);
318 private function iterateSaveMessages()
320 $old = clone($this->state);
322 $newest = intval($this->state->messages_newest);
324 $stub = new Yammer_notice_stub();
326 $stub->whereAdd('id > ' . $newest);
329 $stub->orderBy('id');
333 common_log(LOG_INFO, "Finished saving Yammer messages; import complete!");
334 $this->state->state = 'done';
336 while ($stub->fetch()) {
337 $item = $stub->getData();
338 $notice = $this->importer->importNotice($item);
339 common_log(LOG_INFO, "Imported Yammer notice " . $item['id'] . " as $notice->id");
340 $newest = $item['id'];
342 $this->state->messages_newest = $newest;
344 $this->state->modified = common_sql_now();
345 $this->state->update($old);
350 * Count the number of Yammer users we've mapped into our system!
354 public function countUsers()
356 $map = new Yammer_user();
357 return $map->count();
361 * Count the number of Yammer groups we've mapped into our system!
365 public function countGroups()
367 $map = new Yammer_group();
368 return $map->count();
372 * Count the number of Yammer notices we've pulled down for pending import...
376 public function countFetchedNotices()
378 $map = new Yammer_notice_stub();
379 return $map->count();
383 * Count the number of Yammer notices we've mapped into our system!
387 public function countSavedNotices()
389 $map = new Yammer_notice();
390 return $map->count();
394 * Start running import work in the background queues...
396 public function startBackgroundImport()
398 $qm = QueueManager::get();
399 $qm->enqueue('YammerImport', 'yammer');
403 * Record an error condition from a background run, which we should
404 * display in progress state for the admin.
408 public function recordError($msg)
412 $temp = new Yammer_state();
413 $temp->query('ROLLBACK');
414 } catch (Exception $e) {
415 common_log(LOG_ERR, 'Exception while confirming rollback while recording error: ' . $e->getMessage());
417 $old = clone($this->state);
418 $this->state->last_error = $msg;
419 $this->state->update($old);
423 * Clear the error state.
425 public function clearError()
427 $this->recordError('');
431 * Get the last recorded background error message, if any.
435 public function lastError()
437 return $this->state->last_error;