code:
# Translation of StatusNet - Xmpp to Ukrainian (Українська)
# Exported from translatewiki.net
#
# Author: Boogie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - Xmpp\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-06-18 15:54+0000\n"
"PO-Revision-Date: 2011-06-18 15:58:04+0000\n"
"Language-Team: Ukrainian <http://translatewiki.net/wiki/Portal:uk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-06-05 21:52:15+0000\n"
"X-Generator: MediaWiki 1.19alpha (r90318); Translate extension (2011-06-02)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: uk\n"
"X-Message-Group: #out-statusnet-plugin-xmpp\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"

#. TRANS: Presence announcement for XMPP.
#. TRANS: Message for XMPP reconnect.
msgid "Send me a message to post a notice"
msgstr "Надішліть мені повідомлення, щоб опублікувати допис"

#. TRANS: Plugin display name.
msgid "XMPP/Jabber/GTalk"
msgstr "XMPP/Jabber/GTalk"

#. TRANS: Exception thrown when using too many @ signs in a Jabber ID.
msgid "Invalid JID: too many @s."
msgstr ""

#. TRANS: Exception thrown when using @ sign not followed by a Jabber ID.
msgid "Invalid JID: @ but no node"
msgstr ""

#. TRANS: Exception thrown when using too long a Jabber ID (>1023).
msgid "Invalid JID: node too long."
msgstr ""

#. TRANS: Exception thrown when using an invalid Jabber ID.
#. TRANS: %s is the invalid Jabber ID.
#, php-format
msgid "Invalid JID node \"%s\"."
msgstr ""

#. TRANS: Exception thrown when using too long a Jabber domain (>1023).
msgid "Invalid JID: domain too long."
msgstr ""

#. TRANS: Exception thrown when using an invalid Jabber domain name.
#. TRANS: %s is the invalid domain name.
#, php-format
msgid "Invalid JID domain name \"%s\"."
msgstr ""

#. TRANS: Exception thrown when using an invalid Jabber resource.
#. TRANS: %s is the invalid resource.
#, php-format
msgid "Invalid JID resource \"%s\"."
msgstr ""

#. TRANS: Link description to notice in conversation.
#. TRANS: %s is a notice ID.
#, php-format
msgid "[%s]"
msgstr "[%s]"

#. TRANS: Exception thrown when the plugin configuration is incorrect.
msgid "You must specify a server in the configuration."
msgstr ""

#. TRANS: Exception thrown when the plugin configuration is incorrect.
msgid "You must specify a port in the configuration."
msgstr ""

#. TRANS: Exception thrown when the plugin configuration is incorrect.
msgid "You must specify a user in the configuration."
msgstr ""

#. TRANS: Exception thrown when the plugin configuration is incorrect.
msgid "You must specify a password in the configuration."
msgstr ""

#. TRANS: Plugin description.
msgid ""
"The XMPP plugin allows users to send and receive notices over the XMPP/"
"Jabber network."
msgstr ""
"Додаток XMPP дозволяє користувачам надсилати і отримувати дописи у мережі "
"XMPP (Jabber)."


code:
Yammer Import Plugin
====================

This plugin allows a one-time import pulling user accounts, groups, and
public messages from an existing Yammer instance, using Yammer's public API.

Requirements
------------

* An account on the Yammer network you wish to import from
* An administrator account on the target StatusNet instance, or
  command-line administrative access
* This YammerImport plugin enabled on your StatusNet instance

Limitations
-----------

Yammer API key registrations only work for your own network unless you make
arrangements for a 'trusted app' key, so for now users will need to register
the app themselves. There is a helper in the admin panel for this.

In theory any number of users, groups, and messages should be supported, but
it hasn't been fully tested on non-trivial-sized sites.

No provision has yet been made for dealing with conflicting usernames or
group names, or names which are not considered valid by StatusNet. Errors
are possible.

Running via the web admin interface requires having queueing enabled, and is
fairly likely to have problems with the application key registration step in
a small installation at this time.

Web setup
---------

The import process is runnable through an administration panel on your
StatusNet site. The user interface is still a bit flaky, however, and if
errors occur during import the process may stop with no way to restart it
visible.

The admin interface will probably kinda blow up if JS/AJAX isn't working.

You'll be prompted to register the application and authenticate into Yammer,
after which a progress screen will display.

Two big warnings:
* The progress display does not currently auto-refresh.
* If anything fails once actual import has begun, it'll just keep showing
  the current state. You won't see an error message, and there's no way
  to reset or restart from the web UI yet.

You can continue or reset the import state using the command-line script.

CLI setup
---------

You'll need to register an application consumer key to allow the importer
to connect to your Yammer network; this requires logging into Yammer:

  https://www.yammer.com/client_applications/new

Check all the 'read' options; no 'write' options are required, but Yammer
seems to end up setting them anyway.

You can set the resulting keys directly in config.php:

    $config['yammer']['consumer_key'] = '#####';
    $config['yammer']['consumer_secret'] = '##########';

Initiate authentication by starting up the importer script:

    php plugins/YammerImport/scripts/yammer-import.php

Since you haven't yet authenticated, this will request an auth token and
give you a URL to open in your web browser. Once logged in and authorized
there, you'll be given a confirmation code. Pass this back:

    php plugins/YammerImport/scripts/yammer-import.php --verify=####

If all is well, the import process will begin and run through the end.

In case of error or manual abort, you should be able to continue the
import from where you left off by running the script again:

    php plugins/YammerImport/scripts/yammer-import.php

To reset the Yammer import state -- without removing any of the items
that have already been imported -- you can pass the --reset option:

    php plugins/YammerImport/scripts/yammer-import.php --reset

This'll let you start over from the requesting-authentication stage.
Any users, groups, or notices that have already been imported will be
retained.

Subscriptions and group memberships
-----------------------------------

Yammer's API does not expose user/tag subscriptions or group memberships
except for the authenticating user. As a result, users will need to re-join
groups and re-follow their fellow users after the import.

(This limitation may be lifted in future for sites on the Silver or Gold
plans where the import is done by a verified admin, as it should be possible
to fetch the information for each user via the admin account.)

Authentication
--------------

Account passwords cannot be retrieved, but the primary e-mail address is
retained so users can reset their passwords by mail if you're not using a
custom authentication system like LDAP.

Private messages and groups
---------------------------

At this time, only public messages are imported; private direct and group
messages are ignored. (This may change with Silver and Gold plans in future.)

Yammer groups may be either public or private. Groups in StatusNet currently
have no privacy option, so any private groups will become public groups in the
imported site.

Attachments
-----------

Attached image and document files will be copied in as if they had been
uploaded to the StatusNet site. Currently images do not display inline like
they do on Yammer; they will be linked instead.

File type and size limitations on attachments will be applied, so beware some
attachments may not make it through.

Code structure
==============

Standalone classes
------------------

YammerRunner: encapsulates the iterative process of retrieving the various users,
              groups, and messages via SNYammerClient and saving them locally
              via YammerImporter.

SNYammerClient: encapsulates HTTP+OAuth interface to Yammer API, returns data
                 as straight decoded JSON object trees.

YammerImporter: encapsulates logic to pull information from the returned API data
                and convert them to native StatusNet users, groups, and messages.

Web UI actions
-------------

YammeradminpanelAction: web panel for site administrator to initiate and monitor
                        the import process.

Command-line scripts
--------------------

yammer-import.php: CLI script to start a Yammer import run in one go.

Database objects
----------------

Yammer_state: data object storing YammerRunner's state between iterations.

Yammer_notice_stub: data object for temporary storage of fetched Yammer messages
                    between fetching them (reverse chron order) and saving them
                    to local messages (forward chron order).
Yammer_user,
Yammer_group,
Yammer_notice: data objects mapping original Yammer item IDs to their local copies.

path: plugins/YammerImport/README

license: mit


code:
<?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/>.
 */

/**
 * @package YammerImportPlugin
 * @maintainer Brion Vibber <brion@status.net>
 */

if (!defined('STATUSNET')) { exit(1); }

class YammerImportPlugin extends Plugin
{
    /**
     * Hook for RouterInitialized event.
     *
     * @param URLMapper $m path-to-action mapper
     * @return boolean hook return
     */
    public function onRouterInitialized(URLMapper $m)
    {
        $m->connect('panel/yammer',
                    array('action' => 'yammeradminpanel'));
        $m->connect('panel/yammer/auth',
                    array('action' => 'yammerauth'));
        return true;
    }

    /**
     * Set up queue handlers for import processing
     * @param QueueManager $qm
     * @return boolean hook return
     */
    function onEndInitializeQueueManager(QueueManager $qm)
    {
        $qm->connect('yammer', 'YammerQueueHandler');

        return true;
    }

    /**
     * Set up all our tables...
     */
    function onCheckSchema()
    {
        $schema = Schema::get();

        $tables = array('Yammer_state',
                        'Yammer_user',
                        'Yammer_group',
                        'Yammer_notice',
                        'Yammer_notice_stub');
        foreach ($tables as $table) {
            $schemaDef = call_user_func(array($table, 'schemaDef'));
            $schema->ensureTable(strtolower($table), $schemaDef);
        }

        return true;
    }

    /**
     * If the plugin's installed, this should be accessible to admins.
     */
    function onAdminPanelCheck($name, &$isOK)
    {
        if ($name == 'yammer') {
            $isOK = true;
            return false;
        }

        return true;
    }

    /**
     * Add the Yammer admin panel to the list...
     */
    function onEndAdminPanelNav($nav)
    {
        if (AdminPanelAction::canAdmin('yammer')) {
            $action_name = $nav->action->trimmed('action');

            $nav->out->menuItem(common_local_url('yammeradminpanel'),
                                // TRANS: Menu item for Yammer import.
                                _m('MENU','Yammer'),
                                // TRANS: Menu item title for Yammer import.
                                _m('Yammer import module.'),
                                $action_name == 'yammeradminpanel',
                                'nav_yammer_admin_panel');
        }

        return true;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Yammer import administration panel
 *
 * PHP version 5
 *
 * LICENCE: 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/>.
 *
 * @category  Settings
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

class YammeradminpanelAction extends AdminPanelAction
{
    private $runner;

    /**
     * Returns the page title
     *
     * @return string page title
     */
    function title()
    {
        // TRANS: Page title for Yammer import administration panel.
        return _m('Yammer Import');
    }

    /**
     * Instructions for using this form.
     *
     * @return string instructions
     */
    function getInstructions()
    {
        // TRANS: Instructions for Yammer import administration panel.
        return _m('This Yammer import tool is still undergoing testing, ' .
                  'and is incomplete in some areas. ' .
                'Currently user subscriptions and group memberships are not ' .
                'transferred; in the future this may be supported for ' .
                'imports done by verified administrators on the Yammer side.');
    }

    function prepare($args)
    {
        $ok = parent::prepare($args);

        $this->subaction = $this->trimmed('subaction');
        $this->runner = YammerRunner::init();

        return $ok;
    }

    function handle($args)
    {
        // @fixme move this to saveSettings and friends?
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
            StatusNet::setApi(true); // short error pages :P
            $this->checkSessionToken();
            if ($this->subaction == 'change-apikey') {
                $form = new YammerApiKeyForm($this);
            } else if ($this->subaction == 'apikey') {
                if ($this->saveKeys()) {
                    $form = new YammerAuthInitForm($this, $this->runner);
                } else {
                    $form = new YammerApiKeyForm($this);
                }
            } else if ($this->subaction == 'authinit') {
                // hack
                if ($this->arg('change-apikey')) {
                    $form = new YammerApiKeyForm($this);
                } else {
                    $url = $this->runner->requestAuth();
                    $form = new YammerAuthVerifyForm($this, $this->runner);
                }
            } else if ($this->subaction == 'authverify') {
                $this->runner->saveAuthToken($this->trimmed('verify_token'));

                // Haho! Now we can make THE FUN HAPPEN
                $this->runner->startBackgroundImport();

                $form = new YammerProgressForm($this, $this->runner);
            } else if ($this->subaction == 'pause-import') {
                // TRANS: Error message about an import job being paused from the admin panel.
                $this->runner->recordError(_m('Paused from admin panel.'));
                $form = $this->statusForm();
            } else if ($this->subaction == 'continue-import') {
                $this->runner->clearError();
                $this->runner->startBackgroundImport();
                $form = $this->statusForm();
            } else if ($this->subaction == 'abort-import') {
                $this->runner->reset();
                $form = $this->statusForm();
            } else if ($this->subaction == 'progress') {
                $form = $this->statusForm();
            } else {
                // TRANS: Client exception thrown when encountering an unhandled sub action.
                throw new ClientException(_m('Invalid POST'));
            }
            return $this->showAjaxForm($form);
        }
        return parent::handle($args);
    }

    function saveKeys()
    {
        $key = $this->trimmed('consumer_key');
        $secret = $this->trimmed('consumer_secret');
        Config::save('yammer', 'consumer_key', $key);
        Config::save('yammer', 'consumer_secret', $secret);

        return !empty($key) && !empty($secret);
    }

    function showAjaxForm($form)
    {
        $this->startHTML('text/xml;charset=utf-8');
        $this->elementStart('head');
        // TRANS: Page title for Yammer import administration panel.
        $this->element('title', null, _m('Yammer import'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $form->show();
        $this->elementEnd('body');
        $this->endHTML();
    }

    /**
     * Fetch the appropriate form for our current state.
     * @return Form
     */
    function statusForm()
    {
        if (!(common_config('yammer', 'consumer_key'))
            || !(common_config('yammer', 'consumer_secret'))) {
            return new YammerApiKeyForm($this);
        }
        switch($this->runner->state())
        {
            case 'init':
                return new YammerAuthInitForm($this, $this->runner);
            case 'requesting-auth':
                return new YammerAuthVerifyForm($this, $this->runner);
            default:
                return new YammerProgressForm($this, $this->runner);
        }
    }

    /**
     * Show the Yammer admin panel form
     *
     * @return void
     */
    function showForm()
    {
        $this->elementStart('fieldset');
        $this->statusForm()->show();
        $this->elementEnd('fieldset');
    }

    function showStylesheets()
    {
        parent::showStylesheets();
        $this->cssLink(Plugin::staticPath('YammerImport', 'css/admin.css'), null, 'screen, projection, tv');
    }

    function showScripts()
    {
        parent::showScripts();
        $this->script(Plugin::staticPath('YammerImport', 'js/yammer-admin.js'));
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Yammer import administration panel
 *
 * PHP version 5
 *
 * LICENCE: 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/>.
 *
 * @category  Settings
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

class YammerauthAction extends AdminPanelAction
{
    /**
     * Show the Yammer admin panel form
     *
     * @return void
     */
    function prepare($args)
    {
        parent::prepare($args);

        $this->verify_token = $this->trim('verify_token');
    }

    /**
     * Handle request
     *
     * Does the subscription and returns results.
     *
     * @param Array $args unused.
     *
     * @return void
     */
    function handle($args)
    {
        if ($this->verify_token) {
            $runner->saveAuthToken($this->verify_token);
            $form = new YammerAuthProgressForm();
        } else {
            $url = $runner->requestAuth();
            $form = new YammerAuthVerifyForm($this, $url);
        }

        $this->startHTML('text/xml;charset=utf-8');
        $this->elementStart('head');
        // TRANS: Page title for Yammer administration panel.
        $this->element('title', null, _m('Connect to Yammer'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $form->show();
        $this->elementEnd('body');
        $this->endHTML();
    }
}


code:
<?php
/**
 * Data class for remembering Yammer import mappings
 *
 * PHP version 5
 *
 * @category Data
 * @package  StatusNet
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * 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);
}

/**
 * Common base class for the Yammer import mappings for users, groups, and notices.
 *
 * Child classes must override these static methods, since we need to run
 * on PHP 5.2.x which has no late static binding: (not really anymore)
 * - staticGet (as our other classes)
 * - schemaDef (call self::doSchemaDef)
 * - record (call self::doRecord)
 */
class Yammer_common extends Managed_DataObject
{
    public $__table = 'yammer_XXXX'; // table name
    public $__field = 'XXXX_id';     // field name to save into
    public $id;                      // int  primary_key not_null
    public $user_id;                 // int(4)
    public $created;                 // datetime

    /**
     * @todo FIXME: Add a 'references' thing for the foreign key when we support that
     */
    protected static function doSchemaDef($field)
    {
        return array(new ColumnDef('id', 'bigint', null,
                                   false, 'PRI'),
                     new ColumnDef($field, 'integer', null,
                                   false, 'UNI'),
                     new ColumnDef('created', 'datetime', null,
                                   false));
    }

    /**
     * return table definition for DB_DataObject
     *
     * DB_DataObject needs to know something about the table to manipulate
     * instances. This method provides all the DB_DataObject needs to know.
     *
     * @return array array of column definitions
     */

    function table()
    {
        return array('id'           => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
                     $this->__field => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
                     'created'      => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
    }

    /**
     * return key definitions for DB_DataObject
     *
     * DB_DataObject needs to know about keys that the table has, since it
     * won't appear in StatusNet's own keys list. In most cases, this will
     * simply reference your keyTypes() function.
     *
     * @return array list of key field names
     */
    function keys()
    {
        return array_keys($this->keyTypes());
    }

    /**
     * return key definitions for Memcached_DataObject
     *
     * Our caching system uses the same key definitions, but uses a different
     * method to get them. This key information is used to store and clear
     * cached data, so be sure to list any key that will be used for static
     * lookups.
     *
     * @return array associative array of key definitions, field name to type:
     *         'K' for primary key: for compound keys, add an entry for each component;
     *         'U' for unique keys: compound keys are not well supported here.
     */
    function keyTypes()
    {
        return array('id' => 'K', $this->__field => 'U');
    }

    /**
     * Magic formula for non-autoincrementing integer primary keys
     *
     * If a table has a single integer column as its primary key, DB_DataObject
     * assumes that the column is auto-incrementing and makes a sequence table
     * to do this incrementation. Since we don't need this for our class, we
     * overload this method and return the magic formula that DB_DataObject needs.
     *
     * @return array magic three-false array that stops auto-incrementing.
     */
    function sequenceKey()
    {
        return array(false, false, false);
    }

    /**
     * Save a mapping between a remote Yammer and local imported user.
     *
     * @param integer $user_id ID of the status in StatusNet
     * @param integer $orig_id ID of the notice in Yammer
     *
     * @return Yammer_common new object for this value
     */
    protected static function doRecord($class, $field, $orig_id, $local_id)
    {
        $map = Memcached_DataObject::getClassKV($class, 'id', $orig_id);

        if (!empty($map)) {
            return $map;
        }

        $map = Memcached_DataObject::getClassKV($class, $field, $local_id);

        if (!empty($map)) {
            return $map;
        }

        common_debug("Mapping Yammer $field {$orig_id} to local $field {$local_id}");

        $map = new $class();

        $map->id = $orig_id;
        $map->$field = $local_id;
        $map->created = common_sql_now();

        $map->insert();

        return $map;
    }
}


code:
<?php
/**
 * Data class for remembering Yammer import mappings
 *
 * PHP version 5
 *
 * @category Data
 * @package  StatusNet
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * 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);
}

class Yammer_group extends Yammer_common
{
    public $__table = 'yammer_group'; // table name
    public $__field = 'group_id';     // field to map to
    public $group_id;                 // int

    /**
     * Return schema definition to set this table up in onCheckSchema
     */
    static function schemaDef()
    {
        return self::doSchemaDef('group_id');
    }

    /**
     * Save a mapping between a remote Yammer and local imported group.
     *
     * @param integer $orig_id ID of the notice in Yammer
     * @param integer $group_id ID of the status in StatusNet
     *
     * @return Yammer_group new object for this value
     */
    static function record($orig_id, $group_id)
    {
        return self::doRecord('Yammer_group', 'group_id', $orig_id, $group_id);
    }
}


code:
<?php
/**
 * Data class for remembering Yammer import mappings
 *
 * PHP version 5
 *
 * @category Data
 * @package  StatusNet
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * 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);
}

class Yammer_notice extends Yammer_common
{
    public $__table = 'yammer_notice'; // table name
    public $__field = 'notice_id';     // field to map to
    public $notice_id;                 // int

    /**
     * Return schema definition to set this table up in onCheckSchema
     */
    static function schemaDef()
    {
        return self::doSchemaDef('notice_id');
    }

    /**
     * Save a mapping between a remote Yammer and local imported notice.
     *
     * @param integer $orig_id ID of the notice in Yammer
     * @param integer $notice_id ID of the status in StatusNet
     *
     * @return Yammer_notice new object for this value
     */
    static function record($orig_id, $notice_id)
    {
        return self::doRecord('Yammer_notice', 'notice_id', $orig_id, $notice_id);
    }
}


code:
<?php
/**
 * Data class for remembering Yammer import mappings
 *
 * PHP version 5
 *
 * @category Data
 * @package  StatusNet
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * 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);
}

/**
 * Temporary storage for imported Yammer messages between fetching and saving
 * as local notices.
 *
 * The Yammer API only allows us to page down from the most recent items; in
 * order to start saving the oldest notices first, we have to pull them all
 * down in reverse chronological order, then go back over them from oldest to
 * newest and actually save them into our notice table.
 */
class Yammer_notice_stub extends Managed_DataObject
{
    public $__table = 'yammer_notice_stub'; // table name
    public $id;                             // int  primary_key not_null
    public $json_data;                      // text
    public $created;                        // datetime

    /**
     * Return schema definition to set this table up in onCheckSchema
     */
    static function schemaDef()
    {
        return array(new ColumnDef('id', 'bigint', null,
                                   false, 'PRI'),
                     new ColumnDef('json_data', 'text', null,
                                   false),
                     new ColumnDef('created', 'datetime', null,
                                   false));
    }

    /**
     * return table definition for DB_DataObject
     *
     * DB_DataObject needs to know something about the table to manipulate
     * instances. This method provides all the DB_DataObject needs to know.
     *
     * @return array array of column definitions
     */
    function table()
    {
        return array('id'           => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
                     'json_data'    => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
                     'created'      => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
    }

    /**
     * return key definitions for DB_DataObject
     *
     * DB_DataObject needs to know about keys that the table has, since it
     * won't appear in StatusNet's own keys list. In most cases, this will
     * simply reference your keyTypes() function.
     *
     * @return array list of key field names
     */

    function keys()
    {
        return array_keys($this->keyTypes());
    }

    /**
     * return key definitions for Memcached_DataObject
     *
     * Our caching system uses the same key definitions, but uses a different
     * method to get them. This key information is used to store and clear
     * cached data, so be sure to list any key that will be used for static
     * lookups.
     *
     * @return array associative array of key definitions, field name to type:
     *         'K' for primary key: for compound keys, add an entry for each component;
     *         'U' for unique keys: compound keys are not well supported here.
     */
    function keyTypes()
    {
        return array('id' => 'K');
    }

    /**
     * Magic formula for non-autoincrementing integer primary keys
     *
     * If a table has a single integer column as its primary key, DB_DataObject
     * assumes that the column is auto-incrementing and makes a sequence table
     * to do this incrementation. Since we don't need this for our class, we
     * overload this method and return the magic formula that DB_DataObject needs.
     *
     * @return array magic three-false array that stops auto-incrementing.
     */
    function sequenceKey()
    {
        return array(false, false, false);
    }

    /**
     * Decode the stored data structure.
     *
     * @return mixed
     */
    public function getData()
    {
        return json_decode($this->json_data, true);
    }

    /**
     * Save the native Yammer API representation of a message for the pending
     * import. Since they come in in reverse chronological order, we need to
     * record them all as stubs and then go through from the beginning and
     * save them as native notices, or we'll lose ordering and threading
     * data.
     *
     * @param integer $orig_id ID of the notice on Yammer
     * @param array $data the message record fetched out of Yammer API returnd data
     *
     * @return Yammer_notice_stub new object for this value
     */

    static function record($orig_id, $data)
    {
        common_debug("Recording Yammer message stub {$orig_id} for pending import...");

        $stub = new Yammer_notice_stub();

        $stub->id = $orig_id;
        $stub->json_data = json_encode($data);
        $stub->created = common_sql_now();

        $stub->insert();

        return $stub;
    }
}


code:
<?php
/**
 * Data class for remembering Yammer import state
 *
 * PHP version 5
 *
 * @category Data
 * @package  StatusNet
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * 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);
}

class Yammer_state extends Managed_DataObject
{
    public $__table = 'yammer_state'; // table name
    public $id;                       // int  primary_key not_null
    public $state;                    // import state key
    public $last_error;               // text of last-encountered error, if any
    public $oauth_token;              // actual oauth token! clear when import is done?
    public $oauth_secret;             // actual oauth secret! clear when import is done?
    public $users_page;               // last page of users we've fetched
    public $groups_page;              // last page of groups we've fetched
    public $messages_oldest;          // oldest message ID we've fetched
    public $messages_newest;          // newest message ID we've imported
    public $created;                  // datetime
    public $modified;                 // datetime

    /**
     * Return schema definition to set this table up in onCheckSchema
     */
    static function schemaDef()
    {
        return array(new ColumnDef('id', 'int', null,
                                   false, 'PRI'),
                     new ColumnDef('state', 'text'),
                     new ColumnDef('last_error', 'text'),
                     new ColumnDef('oauth_token', 'text'),
                     new ColumnDef('oauth_secret', 'text'),
                     new ColumnDef('users_page', 'int'),
                     new ColumnDef('groups_page', 'int'),
                     new ColumnDef('messages_oldest', 'bigint'),
                     new ColumnDef('messages_newest', 'bigint'),
                     new ColumnDef('created', 'datetime'),
                     new ColumnDef('modified', 'datetime'));
    }

    /**
     * return table definition for DB_DataObject
     *
     * DB_DataObject needs to know something about the table to manipulate
     * instances. This method provides all the DB_DataObject needs to know.
     *
     * @return array array of column definitions
     */
    function table()
    {
        return array('id'              => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
                     'state'           => DB_DATAOBJECT_STR,
                     'last_error'      => DB_DATAOBJECT_STR,
                     'oauth_token'     => DB_DATAOBJECT_STR,
                     'oauth_secret'    => DB_DATAOBJECT_STR,
                     'users_page'      => DB_DATAOBJECT_INT,
                     'groups_page'     => DB_DATAOBJECT_INT,
                     'messages_oldest' => DB_DATAOBJECT_INT,
                     'messages_newest' => DB_DATAOBJECT_INT,
                     'created'         => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
    }

    /**
     * return key definitions for DB_DataObject
     *
     * DB_DataObject needs to know about keys that the table has, since it
     * won't appear in StatusNet's own keys list. In most cases, this will
     * simply reference your keyTypes() function.
     *
     * @return array list of key field names
     */
    function keys()
    {
        return array_keys($this->keyTypes());
    }

    /**
     * return key definitions for Memcached_DataObject
     *
     * Our caching system uses the same key definitions, but uses a different
     * method to get them. This key information is used to store and clear
     * cached data, so be sure to list any key that will be used for static
     * lookups.
     *
     * @return array associative array of key definitions, field name to type:
     *         'K' for primary key: for compound keys, add an entry for each component;
     *         'U' for unique keys: compound keys are not well supported here.
     */
    function keyTypes()
    {
        return array('id' => 'K');
    }

    /**
     * Magic formula for non-autoincrementing integer primary keys
     *
     * If a table has a single integer column as its primary key, DB_DataObject
     * assumes that the column is auto-incrementing and makes a sequence table
     * to do this incrementation. Since we don't need this for our class, we
     * overload this method and return the magic formula that DB_DataObject needs.
     *
     * @return array magic three-false array that stops auto-incrementing.
     */
    function sequenceKey()
    {
        return array(false, false, false);
    }
}


code:
<?php
/**
 * Data class for remembering Yammer import mappings
 *
 * PHP version 5
 *
 * @category Data
 * @package  StatusNet
 * @author   Brion Vibber <brion@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * 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);
}

class Yammer_user extends Yammer_common
{
    public $__table = 'yammer_user'; // table name
    public $__field = 'user_id';     // field to map to
    public $user_id;                 // int

    /**
     * Return schema definition to set this table up in onCheckSchema
     */
    static function schemaDef()
    {
        return self::doSchemaDef('user_id');
    }

    /**
     * Save a mapping between a remote Yammer and local imported user.
     *
     * @param integer $orig_id ID of the notice in Yammer
     * @param integer $user_id ID of the status in StatusNet
     *
     * @return Yammer_user new object for this value
     */
    static function record($orig_id, $user_id)
    {
        return self::doRecord('Yammer_user', 'user_id', $orig_id, $user_id);
    }
}


code:
.yammer-import {
    padding: 16px;
}

.import-step {
    padding: 8px;
}
.import-label {
    font-weight: bold;
}
.import-status {
    margin-left: 20px;
    padding-left: 20px;
}

.waiting {
    color: #888;
}

.progress {
    background-color: white;
    border: solid 1px blue;
    border-radius: 8px;
    -moz-border-radius: 8px;
    -webkit-border-radius: 8px;
    -opera-border-radius: 8px;
}

.progress .import-label {
    color: blue;
}

.progress .import-status {
    background-image: url(icon_processing.gif);
    background-repeat: no-repeat;
}

.complete {
    color: black;
}

.complete .import-status {
    background-image: url(done.png);
    background-repeat: no-repeat;
}

.import-step-done .import-status {
    /* override */
    background: none !important;
}

.magiclink {
    margin-left: 40px;
}

fieldset.import-error {
    margin-top: 12px;
    margin-bottom: 0px !important;
    background-color: #fee !important;
}


code:
<?php

class YammerApikeyForm extends Form
{
    private $runner;

    function __construct($out)
    {
        parent::__construct($out);
        $this->runner = $runner;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */

    function id()
    {
        return 'yammer-apikey-form';
    }

    /**
     * class of the form
     *
     * @return string of the form class
     */

    function formClass()
    {
        return 'form_yammer_apikey form_settings';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */

    function action()
    {
        return common_local_url('yammeradminpanel');
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend for adding details to connect to a remote Yammer API.
        $this->out->element('legend', null, _m('Yammer API registration'));
    }

    /**
     * Data elements of the form
     *
     * @return void
     */

    function formData()
    {
        $this->out->hidden('subaction', 'apikey');

        $this->out->elementStart('fieldset');

        $this->out->elementStart('p');
        // TRANS: Explanation of what needs to be done to connect to a Yammer network.
        $this->out->text(_m('Before we can connect to your Yammer network, ' .
                            'you will need to register the importer as an ' .
                            'application authorized to pull data on your behalf. ' .
                            'This registration will work only for your own network. ' .
                            'Follow this link to register the app at Yammer; ' .
                            'you will be prompted to log in if necessary:'));
        $this->out->elementEnd('p');

        $this->out->elementStart('p', array('class' => 'magiclink'));
        $this->out->element('a',
            array('href' => 'https://www.yammer.com/client_applications/new',
                  'target' => '_blank'),
            // TRANS: Link description to a Yammer application registration form.
            _m('Open Yammer application registration form'));
        $this->out->elementEnd('p');

        // TRANS: Instructions.
        $this->out->element('p', array(), _m('Copy the consumer key and secret you are given into the form below:'));

        $this->out->elementStart('ul', array('class' => 'form_data'));
        $this->out->elementStart('li');
        // TRANS: Field label for a Yammer consumer key.
        $this->out->input('consumer_key', _m('Consumer key:'), common_config('yammer', 'consumer_key'));
        $this->out->elementEnd('li');
        $this->out->elementStart('li');
        // TRANS: Field label for a Yammer consumer secret.
        $this->out->input('consumer_secret', _m('Consumer secret:'), common_config('yammer', 'consumer_secret'));
        $this->out->elementEnd('li');
        $this->out->elementEnd('ul');

        // TRANS: Button text for saving a Yammer API registration.
        $this->out->submit('submit', _m('BUTTON','Save'),
                           // TRANS: Button title for saving a Yammer API registration.
                           'submit', null, _m('Save the entered consumer key and consumer secret.'));

        $this->out->elementEnd('fieldset');
    }

    /**
     * Action elements
     *
     * @return void
     */

    function formActions()
    {
    }
}


code:
<?php

class YammerAuthInitForm extends Form
{
    /**
     * ID of the form
     *
     * @return int ID of the form
     */

    function id()
    {
        return 'yammer-auth-init-form';
    }

    /**
     * class of the form
     *
     * @return string of the form class
     */

    function formClass()
    {
        return 'form_yammer_auth_init form_settings';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */

    function action()
    {
        return common_local_url('yammeradminpanel');
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend.
        $this->out->element('legend', null, _m('Connect to Yammer'));
    }

    /**
     * Data elements of the form
     *
     * @return void
     */

    function formData()
    {
        $this->out->hidden('subaction', 'authinit');

        $this->out->elementStart('fieldset');
        // TRANS: Button text for starting Yammer authentication.
        $this->out->submit('submit', _m('BUTTON','Start authentication'),
                           // TRANS: Button title for starting Yammer authentication.
                           'submit', null, _m('Request authorization to connect to a Yammer account.'));
        // TRANS: Button text for starting changing a Yammer API key.
        $this->out->submit('change-apikey', _m('BUTTON','Change API key'));
        $this->out->elementEnd('fieldset');
    }

    /**
     * Action elements
     *
     * @return void
     */

    function formActions()
    {
    }
}


code:
<?php

class YammerAuthVerifyForm extends Form
{
    private $runner;

    function __construct($out, YammerRunner $runner)
    {
        parent::__construct($out);
        $this->runner = $runner;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */

    function id()
    {
        return 'yammer-auth-verify-form';
    }

    /**
     * class of the form
     *
     * @return string of the form class
     */

    function formClass()
    {
        return 'form_yammer_auth_verify form_settings';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */

    function action()
    {
        return common_local_url('yammeradminpanel');
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend.
        $this->out->element('legend', null, _m('Connect to Yammer'));
    }

    /**
     * Data elements of the form
     *
     * @return void
     */

    function formData()
    {
        $this->out->hidden('subaction', 'authverify');

        $this->out->elementStart('fieldset');

        $this->out->elementStart('p');
        // TRANS: Form instructions.
        $this->out->text(_m('Follow this link to confirm authorization at Yammer; you will be prompted to log in if necessary:'));
        $this->out->elementEnd('p');

        // iframe would be nice to avoid leaving -- since they don't seem to have callback url O_O
        /*
        $this->out->element('iframe', array('id' => 'yammer-oauth',
                                            'src' => $this->runner->getAuthUrl()));
        */
        // yeah, it ignores the callback_url
        // soo... crappy link. :(

        $this->out->elementStart('p', array('class' => 'magiclink'));
        $this->out->element('a',
            array('href' => $this->runner->getAuthUrl(),
                  'target' => '_blank'),
            // TRANS: Link description for a link in an external Yammer system.
            _m('Open Yammer authentication window'));
        $this->out->elementEnd('p');

        // TRANS: Form instructions.
        $this->out->element('p', array(), _m('Copy the verification code you are given below:'));

        $this->out->elementStart('ul', array('class' => 'form_data'));
        $this->out->elementStart('li');
        // TRANS: Field label.
        $this->out->input('verify_token', _m('Verification code:'));
        $this->out->elementEnd('li');
        $this->out->elementEnd('ul');

        // TRANS: Button text for saving  Yammer authorisation data and starting Yammer import.
        $this->out->submit('submit', _m('BUTTON','Continue'),
                           // TRANS: Button title for saving  Yammer authorisation data and starting Yammer import.
                           'submit', null, _m('Save the verification code and begin import.'));
        $this->out->elementEnd('fieldset');
    }

    /**
     * Action elements
     *
     * @return void
     */

    function formActions()
    {
    }
}


code:
<?php

class YammerProgressForm extends Form
{
    /**
     * ID of the form
     *
     * @return string ID of the form
     */
    function id()
    {
        return 'yammer-progress-form';
    }

    /**
     * class of the form
     *
     * @return string class of the form
     */
    function formClass()
    {
        $classes = array('form_settings');
        $runner = YammerRunner::init();
        if ($runner->lastError()) {
            $classes[] = 'import-error';
        } else if ($runner->state() == 'done') {
            $classes[] = 'import-done';
        } else {
            $classes[] = 'import-progress';
        }
        return implode(' ', $classes);
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('yammeradminpanel');
    }

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        $this->out->hidden('subaction', 'progress');

        $runner = YammerRunner::init();

        $error = $runner->lastError();
        $userCount = $runner->countUsers();
        $groupCount = $runner->countGroups();
        $fetchedCount = $runner->countFetchedNotices();
        $savedCount = $runner->countSavedNotices();

        $labels = array(
            'init' => array(
                // TRANS: Field label for a Yammer import initialise step.
                'label' => _m('Initialize'),
                // TRANS: "In progress" description.
                'progress' => _m('No import running'),
                // TRANS: "Complete" description for initialize state.
                'complete' => _m('Initiated Yammer server connection...'),
            ),
            'requesting-auth' => array(
                // TRANS: Field label for a Yammer import connect step.
                'label' => _m('Connect to Yammer'),
                // TRANS: "In progress" description.
                'progress' => _m('Awaiting authorization...'),
                // TRANS: "Complete" description for connect state.
                'complete' => _m('Connected.'),
            ),
            'import-users' => array(
                // TRANS: Field label for a Yammer user import users step.
                'label' => _m('Import user accounts'),
                // TRANS: "In progress" description.
                // TRANS: %d is the number of users to be imported.
                'progress' => sprintf(_m('Importing %d user...',
                                         'Importing %d users...',
                                         $userCount),
                                      $userCount),
                // TRANS: "Complete" description for step.
                // TRANS: %d is the number of users imported.
                'complete' => sprintf(_m('Imported %d user.',
                                         'Imported %d users.',
                                         $userCount),
                                      $userCount),
            ),
            'import-groups' => array(
                // TRANS: Field label for a Yammer group import step.
                'label' => _m('Import user groups'),
                // TRANS: "In progress" description.
                // TRANS: %d is the number of groups to be imported.
                'progress' => sprintf(_m('Importing %d group...',
                                         'Importing %d groups...',
                                         $groupCount),
                                      $groupCount),
                // TRANS: "Complete" description for step.
                // TRANS: %d is the number of groups imported.
                'complete' => sprintf(_m('Imported %d group.',
                                         'Imported %d groups.',
                                         $groupCount),
                                      $groupCount),
            ),
            'fetch-messages' => array(
                // TRANS: Field label for a Yammer import prepare notices step.
                'label' => _m('Prepare public notices for import'),
                // TRANS: "In progress" description.
                // TRANS: %d is the number of notices to be prepared for import.
                'progress' => sprintf(_m('Preparing %d notice...',
                                         'Preparing %d notices...',
                                         $fetchedCount),
                                      $fetchedCount),
                // TRANS: "Complete" description for step.
                // TRANS: %d is the number of notices prepared for import.
                'complete' => sprintf(_m('Prepared %d notice.',
                                         'Prepared %d notices.',
                                         $fetchedCount),
                                      $fetchedCount),
            ),
            'save-messages' => array(
                // TRANS: Field label for a Yammer import notices step.
                'label' => _m('Import public notices'),
                // TRANS: "In progress" description.
                // TRANS: %d is the number of notices to be imported.
                'progress' => sprintf(_m('Importing %d notice...',
                                         'Importing %d notices...',
                                         $savedCount),
                                      $savedCount),
                // TRANS: "Complete" description for step.
                // TRANS: %d is the number of notices imported.
                'complete' => sprintf(_m('Imported %d notice.',
                                         'Imported %d notices.',
                                         $savedCount),
                                      $savedCount),
            ),
            'done' => array(
                // TRANS: Field label for a Yammer import done step.
                'label' => _m('Done'),
                // TRANS: "In progress" description for done step.
                'progress' => sprintf(_m('Import is complete!')),
                // TRANS: "Complete" description for done step.
                'complete' => sprintf(_m('Import is complete!')),
            )
        );
        $steps = array_keys($labels);
        $currentStep = array_search($runner->state(), $steps);

        $classes = array('yammer-import');
        if ($error) {
            $classes[] = 'yammer-error';
        } else {
            $classes[] = 'yammer-running';
        }
        $this->out->elementStart('fieldset', array('class' => implode(' ', $classes)));
        // TRANS: Fieldset legend.
        $this->out->element('legend', array(), _m('Import status'));
        foreach ($steps as $step => $state) {
            if ($state == 'init') {
                // Don't show 'init', it's boring.
                continue;
            }
            if ($step < $currentStep) {
                // This step is done
                $this->progressBar($state,
                                   'complete',
                                   $labels[$state]['label'],
                                   $labels[$state]['complete']);
            } else if ($step == $currentStep) {
                // This step is in progress
                $this->progressBar($state,
                                   'progress',
                                   $labels[$state]['label'],
                                   $labels[$state]['progress'],
                                   $error);
            } else {
                // This step has not yet been done.
                $this->progressBar($state,
                                   'waiting',
                                   $labels[$state]['label'],
                                   // TRANS: Progress bar status.
                                   _m('Waiting...'));
            }
        }
        $this->out->elementEnd('fieldset');
    }

    private function progressBar($state, $class, $label, $status, $error=null)
    {
        // @fixme prettify ;)
        $this->out->elementStart('div', array('class' => "import-step import-step-$state $class"));
        $this->out->element('div', array('class' => 'import-label'), $label);
        $this->out->element('div', array('class' => 'import-status'), $status);
        if ($class == 'progress') {
            if ($state == 'done') {
                // TRANS: Button text for resetting the import state.
                $this->out->submit('abort-import', _m('Reset import state'));
            } else {
                if ($error) {
                    $this->errorBox($error);
                } else {
                    // TRANS: Button text for pausing an import.
                    $this->out->submit('pause-import', _m('Pause import'));
                }
            }
        }
        $this->out->elementEnd('div');
    }

    private function errorBox($msg)
    {
        // TRANS: Error message. %s are the error details.
        $errline = sprintf(_m('Encountered error "%s".'), $msg);
        $this->out->elementStart('fieldset', array('class' => 'import-error'));
        // TRANS: Fieldset legend for a paused import.
        $this->out->element('legend', array(), _m('Paused'));
        $this->out->element('p', array(), $errline);
        // TRANS: Button text for continuing a paused import.
        $this->out->submit('continue-import', _m('Continue'));
        // TRANS: Button text for aborting a paused import.
        $this->out->submit('abort-import', _m('Abort import'));
        $this->out->elementEnd('fieldset');
    }
}


code:
<?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/>.
 */

/**
 * Basic client class for Yammer's OAuth/JSON API.
 *
 * @package YammerImportPlugin
 * @author Brion Vibber <brion@status.net>
 */
class SNYammerClient
{
    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 {
            // TRANS: Exeption thrown when an external Yammer system gives an error.
            // TRANS: %1$s is an HTTP error code, %2$s is the error message body.
            throw new Exception(sprintf(_m('Yammer API returned HTTP code %1$s: %2$s'),
                                $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 === null) {
            common_log(LOG_ERR, "Invalid JSON response from Yammer API: " . $body);
            // TRANS: Exeption thrown when an external Yammer system an invalid JSON response.
            throw new Exception(_m('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);
    }

    /**
     * Encode a key-value pair for use in an authentication header.
     *
     * @param string $key
     * @param string $val
     * @return string
     */
    protected function authHeaderChunk($key, $val)
    {
        return urlencode($key) . '="' . urlencode($val) . '"';
    }

    /**
     * Ask the Yammer server for a request token, which can be passed on
     * to authorizeUrl() for the user to start the authentication process.
     *
     * @return array of oauth return data; should contain nice things
     */
    public function requestToken()
    {
        if ($this->token || $this->tokenSecret) {
            // TRANS: Exeption thrown when a trust relationship has already been established.
            throw new Exception(_m('Requesting a token, but already set up with a token.'));
        }
        $data = $this->fetchApi('oauth/request_token');
        $arr = array();
        parse_str($data, $arr);
        return $arr;
    }

    /**
     * Get a final access token from the verifier/PIN code provided to
     * the user from Yammer's auth pages.
     *
     * @return array of oauth return data; should contain nice things
     */
    public function accessToken($verifier)
    {
        $this->verifier = $verifier;
        $data = $this->fetchApi('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);
    }
}


code:
<?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/>.
 */

/**
 * Basic client class for Yammer's OAuth/JSON API.
 *
 * Depends on Favorite plugin
 *
 * @package YammerImportPlugin
 * @author Brion Vibber <brion@status.net>
 */
class YammerImporter
{
    protected $client;

    function __construct(SNYammerClient $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);
        $nickname = $data['options']['nickname'];

        $profileId = $this->findImportedUser($data['orig_id']);
        if ($profileId) {
            return Profile::getKV('id', $profileId);
        } else {
            $user = User::getKV('nickname', $nickname);
            if ($user) {
                common_log(LOG_WARNING, "Copying Yammer profile info onto existing user $nickname");
                $profile = $user->getProfile();
                $this->savePropertiesOn($profile, $data['options'],
                        array('fullname', 'homepage', 'bio', 'location'));
            } 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);
        $nickname = $data['options']['nickname'];

        $groupId = $this->findImportedGroup($data['orig_id']);
        if ($groupId) {
            return User_group::getKV('id', $groupId);
        } else {
            $local = Local_group::getKV('nickname', $nickname);
            if ($local) {
                common_log(LOG_WARNING, "Copying Yammer group info onto existing group $nickname");
                $group = User_group::getKV('id', $local->group_id);
                $this->savePropertiesOn($group, $data['options'],
                        array('fullname', 'description'));
            } 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;
        }
    }

    private function savePropertiesOn($target, $options, $propList)
    {
        $changed = 0;
        $orig = clone($target);
        foreach ($propList as $prop) {
            if (!empty($options[$prop]) && $target->$prop != $options[$prop]) {
                $target->$prop = $options[$prop];
                $changed++;
            }
        }
        $target->update($orig);
    }

    /**
     * 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::getKV('id', $noticeId);
        } else {
            $notice = Notice::getKV('uri', $data['options']['uri']);
            $content = $data['content'];
            $user = User::getKV($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::getKV('nickname', $nickname);
                if ($user) {
                    try {
                        Fave::addNew($user->getProfile(), $notice);
                    } catch (Exception $e) {
                        // failed, let's move to the next
                        common_debug('YammerImport failed favoriting a notice: '.$e->getMessage());
                    }
                }
            }

            // 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') {
            // TRANS: Exception thrown when a non-user item type is used, but expected.
            throw new Exception(_m('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'])) {
            // TRANS: Used as a prefix for the Yammer expertise field contents.
            $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') {
            // TRANS: Exception thrown when a non-group item type is used, but expected.
            throw new Exception(_m('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']));

        // Set some default vals or User_group::register will whine
        $options['homepage'] = '';
        $options['location'] = '';
        $options['aliases'] = array();
        // @todo FIXME: What about admin user for the group?

        $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') {
            // TRANS: Exception thrown when a non-message item type is used, but expected.
            throw new Exception(_m('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 (!empty($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::getKV('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)
    {
        $map = Yammer_user::getKV('id', $origId);
        return $map ? $map->user_id : null;
    }

    private function findImportedGroup($origId)
    {
        $map = Yammer_group::getKV('id', $origId);
        return $map ? $map->group_id : null;
    }

    private function findImportedNotice($origId)
    {
        $map = Yammer_notice::getKV('id', $origId);
        return $map ? $map->notice_id : null;
    }

    private function recordImportedUser($origId, $userId)
    {
        Yammer_user::record($origId, $userId);
    }

    private function recordImportedGroup($origId, $groupId)
    {
        Yammer_group::record($origId, $groupId);
    }

    private function recordImportedNotice($origId, $noticeId)
    {
        Yammer_notice::record($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($tz, $known)) {
            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)) {
            // TRANS: Server exception thrown when an avatar URL is invalid.
            // TRANS: %s is the invalid avatar 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');
        try {
            if (!copy($url, $temp_filename)) {
                // TRANS: Server exception thrown when an avatar could not be fetched.
                // TRANS: %s is the failed avatar URL.
                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));
        } catch (Exception $e) {
            unlink($temp_filename);
            throw $e;
        }
        // @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->getProfile());
            fclose($temp);
            return $upload;
        } catch (Exception $e) {
            fclose($temp);
            throw $e;
        }
    }
}


code:
<?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);
}

/**
 * Queue handler for bumping the next chunk of Yammer import activity!
 *
 * @package YammerImportPlugin
 * @author Brion Vibber <brion@status.net>
 */
class YammerQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'yammer';
    }

    function handle($notice)
    {
        $runner = YammerRunner::init();
        if ($runner->hasWork()) {
            try {
                if ($runner->iterate()) {
                    if ($runner->hasWork()) {
                        // More to do? Shove us back on the queue...
                        $runner->startBackgroundImport();
                    }
                }
            } catch (Exception $e) {
                try {
                    $runner->recordError($e->getMessage());
                } catch (Exception $f) {
                    common_log(LOG_ERR, "Error while recording error in Yammer background import: " . $e->getMessage() . " " . $f->getMessage());
                }
            }
        } else {
            // We're done!
            common_log(LOG_INFO, "Yammer import has no work to do at this time; discarding.");
        }
        return true;
    }
}