From: Brion Vibber Date: Wed, 22 Sep 2010 19:52:34 +0000 (-0700) Subject: Split Yammer importer files into subdirs before I get too lost adding UI X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=12ec7efe901d5667e26c86e11543010105656d84;p=quix0rs-gnu-social.git Split Yammer importer files into subdirs before I get too lost adding UI --- diff --git a/plugins/YammerImport/YammerImportPlugin.php b/plugins/YammerImport/YammerImportPlugin.php index a3520d8a86..79b8260b69 100644 --- a/plugins/YammerImport/YammerImportPlugin.php +++ b/plugins/YammerImport/YammerImportPlugin.php @@ -68,9 +68,7 @@ class YammerImportPlugin extends Plugin switch ($lower) { case 'sn_yammerclient': case 'yammerimporter': - case 'yammerimqueuehandler': - case 'importyammeraction': - require_once "$base/$lower.php"; + require_once "$base/lib/$lower.php"; return false; default: return true; diff --git a/plugins/YammerImport/lib/sn_yammerclient.php b/plugins/YammerImport/lib/sn_yammerclient.php new file mode 100644 index 0000000000..8f9f1d4131 --- /dev/null +++ b/plugins/YammerImport/lib/sn_yammerclient.php @@ -0,0 +1,236 @@ +. + */ + +/** + * Basic client class for Yammer's OAuth/JSON API. + * + * @package YammerImportPlugin + * @author Brion Vibber + */ +class SN_YammerClient +{ + protected $apiBase = "https://www.yammer.com"; + protected $consumerKey, $consumerSecret; + protected $token, $tokenSecret, $verifier; + + public function __construct($consumerKey, $consumerSecret, $token=null, $tokenSecret=null) + { + $this->consumerKey = $consumerKey; + $this->consumerSecret = $consumerSecret; + $this->token = $token; + $this->tokenSecret = $tokenSecret; + } + + /** + * Make an HTTP GET request with OAuth headers and return an HTTPResponse + * with the returned body and codes. + * + * @param string $url + * @return HTTPResponse + * + * @throws Exception on low-level network error + */ + protected function httpGet($url) + { + $headers = array('Authorization: ' . $this->authHeader()); + + $client = HTTPClient::start(); + return $client->get($url, $headers); + } + + /** + * Make an HTTP GET request with OAuth headers and return the response body + * on success. + * + * @param string $url + * @return string + * + * @throws Exception on low-level network or HTTP error + */ + public function fetchUrl($url) + { + $response = $this->httpGet($url); + if ($response->isOk()) { + return $response->getBody(); + } else { + throw new Exception("Yammer API returned HTTP code " . $response->getStatus() . ': ' . $response->getBody()); + } + } + + /** + * Make an HTTP hit with OAuth headers and return the response body on success. + * + * @param string $path URL chunk for the API method + * @param array $params + * @return string + * + * @throws Exception on low-level network or HTTP error + */ + protected function fetchApi($path, $params=array()) + { + $url = $this->apiBase . '/' . $path; + if ($params) { + $url .= '?' . http_build_query($params, null, '&'); + } + return $this->fetchUrl($url); + } + + /** + * Hit the main Yammer API point and decode returned JSON data. + * + * @param string $method + * @param array $params + * @return array from JSON data + * + * @throws Exception for HTTP error or bad JSON return + */ + public function api($method, $params=array()) + { + $body = $this->fetchApi("api/v1/$method.json", $params); + $data = json_decode($body, true); + if (!$data) { + throw new Exception("Invalid JSON response from Yammer API"); + } + return $data; + } + + /** + * Build an Authorization header value from the keys we have available. + */ + protected function authHeader() + { + // token + // token_secret + $params = array('realm' => '', + 'oauth_consumer_key' => $this->consumerKey, + 'oauth_signature_method' => 'PLAINTEXT', + 'oauth_timestamp' => time(), + 'oauth_nonce' => time(), + 'oauth_version' => '1.0'); + if ($this->token) { + $params['oauth_token'] = $this->token; + } + if ($this->tokenSecret) { + $params['oauth_signature'] = $this->consumerSecret . '&' . $this->tokenSecret; + } else { + $params['oauth_signature'] = $this->consumerSecret . '&'; + } + if ($this->verifier) { + $params['oauth_verifier'] = $this->verifier; + } + $parts = array_map(array($this, 'authHeaderChunk'), array_keys($params), array_values($params)); + return 'OAuth ' . implode(', ', $parts); + } + + /** + * @param string $key + * @param string $val + */ + protected function authHeaderChunk($key, $val) + { + return urlencode($key) . '="' . urlencode($val) . '"'; + } + + /** + * @return array of oauth return data; should contain nice things + */ + public function requestToken() + { + if ($this->token || $this->tokenSecret) { + throw new Exception("Requesting a token, but already set up with a token"); + } + $data = $this->fetch('oauth/request_token'); + $arr = array(); + parse_str($data, $arr); + return $arr; + } + + /** + * @return array of oauth return data; should contain nice things + */ + public function accessToken($verifier) + { + $this->verifier = $verifier; + $data = $this->fetch('oauth/access_token'); + $this->verifier = null; + $arr = array(); + parse_str($data, $arr); + return $arr; + } + + /** + * Give the URL to send users to to authorize a new app setup + * + * @param string $token as returned from accessToken() + * @return string URL + */ + public function authorizeUrl($token) + { + return $this->apiBase . '/oauth/authorize?oauth_token=' . urlencode($token); + } + + /** + * High-level API hit: fetch all messages in the network (up to 20 at a time). + * Return data is the full JSON array returned, including meta and references + * sections. + * + * The matching messages themselves will be in the 'messages' item within. + * + * @param array $options optional set of additional params for the request. + * @return array + * + * @throws Exception on low-level or HTTP error + */ + public function messages($params=array()) + { + return $this->api('messages', $params); + } + + /** + * High-level API hit: fetch all users in the network (up to 50 at a time). + * Return data is the full JSON array returned, listing user items. + * + * The matching messages themselves will be in the 'users' item within. + * + * @param array $options optional set of additional params for the request. + * @return array of JSON-sourced user data arrays + * + * @throws Exception on low-level or HTTP error + */ + public function users($params=array()) + { + return $this->api('users', $params); + } + + /** + * High-level API hit: fetch all groups in the network (up to 20 at a time). + * Return data is the full JSON array returned, listing user items. + * + * The matching messages themselves will be in the 'users' item within. + * + * @param array $options optional set of additional params for the request. + * @return array of JSON-sourced user data arrays + * + * @throws Exception on low-level or HTTP error + */ + public function groups($params=array()) + { + return $this->api('groups', $params); + } +} diff --git a/plugins/YammerImport/lib/yammerimporter.php b/plugins/YammerImport/lib/yammerimporter.php new file mode 100644 index 0000000000..9ce0d1e588 --- /dev/null +++ b/plugins/YammerImport/lib/yammerimporter.php @@ -0,0 +1,468 @@ +. + */ + +/** + * Basic client class for Yammer's OAuth/JSON API. + * + * @package YammerImportPlugin + * @author Brion Vibber + */ +class YammerImporter +{ + protected $client; + protected $users=array(); + protected $groups=array(); + protected $notices=array(); + + function __construct(SN_YammerClient $client) + { + $this->client = $client; + } + + /** + * Load or create an imported profile from Yammer data. + * + * @param object $item loaded JSON data for Yammer importer + * @return Profile + */ + function importUser($item) + { + $data = $this->prepUser($item); + + $profileId = $this->findImportedUser($data['orig_id']); + if ($profileId) { + return Profile::staticGet('id', $profileId); + } else { + $user = User::register($data['options']); + $profile = $user->getProfile(); + if ($data['avatar']) { + try { + $this->saveAvatar($data['avatar'], $profile); + } catch (Exception $e) { + common_log(LOG_ERR, "Error importing Yammer avatar: " . $e->getMessage()); + } + } + $this->recordImportedUser($data['orig_id'], $profile->id); + return $profile; + } + } + + /** + * Load or create an imported group from Yammer data. + * + * @param object $item loaded JSON data for Yammer importer + * @return User_group + */ + function importGroup($item) + { + $data = $this->prepGroup($item); + + $groupId = $this->findImportedGroup($data['orig_id']); + if ($groupId) { + return User_group::staticGet('id', $groupId); + } else { + $group = User_group::register($data['options']); + if ($data['avatar']) { + try { + $this->saveAvatar($data['avatar'], $group); + } catch (Exception $e) { + common_log(LOG_ERR, "Error importing Yammer avatar: " . $e->getMessage()); + } + } + $this->recordImportedGroup($data['orig_id'], $group->id); + return $group; + } + } + + /** + * Load or create an imported notice from Yammer data. + * + * @param object $item loaded JSON data for Yammer importer + * @return Notice + */ + function importNotice($item) + { + $data = $this->prepNotice($item); + + $noticeId = $this->findImportedNotice($data['orig_id']); + if ($noticeId) { + return Notice::staticGet('id', $noticeId); + } else { + $content = $data['content']; + $user = User::staticGet($data['profile']); + + // Fetch file attachments and add the URLs... + $uploads = array(); + foreach ($data['attachments'] as $url) { + try { + $upload = $this->saveAttachment($url, $user); + $content .= ' ' . $upload->shortUrl(); + $uploads[] = $upload; + } catch (Exception $e) { + common_log(LOG_ERR, "Error importing Yammer attachment: " . $e->getMessage()); + } + } + + // Here's the meat! Actually save the dang ol' notice. + $notice = Notice::saveNew($user->id, + $content, + $data['source'], + $data['options']); + + // Save "likes" as favorites... + foreach ($data['faves'] as $nickname) { + $user = User::staticGet('nickname', $nickname); + if ($user) { + Fave::addNew($user->getProfile(), $notice); + } + } + + // And finally attach the upload records... + foreach ($uploads as $upload) { + $upload->attachToNotice($notice); + } + $this->recordImportedNotice($data['orig_id'], $notice->id); + return $notice; + } + } + + /** + * Pull relevant info out of a Yammer data record for a user import. + * + * @param array $item + * @return array + */ + function prepUser($item) + { + if ($item['type'] != 'user') { + throw new Exception('Wrong item type sent to Yammer user import processing.'); + } + + $origId = $item['id']; + $origUrl = $item['url']; + + // @fixme check username rules? + + $options['nickname'] = $item['name']; + $options['fullname'] = trim($item['full_name']); + + // Avatar... this will be the "_small" variant. + // Remove that (pre-extension) suffix to get the orig-size image. + $avatar = $item['mugshot_url']; + + // The following info is only available in full data, not in the reference version. + + // There can be extensive contact info, but for now we'll only pull the primary email. + if (isset($item['contact'])) { + foreach ($item['contact']['email_addresses'] as $addr) { + if ($addr['type'] == 'primary') { + $options['email'] = $addr['address']; + $options['email_confirmed'] = true; + break; + } + } + } + + // There can be multiple external URLs; for now pull the first one as home page. + if (isset($item['external_urls'])) { + foreach ($item['external_urls'] as $url) { + if (common_valid_http_url($url)) { + $options['homepage'] = $url; + break; + } + } + } + + // Combine a few bits into the bio... + $bio = array(); + if (!empty($item['job_title'])) { + $bio[] = $item['job_title']; + } + if (!empty($item['summary'])) { + $bio[] = $item['summary']; + } + if (!empty($item['expertise'])) { + $bio[] = _m('Expertise:') . ' ' . $item['expertise']; + } + $options['bio'] = implode("\n\n", $bio); + + // Pull raw location string, may be lookupable + if (!empty($item['location'])) { + $options['location'] = $item['location']; + } + + // Timezone is in format like 'Pacific Time (US & Canada)' + // We need to convert that to a zone id. :P + // @fixme timezone not yet supported at registration time :) + if (!empty($item['timezone'])) { + $tz = $this->timezone($item['timezone']); + if ($tz) { + $options['timezone'] = $tz; + } + } + + return array('orig_id' => $origId, + 'orig_url' => $origUrl, + 'avatar' => $avatar, + 'options' => $options); + + } + + /** + * Pull relevant info out of a Yammer data record for a group import. + * + * @param array $item + * @return array + */ + function prepGroup($item) + { + if ($item['type'] != 'group') { + throw new Exception('Wrong item type sent to Yammer group import processing.'); + } + + $origId = $item['id']; + $origUrl = $item['url']; + + $privacy = $item['privacy']; // Warning! only public groups in SN so far + + $options['nickname'] = $item['name']; + $options['fullname'] = $item['full_name']; + $options['description'] = $item['description']; + $options['created'] = $this->timestamp($item['created_at']); + + $avatar = $item['mugshot_url']; // as with user profiles... + + + $options['mainpage'] = common_local_url('showgroup', + array('nickname' => $options['nickname'])); + + // @fixme what about admin user for the group? + // bio? homepage etc? aliases? + + $options['local'] = true; + return array('orig_id' => $origId, + 'orig_url' => $origUrl, + 'options' => $options, + 'avatar' => $avatar); + } + + /** + * Pull relevant info out of a Yammer data record for a notice import. + * + * @param array $item + * @return array + */ + function prepNotice($item) + { + if (isset($item['type']) && $item['type'] != 'message') { + throw new Exception('Wrong item type sent to Yammer message import processing.'); + } + + $origId = $item['id']; + $origUrl = $item['url']; + + $profile = $this->findImportedUser($item['sender_id']); + $content = $item['body']['plain']; + $source = 'yammer'; + $options = array(); + + if ($item['replied_to_id']) { + $replyTo = $this->findImportedNotice($item['replied_to_id']); + if ($replyTo) { + $options['reply_to'] = $replyTo; + } + } + $options['created'] = $this->timestamp($item['created_at']); + + if ($item['group_id']) { + $groupId = $this->findImportedGroup($item['group_id']); + if ($groupId) { + $options['groups'] = array($groupId); + + // @fixme if we see a group link inline, don't add this? + $group = User_group::staticGet('id', $groupId); + if ($group) { + $content .= ' !' . $group->nickname; + } + } + } + + $faves = array(); + foreach ($item['liked_by']['names'] as $liker) { + // "permalink" is the username. wtf? + $faves[] = $liker['permalink']; + } + + $attachments = array(); + foreach ($item['attachments'] as $attach) { + if ($attach['type'] == 'image' || $attach['type'] == 'file') { + $attachments[] = $attach[$attach['type']]['url']; + } else { + common_log(LOG_WARNING, "Unrecognized Yammer attachment type: " . $attach['type']); + } + } + + return array('orig_id' => $origId, + 'orig_url' => $origUrl, + 'profile' => $profile, + 'content' => $content, + 'source' => $source, + 'options' => $options, + 'faves' => $faves, + 'attachments' => $attachments); + } + + private function findImportedUser($origId) + { + if (isset($this->users[$origId])) { + return $this->users[$origId]; + } else { + return false; + } + } + + private function findImportedGroup($origId) + { + if (isset($this->groups[$origId])) { + return $this->groups[$origId]; + } else { + return false; + } + } + + private function findImportedNotice($origId) + { + if (isset($this->notices[$origId])) { + return $this->notices[$origId]; + } else { + return false; + } + } + + private function recordImportedUser($origId, $userId) + { + $this->users[$origId] = $userId; + } + + private function recordImportedGroup($origId, $groupId) + { + $this->groups[$origId] = $groupId; + } + + private function recordImportedNotice($origId, $noticeId) + { + $this->notices[$origId] = $noticeId; + } + + /** + * Normalize timestamp format. + * @param string $ts + * @return string + */ + private function timestamp($ts) + { + return common_sql_date(strtotime($ts)); + } + + private function timezone($tz) + { + // Blaaaaaarf! + $known = array('Pacific Time (US & Canada)' => 'America/Los_Angeles', + 'Eastern Time (US & Canada)' => 'America/New_York'); + if (array_key_exists($known, $tz)) { + return $known[$tz]; + } else { + return false; + } + } + + /** + * Download and update given avatar image + * + * @param string $url + * @param mixed $dest either a Profile or User_group object + * @throws Exception in various failure cases + */ + private function saveAvatar($url, $dest) + { + // Yammer API data mostly gives us the small variant. + // Try hitting the source image if we can! + // @fixme no guarantee of this URL scheme I think. + $url = preg_replace('/_small(\..*?)$/', '$1', $url); + + if (!common_valid_http_url($url)) { + throw new ServerException(sprintf(_m("Invalid avatar URL %s."), $url)); + } + + // @fixme this should be better encapsulated + // ripped from oauthstore.php (for old OMB client) + $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); + if (!copy($url, $temp_filename)) { + throw new ServerException(sprintf(_m("Unable to fetch avatar from %s."), $url)); + } + + $id = $dest->id; + // @fixme should we be using different ids? + $imagefile = new ImageFile($id, $temp_filename); + $filename = Avatar::filename($id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + // @fixme hardcoded chmod is lame, but seems to be necessary to + // keep from accidentally saving images from command-line (queues) + // that can't be read from web server, which causes hard-to-notice + // problems later on: + // + // http://status.net/open-source/issues/2663 + chmod(Avatar::path($filename), 0644); + + $dest->setOriginal($filename); + } + + /** + * Fetch an attachment from Yammer and save it into our system. + * Unlike avatars, the attachment URLs are guarded by authentication, + * so we need to run the HTTP hit through our OAuth API client. + * + * @param string $url + * @param User $user + * @return MediaFile + * + * @throws Exception on low-level network or HTTP error + */ + private function saveAttachment($url, User $user) + { + // Fetch the attachment... + // WARNING: file must fit in memory here :( + $body = $this->client->fetchUrl($url); + + // Save to a temporary file and shove it into our file-attachment space... + $temp = tmpfile(); + fwrite($temp, $body); + try { + $upload = MediaFile::fromFileHandle($temp, $user); + fclose($temp); + return $upload; + } catch (Exception $e) { + fclose($temp); + throw $e; + } + } +} diff --git a/plugins/YammerImport/scripts/yamdump.php b/plugins/YammerImport/scripts/yamdump.php new file mode 100644 index 0000000000..a358777ad1 --- /dev/null +++ b/plugins/YammerImport/scripts/yamdump.php @@ -0,0 +1,34 @@ +users(); +var_dump($data); +// @fixme follow paging +foreach ($data as $item) { + $user = $imp->prepUser($item); + var_dump($user); +} + +/* +$data = $yam->messages(); +var_dump($data); +// @fixme follow paging +$messages = $data['messages']; +$messages = array_reverse($messages); +foreach ($messages as $message) { + $notice = $imp->prepNotice($message); + var_dump($notice); +} +*/ diff --git a/plugins/YammerImport/scripts/yammer-import.php b/plugins/YammerImport/scripts/yammer-import.php new file mode 100644 index 0000000000..ac258e1c7d --- /dev/null +++ b/plugins/YammerImport/scripts/yammer-import.php @@ -0,0 +1,41 @@ +users(); +foreach ($data as $item) { + $user = $imp->importUser($item); + echo "Imported Yammer user " . $item['id'] . " as $user->nickname ($user->id)\n"; +} + +// Groups! +// @fixme follow paging -- we only get 20 at a time +$data = $yam->groups(); +foreach ($data as $item) { + $group = $imp->importGroup($item); + echo "Imported Yammer group " . $item['id'] . " as $group->nickname ($group->id)\n"; +} + +// Messages! +// Process in reverse chron order... +// @fixme follow paging -- we only get 20 at a time, and start at the most recent! +$data = $yam->messages(); +$messages = $data['messages']; +$messages = array_reverse($messages); +foreach ($messages as $item) { + $notice = $imp->importNotice($item); + echo "Imported Yammer notice " . $item['id'] . " as $notice->id\n"; +} diff --git a/plugins/YammerImport/sn_yammerclient.php b/plugins/YammerImport/sn_yammerclient.php deleted file mode 100644 index 8f9f1d4131..0000000000 --- a/plugins/YammerImport/sn_yammerclient.php +++ /dev/null @@ -1,236 +0,0 @@ -. - */ - -/** - * Basic client class for Yammer's OAuth/JSON API. - * - * @package YammerImportPlugin - * @author Brion Vibber - */ -class SN_YammerClient -{ - protected $apiBase = "https://www.yammer.com"; - protected $consumerKey, $consumerSecret; - protected $token, $tokenSecret, $verifier; - - public function __construct($consumerKey, $consumerSecret, $token=null, $tokenSecret=null) - { - $this->consumerKey = $consumerKey; - $this->consumerSecret = $consumerSecret; - $this->token = $token; - $this->tokenSecret = $tokenSecret; - } - - /** - * Make an HTTP GET request with OAuth headers and return an HTTPResponse - * with the returned body and codes. - * - * @param string $url - * @return HTTPResponse - * - * @throws Exception on low-level network error - */ - protected function httpGet($url) - { - $headers = array('Authorization: ' . $this->authHeader()); - - $client = HTTPClient::start(); - return $client->get($url, $headers); - } - - /** - * Make an HTTP GET request with OAuth headers and return the response body - * on success. - * - * @param string $url - * @return string - * - * @throws Exception on low-level network or HTTP error - */ - public function fetchUrl($url) - { - $response = $this->httpGet($url); - if ($response->isOk()) { - return $response->getBody(); - } else { - throw new Exception("Yammer API returned HTTP code " . $response->getStatus() . ': ' . $response->getBody()); - } - } - - /** - * Make an HTTP hit with OAuth headers and return the response body on success. - * - * @param string $path URL chunk for the API method - * @param array $params - * @return string - * - * @throws Exception on low-level network or HTTP error - */ - protected function fetchApi($path, $params=array()) - { - $url = $this->apiBase . '/' . $path; - if ($params) { - $url .= '?' . http_build_query($params, null, '&'); - } - return $this->fetchUrl($url); - } - - /** - * Hit the main Yammer API point and decode returned JSON data. - * - * @param string $method - * @param array $params - * @return array from JSON data - * - * @throws Exception for HTTP error or bad JSON return - */ - public function api($method, $params=array()) - { - $body = $this->fetchApi("api/v1/$method.json", $params); - $data = json_decode($body, true); - if (!$data) { - throw new Exception("Invalid JSON response from Yammer API"); - } - return $data; - } - - /** - * Build an Authorization header value from the keys we have available. - */ - protected function authHeader() - { - // token - // token_secret - $params = array('realm' => '', - 'oauth_consumer_key' => $this->consumerKey, - 'oauth_signature_method' => 'PLAINTEXT', - 'oauth_timestamp' => time(), - 'oauth_nonce' => time(), - 'oauth_version' => '1.0'); - if ($this->token) { - $params['oauth_token'] = $this->token; - } - if ($this->tokenSecret) { - $params['oauth_signature'] = $this->consumerSecret . '&' . $this->tokenSecret; - } else { - $params['oauth_signature'] = $this->consumerSecret . '&'; - } - if ($this->verifier) { - $params['oauth_verifier'] = $this->verifier; - } - $parts = array_map(array($this, 'authHeaderChunk'), array_keys($params), array_values($params)); - return 'OAuth ' . implode(', ', $parts); - } - - /** - * @param string $key - * @param string $val - */ - protected function authHeaderChunk($key, $val) - { - return urlencode($key) . '="' . urlencode($val) . '"'; - } - - /** - * @return array of oauth return data; should contain nice things - */ - public function requestToken() - { - if ($this->token || $this->tokenSecret) { - throw new Exception("Requesting a token, but already set up with a token"); - } - $data = $this->fetch('oauth/request_token'); - $arr = array(); - parse_str($data, $arr); - return $arr; - } - - /** - * @return array of oauth return data; should contain nice things - */ - public function accessToken($verifier) - { - $this->verifier = $verifier; - $data = $this->fetch('oauth/access_token'); - $this->verifier = null; - $arr = array(); - parse_str($data, $arr); - return $arr; - } - - /** - * Give the URL to send users to to authorize a new app setup - * - * @param string $token as returned from accessToken() - * @return string URL - */ - public function authorizeUrl($token) - { - return $this->apiBase . '/oauth/authorize?oauth_token=' . urlencode($token); - } - - /** - * High-level API hit: fetch all messages in the network (up to 20 at a time). - * Return data is the full JSON array returned, including meta and references - * sections. - * - * The matching messages themselves will be in the 'messages' item within. - * - * @param array $options optional set of additional params for the request. - * @return array - * - * @throws Exception on low-level or HTTP error - */ - public function messages($params=array()) - { - return $this->api('messages', $params); - } - - /** - * High-level API hit: fetch all users in the network (up to 50 at a time). - * Return data is the full JSON array returned, listing user items. - * - * The matching messages themselves will be in the 'users' item within. - * - * @param array $options optional set of additional params for the request. - * @return array of JSON-sourced user data arrays - * - * @throws Exception on low-level or HTTP error - */ - public function users($params=array()) - { - return $this->api('users', $params); - } - - /** - * High-level API hit: fetch all groups in the network (up to 20 at a time). - * Return data is the full JSON array returned, listing user items. - * - * The matching messages themselves will be in the 'users' item within. - * - * @param array $options optional set of additional params for the request. - * @return array of JSON-sourced user data arrays - * - * @throws Exception on low-level or HTTP error - */ - public function groups($params=array()) - { - return $this->api('groups', $params); - } -} diff --git a/plugins/YammerImport/yamdump.php b/plugins/YammerImport/yamdump.php deleted file mode 100644 index 809baa1223..0000000000 --- a/plugins/YammerImport/yamdump.php +++ /dev/null @@ -1,34 +0,0 @@ -users(); -var_dump($data); -// @fixme follow paging -foreach ($data as $item) { - $user = $imp->prepUser($item); - var_dump($user); -} - -/* -$data = $yam->messages(); -var_dump($data); -// @fixme follow paging -$messages = $data['messages']; -$messages = array_reverse($messages); -foreach ($messages as $message) { - $notice = $imp->prepNotice($message); - var_dump($notice); -} -*/ diff --git a/plugins/YammerImport/yammer-import.php b/plugins/YammerImport/yammer-import.php deleted file mode 100644 index 4931d1bc52..0000000000 --- a/plugins/YammerImport/yammer-import.php +++ /dev/null @@ -1,41 +0,0 @@ -users(); -foreach ($data as $item) { - $user = $imp->importUser($item); - echo "Imported Yammer user " . $item['id'] . " as $user->nickname ($user->id)\n"; -} - -// Groups! -// @fixme follow paging -- we only get 20 at a time -$data = $yam->groups(); -foreach ($data as $item) { - $group = $imp->importGroup($item); - echo "Imported Yammer group " . $item['id'] . " as $group->nickname ($group->id)\n"; -} - -// Messages! -// Process in reverse chron order... -// @fixme follow paging -- we only get 20 at a time, and start at the most recent! -$data = $yam->messages(); -$messages = $data['messages']; -$messages = array_reverse($messages); -foreach ($messages as $item) { - $notice = $imp->importNotice($item); - echo "Imported Yammer notice " . $item['id'] . " as $notice->id\n"; -} diff --git a/plugins/YammerImport/yammerimporter.php b/plugins/YammerImport/yammerimporter.php deleted file mode 100644 index 9ce0d1e588..0000000000 --- a/plugins/YammerImport/yammerimporter.php +++ /dev/null @@ -1,468 +0,0 @@ -. - */ - -/** - * Basic client class for Yammer's OAuth/JSON API. - * - * @package YammerImportPlugin - * @author Brion Vibber - */ -class YammerImporter -{ - protected $client; - protected $users=array(); - protected $groups=array(); - protected $notices=array(); - - function __construct(SN_YammerClient $client) - { - $this->client = $client; - } - - /** - * Load or create an imported profile from Yammer data. - * - * @param object $item loaded JSON data for Yammer importer - * @return Profile - */ - function importUser($item) - { - $data = $this->prepUser($item); - - $profileId = $this->findImportedUser($data['orig_id']); - if ($profileId) { - return Profile::staticGet('id', $profileId); - } else { - $user = User::register($data['options']); - $profile = $user->getProfile(); - if ($data['avatar']) { - try { - $this->saveAvatar($data['avatar'], $profile); - } catch (Exception $e) { - common_log(LOG_ERR, "Error importing Yammer avatar: " . $e->getMessage()); - } - } - $this->recordImportedUser($data['orig_id'], $profile->id); - return $profile; - } - } - - /** - * Load or create an imported group from Yammer data. - * - * @param object $item loaded JSON data for Yammer importer - * @return User_group - */ - function importGroup($item) - { - $data = $this->prepGroup($item); - - $groupId = $this->findImportedGroup($data['orig_id']); - if ($groupId) { - return User_group::staticGet('id', $groupId); - } else { - $group = User_group::register($data['options']); - if ($data['avatar']) { - try { - $this->saveAvatar($data['avatar'], $group); - } catch (Exception $e) { - common_log(LOG_ERR, "Error importing Yammer avatar: " . $e->getMessage()); - } - } - $this->recordImportedGroup($data['orig_id'], $group->id); - return $group; - } - } - - /** - * Load or create an imported notice from Yammer data. - * - * @param object $item loaded JSON data for Yammer importer - * @return Notice - */ - function importNotice($item) - { - $data = $this->prepNotice($item); - - $noticeId = $this->findImportedNotice($data['orig_id']); - if ($noticeId) { - return Notice::staticGet('id', $noticeId); - } else { - $content = $data['content']; - $user = User::staticGet($data['profile']); - - // Fetch file attachments and add the URLs... - $uploads = array(); - foreach ($data['attachments'] as $url) { - try { - $upload = $this->saveAttachment($url, $user); - $content .= ' ' . $upload->shortUrl(); - $uploads[] = $upload; - } catch (Exception $e) { - common_log(LOG_ERR, "Error importing Yammer attachment: " . $e->getMessage()); - } - } - - // Here's the meat! Actually save the dang ol' notice. - $notice = Notice::saveNew($user->id, - $content, - $data['source'], - $data['options']); - - // Save "likes" as favorites... - foreach ($data['faves'] as $nickname) { - $user = User::staticGet('nickname', $nickname); - if ($user) { - Fave::addNew($user->getProfile(), $notice); - } - } - - // And finally attach the upload records... - foreach ($uploads as $upload) { - $upload->attachToNotice($notice); - } - $this->recordImportedNotice($data['orig_id'], $notice->id); - return $notice; - } - } - - /** - * Pull relevant info out of a Yammer data record for a user import. - * - * @param array $item - * @return array - */ - function prepUser($item) - { - if ($item['type'] != 'user') { - throw new Exception('Wrong item type sent to Yammer user import processing.'); - } - - $origId = $item['id']; - $origUrl = $item['url']; - - // @fixme check username rules? - - $options['nickname'] = $item['name']; - $options['fullname'] = trim($item['full_name']); - - // Avatar... this will be the "_small" variant. - // Remove that (pre-extension) suffix to get the orig-size image. - $avatar = $item['mugshot_url']; - - // The following info is only available in full data, not in the reference version. - - // There can be extensive contact info, but for now we'll only pull the primary email. - if (isset($item['contact'])) { - foreach ($item['contact']['email_addresses'] as $addr) { - if ($addr['type'] == 'primary') { - $options['email'] = $addr['address']; - $options['email_confirmed'] = true; - break; - } - } - } - - // There can be multiple external URLs; for now pull the first one as home page. - if (isset($item['external_urls'])) { - foreach ($item['external_urls'] as $url) { - if (common_valid_http_url($url)) { - $options['homepage'] = $url; - break; - } - } - } - - // Combine a few bits into the bio... - $bio = array(); - if (!empty($item['job_title'])) { - $bio[] = $item['job_title']; - } - if (!empty($item['summary'])) { - $bio[] = $item['summary']; - } - if (!empty($item['expertise'])) { - $bio[] = _m('Expertise:') . ' ' . $item['expertise']; - } - $options['bio'] = implode("\n\n", $bio); - - // Pull raw location string, may be lookupable - if (!empty($item['location'])) { - $options['location'] = $item['location']; - } - - // Timezone is in format like 'Pacific Time (US & Canada)' - // We need to convert that to a zone id. :P - // @fixme timezone not yet supported at registration time :) - if (!empty($item['timezone'])) { - $tz = $this->timezone($item['timezone']); - if ($tz) { - $options['timezone'] = $tz; - } - } - - return array('orig_id' => $origId, - 'orig_url' => $origUrl, - 'avatar' => $avatar, - 'options' => $options); - - } - - /** - * Pull relevant info out of a Yammer data record for a group import. - * - * @param array $item - * @return array - */ - function prepGroup($item) - { - if ($item['type'] != 'group') { - throw new Exception('Wrong item type sent to Yammer group import processing.'); - } - - $origId = $item['id']; - $origUrl = $item['url']; - - $privacy = $item['privacy']; // Warning! only public groups in SN so far - - $options['nickname'] = $item['name']; - $options['fullname'] = $item['full_name']; - $options['description'] = $item['description']; - $options['created'] = $this->timestamp($item['created_at']); - - $avatar = $item['mugshot_url']; // as with user profiles... - - - $options['mainpage'] = common_local_url('showgroup', - array('nickname' => $options['nickname'])); - - // @fixme what about admin user for the group? - // bio? homepage etc? aliases? - - $options['local'] = true; - return array('orig_id' => $origId, - 'orig_url' => $origUrl, - 'options' => $options, - 'avatar' => $avatar); - } - - /** - * Pull relevant info out of a Yammer data record for a notice import. - * - * @param array $item - * @return array - */ - function prepNotice($item) - { - if (isset($item['type']) && $item['type'] != 'message') { - throw new Exception('Wrong item type sent to Yammer message import processing.'); - } - - $origId = $item['id']; - $origUrl = $item['url']; - - $profile = $this->findImportedUser($item['sender_id']); - $content = $item['body']['plain']; - $source = 'yammer'; - $options = array(); - - if ($item['replied_to_id']) { - $replyTo = $this->findImportedNotice($item['replied_to_id']); - if ($replyTo) { - $options['reply_to'] = $replyTo; - } - } - $options['created'] = $this->timestamp($item['created_at']); - - if ($item['group_id']) { - $groupId = $this->findImportedGroup($item['group_id']); - if ($groupId) { - $options['groups'] = array($groupId); - - // @fixme if we see a group link inline, don't add this? - $group = User_group::staticGet('id', $groupId); - if ($group) { - $content .= ' !' . $group->nickname; - } - } - } - - $faves = array(); - foreach ($item['liked_by']['names'] as $liker) { - // "permalink" is the username. wtf? - $faves[] = $liker['permalink']; - } - - $attachments = array(); - foreach ($item['attachments'] as $attach) { - if ($attach['type'] == 'image' || $attach['type'] == 'file') { - $attachments[] = $attach[$attach['type']]['url']; - } else { - common_log(LOG_WARNING, "Unrecognized Yammer attachment type: " . $attach['type']); - } - } - - return array('orig_id' => $origId, - 'orig_url' => $origUrl, - 'profile' => $profile, - 'content' => $content, - 'source' => $source, - 'options' => $options, - 'faves' => $faves, - 'attachments' => $attachments); - } - - private function findImportedUser($origId) - { - if (isset($this->users[$origId])) { - return $this->users[$origId]; - } else { - return false; - } - } - - private function findImportedGroup($origId) - { - if (isset($this->groups[$origId])) { - return $this->groups[$origId]; - } else { - return false; - } - } - - private function findImportedNotice($origId) - { - if (isset($this->notices[$origId])) { - return $this->notices[$origId]; - } else { - return false; - } - } - - private function recordImportedUser($origId, $userId) - { - $this->users[$origId] = $userId; - } - - private function recordImportedGroup($origId, $groupId) - { - $this->groups[$origId] = $groupId; - } - - private function recordImportedNotice($origId, $noticeId) - { - $this->notices[$origId] = $noticeId; - } - - /** - * Normalize timestamp format. - * @param string $ts - * @return string - */ - private function timestamp($ts) - { - return common_sql_date(strtotime($ts)); - } - - private function timezone($tz) - { - // Blaaaaaarf! - $known = array('Pacific Time (US & Canada)' => 'America/Los_Angeles', - 'Eastern Time (US & Canada)' => 'America/New_York'); - if (array_key_exists($known, $tz)) { - return $known[$tz]; - } else { - return false; - } - } - - /** - * Download and update given avatar image - * - * @param string $url - * @param mixed $dest either a Profile or User_group object - * @throws Exception in various failure cases - */ - private function saveAvatar($url, $dest) - { - // Yammer API data mostly gives us the small variant. - // Try hitting the source image if we can! - // @fixme no guarantee of this URL scheme I think. - $url = preg_replace('/_small(\..*?)$/', '$1', $url); - - if (!common_valid_http_url($url)) { - throw new ServerException(sprintf(_m("Invalid avatar URL %s."), $url)); - } - - // @fixme this should be better encapsulated - // ripped from oauthstore.php (for old OMB client) - $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); - if (!copy($url, $temp_filename)) { - throw new ServerException(sprintf(_m("Unable to fetch avatar from %s."), $url)); - } - - $id = $dest->id; - // @fixme should we be using different ids? - $imagefile = new ImageFile($id, $temp_filename); - $filename = Avatar::filename($id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - // @fixme hardcoded chmod is lame, but seems to be necessary to - // keep from accidentally saving images from command-line (queues) - // that can't be read from web server, which causes hard-to-notice - // problems later on: - // - // http://status.net/open-source/issues/2663 - chmod(Avatar::path($filename), 0644); - - $dest->setOriginal($filename); - } - - /** - * Fetch an attachment from Yammer and save it into our system. - * Unlike avatars, the attachment URLs are guarded by authentication, - * so we need to run the HTTP hit through our OAuth API client. - * - * @param string $url - * @param User $user - * @return MediaFile - * - * @throws Exception on low-level network or HTTP error - */ - private function saveAttachment($url, User $user) - { - // Fetch the attachment... - // WARNING: file must fit in memory here :( - $body = $this->client->fetchUrl($url); - - // Save to a temporary file and shove it into our file-attachment space... - $temp = tmpfile(); - fwrite($temp, $body); - try { - $upload = MediaFile::fromFileHandle($temp, $user); - fclose($temp); - return $upload; - } catch (Exception $e) { - fclose($temp); - throw $e; - } - } -}