code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * A single list item for showing in a message list
 *
 * PHP version 5
 *
 * 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  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * A single item in a message list
 *
 * @category  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
abstract class MessageListItem extends Widget
{
    var $message;

    /**
     * Constructor
     *
     * @param HTMLOutputter $out     Output context
     * @param Message       $message Message to show
     */
    function __construct($out, $message)
    {
        parent::__construct($out);
        $this->message = $message;
    }

    /**
     * Show the widget
     *
     * @return void
     */
    function show()
    {
        $this->out->elementStart('li', array('class' => 'h-entry notice',
                                             'id' => 'message-' . $this->message->id));

        $profile = $this->getMessageProfile();

        $this->out->elementStart('a', array('href' => $profile->profileurl,
                                            'class' => 'p-author'));
        $avatarUrl = $profile->avatarUrl(AVATAR_STREAM_SIZE);
        $this->out->element('img', array('src' => $avatarUrl,
                                         'class' => 'avatar u-photo',
                                         'width' => AVATAR_STREAM_SIZE,
                                         'height' => AVATAR_STREAM_SIZE,
                                         'alt' => $profile->getBestName()));
        $this->out->element('span', array('class' => 'nickname fn'), $profile->getNickname());
        $this->out->elementEnd('a');

        // FIXME: URL, image, video, audio
        $this->out->elementStart('div', array('class' => 'e-content'));
        $this->out->raw($this->message->rendered);
        $this->out->elementEnd('div');

        $messageurl = common_local_url('showmessage',
                                       array('message' => $this->message->id));

        // XXX: we need to figure this out better. Is this right?
        if (strcmp($this->message->uri, $messageurl) != 0 &&
            preg_match('/^http/', $this->message->uri)) {
            $messageurl = $this->message->uri;
        }

        $this->out->elementStart('div', 'entry-metadata');
        $this->out->elementStart('a', array('rel' => 'bookmark',
                                            'class' => 'timestamp',
                                            'href' => $messageurl));
        $dt = common_date_iso8601($this->message->created);
        $this->out->element('time', array('class' => 'dt-published',
                                          'datetime' => common_date_iso8601($this->message->created),
                                          // TRANS: Timestamp title (tooltip text) for NoticeListItem
                                          'title' => common_exact_date($this->message->created)),
                            common_date_string($this->message->created));
        $this->out->elementEnd('a');

        if ($this->message->source) {
            $this->out->elementStart('span', 'source');
            // FIXME: bad i18n. Device should be a parameter (from %s).
            // TRANS: Followed by notice source (usually the client used to send the notice).
            $this->out->text(_('from'));
            $this->showSource($this->message->source);
            $this->out->elementEnd('span');
        }
        $this->out->elementEnd('div');

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

    /**
     * Dummy method. Serves no other purpose than to make strings available used
     * in self::showSource() through xgettext.
     *
     * @return void
     */
    function messageListItemDummyMessages()
    {
        // A dummy array with messages. These will get extracted by xgettext and
        // are used in self::showSource().
        $dummy_messages = array(
            // TRANS: A possible notice source (web interface).
            _m('SOURCE','web'),
            // TRANS: A possible notice source (XMPP).
            _m('SOURCE','xmpp'),
            // TRANS: A possible notice source (e-mail).
            _m('SOURCE','mail'),
            // TRANS: A possible notice source (OpenMicroBlogging).
            _m('SOURCE','omb'),
            // TRANS: A possible notice source (Application Programming Interface).
            _m('SOURCE','api'),
        );
    }

    /**
     * Show the source of the message
     *
     * Returns either the name (and link) of the API client that posted the notice,
     * or one of other other channels.
     *
     * @param string $source the source of the message
     *
     * @return void
     */
    function showSource($source)
    {
        $source_name = _m('SOURCE',$source);
        switch ($source) {
        case 'web':
        case 'xmpp':
        case 'mail':
        case 'omb':
        case 'api':
            $this->out->element('span', 'device', $source_name);
            break;
        default:
            $ns = Notice_source::getKV($source);
            if ($ns) {
                $this->out->elementStart('span', 'device');
                $this->out->element('a', array('href' => $ns->url,
                                               'rel' => 'external'),
                                    $ns->name);
                $this->out->elementEnd('span');
            } else {
                $this->out->element('span', 'device', $source_name);
            }
            break;
        }
        return;
    }

    /**
     * Return the profile to show in the message item
     *
     * Overridden in sub-classes to show sender, receiver, or whatever
     *
     * @return Profile profile to show avatar and name of
     */
    abstract function getMessageProfile();
}


code:
<?php
/**
 * DirectionDetector plugin, detects notices with RTL content & sets RTL
 * style for them.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.	If not, see <http://www.gnu.org/licenses/>.
 *
 * @category     Plugin
 * @package      StatusNet
 * @author		 Behrooz shabani (everplays) - <behrooz@rock.com>
 * @copyright    2009-2010 Behrooz shabani
 * @license      http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 *
 */

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

define('DIRECTIONDETECTORPLUGIN_VERSION', '0.2.0');

class DirectionDetectorPlugin extends Plugin {
    /**
     * SN plugin API, here we will make changes on rendered column
     *
     * @param object $notice notice is going to be saved
     */
    public function onStartNoticeSave($notice){
        if(!preg_match('//', $notice->rendered) && self::isRTL($notice->content))
            $notice->rendered = ''.$notice->rendered.'';
        return true;
    }

    /**
     * is passed string a rtl content or not
     *
     * @param string $content
     * @return boolean
     */
    public static function isRTL($content){
        $content = self::getClearText($content);
        $words = explode(' ', $content);
        $rtl = 0;
        foreach($words as $str)
            if(self::startsWithRTLCharacter($str))
                $rtl++;
            else
                $rtl--;
        if($rtl>0)// if number of rtl words is more than ltr words so it's a rtl content
            return true;
        elseif($rtl==0)
            // check first word again
            return self::startsWithRTLCharacter($words[0]);
        return false;
    }

    /**
     * checks that passed string starts with a RTL language or not
     *
     * @param string $str
     * @return boolean
     */
    public static function startsWithRTLCharacter($str){
        if (strlen($str) < 1) {
            return false;
        }
        if( is_array($cc = self::utf8ToUnicode(mb_substr($str, 0, 1, 'utf-8'))) )
            $cc = $cc[0];
        else
            return false;
        if($cc>=1536 && $cc<=1791) // arabic, persian, urdu, kurdish, ...
            return true;
        if($cc>=65136 && $cc<=65279) // arabic peresent 2
            return true;
        if($cc>=64336 && $cc<=65023) // arabic peresent 1
            return true;
        if($cc>=1424 && $cc<=1535) // hebrew
            return true;
        if($cc>=64256 && $cc<=64335) // hebrew peresent
            return true;
        if($cc>=1792 && $cc<=1871) // Syriac
            return true;
        if($cc>=1920 && $cc<=1983) // Thaana
            return true;
        if($cc>=1984 && $cc<=2047) // NKo
            return true;
        if($cc>=11568 && $cc<=11647) // Tifinagh
            return true;
        return false;
    }

    /**
     * clears text from replys, tags, groups, reteets & whitespaces
     *
     * @param string $str
     * @return string
     */
    private static function getClearText($str){
        $str = preg_replace('/@[^ ]+|![^ ]+|#[^ ]+/u', '', $str); // reply, tag, group
        $str = preg_replace('/^RT[: ]{1}| RT | RT: |^RD[: ]{1}| RD | RD: |[♺♻:]/u', '', $str); // redent, retweet
        $str = preg_replace("/[ \r\t\n]+/", ' ', trim($str)); // remove spaces
        return $str;
    }

    /**
     * adds javascript to do same thing on input textarea
     *
     * @param Action $action
     */
    function onEndShowScripts($action){
        if (common_logged_in()) {
            $action->script($this->path('jquery.DirectionDetector.js'));
        }
    }

    /**
     * Takes an UTF-8 string and returns an array of ints representing the
     * Unicode characters. Astral planes are supported ie. the ints in the
     * output can be > 0xFFFF. O$ccurrances of the BOM are ignored. Surrogates
     * are not allowed.
     *
     * @param string $str
     * @return mixed array of ints, or false on invalid input
     */
    private static function utf8ToUnicode($str){
        $mState = 0;	   // cached expected number of octets after the current octet
                   // until the beginning of the next UTF8 character sequence
        $mUcs4	= 0;     // cached Unicode character
        $mBytes = 1;	   // cached expected number of octets in the current sequence
        $out = array();
        $len = strlen($str);

        for($i = 0; $i < $len; $i++) {
            $in = ord($str{$i});
            if (0 == $mState) {
                // When mState is zero we expect either a US-ASCII character or a
                // multi-octet sequence.
                if (0 == (0x80 & ($in))) {
                    // US-ASCII, pass straight through.
                    $out[] = $in;
                    $mBytes = 1;
                } elseif (0xC0 == (0xE0 & ($in))) {
                    // First octet of 2 octet sequence
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x1F) << 6;
                    $mState = 1;
                    $mBytes = 2;
                } elseif (0xE0 == (0xF0 & ($in))) {
                    // First octet of 3 octet sequence
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x0F) << 12;
                    $mState = 2;
                    $mBytes = 3;
                } elseif (0xF0 == (0xF8 & ($in))) {
                    // First octet of 4 octet sequence
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x07) << 18;
                    $mState = 3;
                    $mBytes = 4;
                } elseif (0xF8 == (0xFC & ($in))) {
                    /* First octet of 5 octet sequence.
                     *
                     * This is illegal because the encoded codepoint must be either
                     * (a) not the shortest form or
                     * (b) outside the Unicode range of 0-0x10FFFF.
                     * Rather than trying to resynchronize, we will carry on until the end
                     * of the sequence and let the later error handling code catch it.
                     */
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x03) << 24;
                    $mState = 4;
                    $mBytes = 5;
                } elseif (0xFC == (0xFE & ($in))) {
                    // First octet of 6 octet sequence, see comments for 5 octet sequence.
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 1) << 30;
                    $mState = 5;
                    $mBytes = 6;
                } else {
                    /* Current octet is neither in the US-ASCII range nor a legal first
                     * octet of a multi-octet sequence.
                     */
                    return false;
                }
            } else {
                // When mState is non-zero, we expect a continuation of the multi-octet
                // sequence
                if (0x80 == (0xC0 & ($in))) {
                    // Legal continuation.
                    $shift = ($mState - 1) * 6;
                    $tmp = $in;
                    $tmp = ($tmp & 0x0000003F) << $shift;
                    $mUcs4 |= $tmp;
                    if (0 == --$mState) {
                        /* End of the multi-octet sequence. mUcs4 now contains the final
                         * Unicode codepoint to be output
                         *
                         * Check for illegal sequences and codepoints.
                         */
                        // From Unicode 3.1, non-shortest form is illegal
                        if	(
                                ((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
                                ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
                                ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
                                (4 < $mBytes) ||
                                // From Unicode 3.2, surrogate characters are illegal
                                (($mUcs4 & 0xFFFFF800) == 0xD800) ||
                                // Codepoints outside the Unicode range are illegal
                                ($mUcs4 > 0x10FFFF)
                            ){
                            return false;
                        }
                        if (0xFEFF != $mUcs4) {
                            $out[] = $mUcs4;
                        }
                        //initialize UTF8 cache
                        $mState = 0;
                        $mUcs4  = 0;
                        $mBytes = 1;
                    }
                } else {
                    /* ((0xC0 & (*in) != 0x80) && (mState != 0))
                     *
                     * Incomplete multi-octet sequence.
                     */
                    return false;
                }
            }
        }
        return $out;
    }

    /**
     * plugin details
     */
    function onPluginVersion(&$versions){
        $url = 'http://status.net/wiki/Plugin:DirectionDetector';

        $versions[] = array(
            'name' => 'Direction detector',
            'version' => DIRECTIONDETECTORPLUGIN_VERSION,
            'author' => 'Behrooz Shabani',
            'homepage' => $url,
            // TRANS: Plugin description.
            'rawdescription' => _m('Shows notices with right-to-left content in correct direction.')
        );
        return true;
    }
}

/*
// Example:
var_dump(DirectionDetectorPlugin::isRTL('RT @everplays ♺: دادگاه به دليل عدم حضور وکلای متهمان بنا بر اصل ١٣٥ قانون اساسی غير قانونی است')); // true
*/

license: mit


code:
/**
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.	If not, see <http://www.gnu.org/licenses/>.
 *
 * @category     Plugin
 * @package      StatusNet
 * @author		 Behrooz shabani (everplays) - <behrooz@rock.com>
 * @copyright    2009-2010 Behrooz shabani
 * @license      http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 *
 */

(function($){
	$.fn.isRTL = function(str){
		if(typeof str != typeof "" || str.length<1)
			return false;
		var cc = str.charCodeAt(0);
		if(cc>=1536 && cc<=1791) // arabic, persian, ...
			return true;
		if(cc>=65136 && cc<=65279) // arabic peresent 2
			return true;
		if(cc>=64336 && cc<=65023) // arabic peresent 1
			return true;
		if(cc>=1424 && cc<=1535) // hebrew
			return true;
		if(cc>=64256 && cc<=64335) // hebrew peresent
			return true;
		if(cc>=1792 && cc<=1871) // Syriac
			return true;
		if(cc>=1920 && cc<=1983) // Thaana
			return true;
		if(cc>=1984 && cc<=2047) // NKo
			return true;
		if(cc>=11568 && cc<=11647) // Tifinagh
			return true;
		return false;
	};
    var origInit = SN.Init.NoticeFormSetup;
    SN.Init.NoticeFormSetup = function(form) {
        origInit(form);
        var tArea = form.find(".notice_data-text:first");
        if (tArea.length > 0) {
            var tCleaner = new RegExp('@[^ ]+|![^ ]+|#[^ ]+|^RT[: ]{1}| RT | RT: |^RD[: ]{1}| RD | RD: |[♺♻:]+', 'g')
            var ping = function(){
                var cleaned = tArea.val().replace(tCleaner, '').replace(/^[ ]+/, '');
                if($().isRTL(cleaned))
                    tArea.css('direction', 'rtl');
                else
                    tArea.css('direction', 'ltr');
            };
            tArea.bind('keyup cut paste', function() {
                // cut/paste trigger before the change
                window.setTimeout(ping, 0);
            });
            form.bind('reset', function() {
                tArea.css('direction', 'ltr');
            });
        }
    };
})(jQuery);


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#. TRANS: Plugin description.
#: DirectionDetectorPlugin.php:256
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""


code:
# Translation of StatusNet - DirectionDetector to Asturian (asturianu)
# Exported from translatewiki.net
#
# Author: Xuacu
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: Asturian <https://translatewiki.net/wiki/Portal:ast>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ast\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Amosar los mensaxes con conteníu de drecha-a-izquierda na direición "
"afayadiza."


code:
# Translation of StatusNet - DirectionDetector to Belarusian (Taraškievica orthography) (беларуская (тарашкевіца))
# Exported from translatewiki.net
#
# Author: EugeneZelenko
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: Belarusian (Taraškievica orthography) <https://translatewiki."
"net/wiki/Portal:be-tarask>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: be-tarask\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Паказвае паведамленьні са зьместам напісаньня справа налева ў слушным "
"накірунку."


code:
# Translation of StatusNet - DirectionDetector to Breton (brezhoneg)
# Exported from translatewiki.net
#
# Author: Fulup
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: Breton <https://translatewiki.net/wiki/Portal:br>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: br\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Diskwel a ra war an tu mat ar c'hemennoù enno testennoù skrivet a-zehou da "
"gleiz."


code:
# Translation of StatusNet - DirectionDetector to German (Deutsch)
# Exported from translatewiki.net
#
# Author: The Evil IP address
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: German <https://translatewiki.net/wiki/Portal:de>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: de\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Zeigt Nachrichten mit Rechts-nach-Links-Inhalt in der korrekten Richtung an."


code:
# Translation of StatusNet - DirectionDetector to Spanish (español)
# Exported from translatewiki.net
#
# Author: Translationista
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: Spanish <https://translatewiki.net/wiki/Portal:es>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: es\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Muestra los mensajes de contenido derecha-a-izquierda en la dirección "
"correcta."


code:
# Translation of StatusNet - DirectionDetector to Basque (euskara)
# Exported from translatewiki.net
#
# Author: Artsuaga
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "Oharrak eskubitik ezkerrera erakusten ditu norabide zuzenean."


code:
# Translation of StatusNet - DirectionDetector to Finnish (suomi)
# Exported from translatewiki.net
#
# Author: Nike
# Author: XTL
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: Finnish <https://translatewiki.net/wiki/Portal:fi>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: fi\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Näyttää oikein ilmoitukset, joiden sisältö on kirjoitettu oikealta "
"vasemmalle."


code:
# Translation of StatusNet - DirectionDetector to French (français)
# Exported from translatewiki.net
#
# Author: Peter17
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:06+0000\n"
"Language-Team: French <https://translatewiki.net/wiki/Portal:fr>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: fr\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Affiche dans les bon sens les avis contenant du texte écrit de droite à "
"gauche."


code:
# Translation of StatusNet - DirectionDetector to Galician (galego)
# Exported from translatewiki.net
#
# Author: Toliño
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Galician <https://translatewiki.net/wiki/Portal:gl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: gl\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "Mostra notas con contidos de dereita á esquerda na dirección correcta."


code:
# Translation of StatusNet - DirectionDetector to Hebrew (עברית)
# Exported from translatewiki.net
#
# Author: Amire80
# Author: YaronSh
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Hebrew <https://translatewiki.net/wiki/Portal:he>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: he\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "הצגת עדכונים עם תוכן מימין לשמאל בכיוון הנכון."


code:
# Translation of StatusNet - DirectionDetector to Interlingua (interlingua)
# Exported from translatewiki.net
#
# Author: McDutchie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Interlingua <https://translatewiki.net/wiki/Portal:ia>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ia\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Monstra notas con scripto de dextra a sinistra in le direction correcte."


code:
# Translation of StatusNet - DirectionDetector to Indonesian (Bahasa Indonesia)
# Exported from translatewiki.net
#
# Author: Farras
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Indonesian <https://translatewiki.net/wiki/Portal:id>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: id\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Menampilkan pemberitahuan dengan konten kanan-ke-kiri dalam arah yang tepat."


code:
# Translation of StatusNet - DirectionDetector to Italian (italiano)
# Exported from translatewiki.net
#
# Author: Ximo17
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Mostra avvisi con contenuti da destra a sinistra nella direzione corretta."


code:
# Translation of StatusNet - DirectionDetector to Japanese (日本語)
# Exported from translatewiki.net
#
# Author: 青子守歌
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Japanese <https://translatewiki.net/wiki/Portal:ja>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ja\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "正しい方向で右から左へ表示される内容の通知を表示する。"


code:
# Translation of StatusNet - DirectionDetector to Luxembourgish (Lëtzebuergesch)
# Exported from translatewiki.net
#
# Author: Robby
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Luxembourgish <https://translatewiki.net/wiki/Portal:lb>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: lb\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Weist Matdeelungen mat Inhalt dee vu riets not lenks geschriwwen ass an där "
"richteger Richtung."


code:
# Translation of StatusNet - DirectionDetector to Macedonian (македонски)
# Exported from translatewiki.net
#
# Author: Bjankuloski06
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Macedonian <https://translatewiki.net/wiki/Portal:mk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: mk\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Ги прикажува забелешките напишани на писма од десно на лево во исправната "
"насока."


code:
# Translation of StatusNet - DirectionDetector to Norwegian Bokmål (norsk (bokmål))
# Exported from translatewiki.net
#
# Author: Nghtwlkr
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Norwegian Bokmål <https://translatewiki.net/wiki/Portal:nb>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: nb\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "Viser notiser med høyre-til-venstre-innhold i riktig retning."


code:
# Translation of StatusNet - DirectionDetector to Dutch (Nederlands)
# Exported from translatewiki.net
#
# Author: Siebrand
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Last-Translator: Siebrand Mazeland <s.mazeland@xs4all.nl>\n"
"Language-Team: Dutch <https://translatewiki.net/wiki/Portal:nl>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: nl\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Geeft mededelingen met inhoud in een van rechts naar links geschreven "
"schrift in de juiste richting weer."


code:
# Translation of StatusNet - DirectionDetector to Portuguese (português)
# Exported from translatewiki.net
#
# Author: GTNS
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:07+0000\n"
"Language-Team: Portuguese <https://translatewiki.net/wiki/Portal:pt>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: pt\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Mostra mensagens com conteúdo da direita para a esquerda na direcção "
"correcta."


code:
# Translation of StatusNet - DirectionDetector to Russian (русский)
# Exported from translatewiki.net
#
# Author: Александр Сигачёв
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:08+0000\n"
"Language-Team: Russian <https://translatewiki.net/wiki/Portal:ru>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ru\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\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: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "Правильно показывает уведомления для системы письма справа налево."


code:
# Translation of StatusNet - DirectionDetector to Tagalog (Tagalog)
# Exported from translatewiki.net
#
# Author: AnakngAraw
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:08+0000\n"
"Language-Team: Tagalog <https://translatewiki.net/wiki/Portal:tl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr ""
"Nagpapakita ng mga pabatid na may nilalamang mula-kanan-pakaliwa sa tamang "
"daan."


code:
# Translation of StatusNet - DirectionDetector 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 - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:08+0000\n"
"Language-Team: Ukrainian <https://translatewiki.net/wiki/Portal:uk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: uk\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\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: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "Показує повідомлення із письмом справа наліво у правильному напрямі."


code:
# Translation of StatusNet - DirectionDetector to Simplified Chinese (中文（简体）)
# Exported from translatewiki.net
#
# Author: Chenxiaoqino
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - DirectionDetector\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:08+0000\n"
"Language-Team: Simplified Chinese <https://translatewiki.net/wiki/Portal:zh-"
"hans>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-09-11 15:10:27+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: zh-hans\n"
"X-Message-Group: #out-statusnet-plugin-directiondetector\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Plugin description.
msgid "Shows notices with right-to-left content in correct direction."
msgstr "在内容方向为从右到左时，以相同的文字方向显示提醒。"


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Adds a user directory
 *
 * PHP version 5
 *
 * 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  Plugin
 * @package   StatusNet
 * @author    Zach Copely <zach@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

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

/**
 * Directory plugin main class
 *
 * @category  Plugin
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class DirectoryPlugin extends Plugin
{
    private $dir = null;

    /**
     * Initializer for this plugin
     *
     * @return boolean hook value; true means continue processing,
     *         false means stop.
     */
    function initialize()
    {
        return true;
    }

    /**
     * Cleanup for this plugin.
     *
     * @return boolean hook value; true means continue processing,
     *         false means stop.
     */
    function cleanup()
    {
        return true;
    }

    /**
     * Map URLs to actions
     *
     * @param URLMapper $m path-to-action mapper
     *
     * @return boolean hook value; true means continue processing,
     *         false means stop.
     */
    public function onRouterInitialized(URLMapper $m)
    {

        $m->connect(
            'directory/users/:filter/sort_by/:sort/reverse/:reverse',
            array('action' => 'userdirectory'),
            array('filter' => '[0-9a-zA-Z]|(0-9)'),
            array('sort' => '[a-z]+'),
            array('reverse' => '[0-9]')
        );

        $m->connect(
            'directory/users/:filter/sort_by/:sort',
            array('action' => 'userdirectory'),
            array('filter' => '[0-9a-zA-Z]|(0-9)'),
            array('sort' => '[a-z]+')
        );

        $m->connect(
            'directory/users/:filter',
            array('action' => 'userdirectory'),
            array('filter' => '[0-9a-zA-Z]|(0-9)')
        );

        $m->connect(
            'directory/users/sort_by/:sort/reverse/:reverse',
            array('action' => 'userdirectory'),
            array('sort' => '[a-z]+'),
            array('reverse' => '[0-9]')
        );

        $m->connect(
            'directory/users/sort_by/:sort',
            array('action' => 'userdirectory'),
            array('sort' => '[a-z]+')
        );

        $m->connect(
            'directory/users',
            array('action' => 'userdirectory')
        );

        $m->connect(
            'groups/:filter',
            array('action' => 'groupdirectory'),
            array('filter' => '[0-9a-zA-Z]|(0-9)')
        );

        $m->connect(
            'groups',
            array('action' => 'groupdirectory')
        );

        $m->connect(
            'groups/all',
            array('action' => 'groupdirectory')
        );

        return true;
    }

    /**
     * Hijack the routing (URL -> Action) for the normal directory page
     * and substitute our group directory action
     *
     * @param string $path     path to connect
     * @param array  $defaults path defaults
     * @param array  $rules    path rules
     * @param array  $result   unused
     *
     * @return boolean hook return
     */
    function onStartConnectPath(&$path, &$defaults, &$rules, &$result)
    {
        if (in_array($path, array('group', 'group/', 'groups', 'groups/'))) {
            $defaults['action'] = 'groupdirectory';
            return true;
        }
        return true;
    }

    // The following three function are to replace the existing groups
    // list page with the directory plugin's group directory page

    /**
     * Hijack the mapping (Action -> URL) and return the URL to our
     * group directory page instead of the normal groups page
     *
     * @param Action    $action     action to find a path for
     * @param array     $params     parameters to pass to the action
     * @param string    $fragment   any url fragement
     * @param boolean   $addSession whether to add session variable
     * @param string    $url        resulting URL to local resource
     *
     * @return string the local URL
     */
    function onEndLocalURL(&$action, &$params, &$fragment, &$addSession, &$url) {
        if (in_array($action, array('group', 'group/', 'groups', 'groups/'))) {
                $url = common_local_url('groupdirectory');
        }
        return true;
    }

    /**
     * Link in a styelsheet for the onboarding actions
     *
     * @param Action $action Action being shown
     *
     * @return boolean hook flag
     */
    public function onEndShowStylesheets(Action $action)
    {
        if (in_array(
            $action->trimmed('action'),
            array('userdirectory', 'groupdirectory'))
        ) {
            $action->cssLink($this->path('css/directory.css'));
        }

        return true;
    }

    /**
     * Fool the public nav into thinking it's on the regular
     * group page when it's actually on our injected group
     * directory page. This way "Groups" gets hilighted when
     * when we're on the groups directory page.
     *
     * @param type $action the current action
     *
     * @return boolean hook flag
     */
    function onStartPublicGroupNav($action)
    {
        if ($action->trimmed('action') == 'groupdirectory') {
            $action->actionName = 'groups';
        }
        return true;
    }

    /**
     * Modify the public local nav to add a link to the user directory
     *
     * @param Action $action The current action handler. Use this to
     *                       do any output.
     *
     * @return boolean hook value; true means continue processing,
     *         false means stop.
     *
     * @see Action
     */
    function onEndPublicGroupNav($nav)
    {
        // XXX: Maybe this should go under search instead?

        $actionName = $nav->action->trimmed('action');

        $nav->out->menuItem(
            common_local_url('userdirectory'),
            // TRANS: Menu item text for user directory.
            _m('MENU','Directory'),
            // TRANS: Menu item title for user directory.
            _m('User Directory.'),
            $actionName == 'userdirectory',
            'nav_directory'
        );

        return true;
    }

    /*
     * Version info
     */
    function onPluginVersion(&$versions)
    {
        $versions[] = array(
            'name' => 'Directory',
            'version' => GNUSOCIAL_VERSION,
            'author' => 'Zach Copley',
            'homepage' => 'http://status.net/wiki/Plugin:Directory',
            // TRANS: Plugin description.
            'rawdescription' => _m('Add a user directory.')
        );

        return true;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Output a group directory
 *
 * 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  Public
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 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);
}

require_once INSTALLDIR . '/lib/publicgroupnav.php';

/**
 * Group directory
 *
 * @category Directory
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class GroupdirectoryAction extends Action
{
    /**
     * The page we're on
     *
     * @var integer
     */
    public $page;

    /**
     * What to filter the search results by
     *
     * @var string
     */
    public $filter;

    /**
     * Column to sort by
     *
     * @var string
     */
    public $sort;

    /**
     * How to order search results, ascending or descending
     *
     * @var string
     */
    public $reverse;

    /**
     * Query
     *
     * @var string
     */
    public $q;

    /**
     * Title of the page
     *
     * @return string Title of the page
     */
    function title()
    {
        // @fixme: This looks kinda gross

        if ($this->filter == 'all') {
            if ($this->page != 1) {
                // TRANS: Title for group directory page. %d is a page number.
                return(sprintf(_m('Group Directory, page %d'), $this->page));
            }
            // TRANS: Title for group directory page.
            return _m('Group directory');
        } else if ($this->page == 1) {
            return sprintf(
                // TRANS: Title for group directory page when it is filtered.
                // TRANS: %s is the filter string.
                _m('Group directory - %s'),
                strtoupper($this->filter)
            );
        } else {
            return sprintf(
                // TRANS: Title for group directory page when it is filtered.
                // TRANS: %1$s is the filter string, %2$d is a page number.
                _m('Group directory - %1$s, page %2$d'),
                strtoupper($this->filter),
                $this->page
            );
        }
    }

    /**
     * Instructions for use
     *
     * @return instructions for use
     */
    function getInstructions()
    {
        // TRANS: Page instructions.
        return _m("After you join a group you can send messages to all other members\n".
                 "using the syntax \"!groupname\".\n\n".
                 "Browse groups, or search for groups by their name, location or topic.\n".
                 "Separate the terms by spaces; they must be three characters or more.") . "\n";
    }

    /**
     * Is this page read-only?
     *
     * @return boolean true
     */
    function isReadOnly($args)
    {
        return true;
    }

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    function prepare($args)
    {
        parent::prepare($args);

        $this->page    = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
        $this->filter  = $this->arg('filter', 'all');
        $this->reverse = $this->boolean('reverse');
        $this->q       = $this->trimmed('q');
        $this->sort    = $this->arg('sort', 'nickname');

        common_set_returnto($this->selfUrl());

        return true;
    }

    /**
     * Handle request
     *
     * Shows the page
     *
     * @param array $args $_REQUEST args; handled in prepare()
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);
        $this->showPage();
    }

    /**
     * Show the page notice
     *
     * Shows instructions for the page
     *
     * @return void
     */
    function showPageNotice()
    {
        $instr  = $this->getInstructions();
        $output = common_markup_to_html($instr);

        $this->elementStart('div', 'instructions');
        $this->raw($output);
        $this->elementEnd('div');
    }

    /**
     * Content area
     *
     * Shows the groups
     *
     * @return void
     */
    function showContent()
    {
        if (common_logged_in()) {
            $this->elementStart(
                'p',
                array(
                    'id' => 'new_group'
                )
            );
            $this->element(
                'a',
                array(
                    'href'  => common_local_url('newgroup'),
                    'class' => 'more'),
                    // TRANS: Link to create a new group on the group list page.
                    _m('Create a new group')
            );
            $this->elementEnd('p');
        }

        $this->showForm();

        $this->elementStart('div', array('id' => 'profile_directory'));

        // @todo FIXME: Does "All" need i18n here?
        $alphaNav = new AlphaNav($this, false, false, array('0-9', 'All'));
        $alphaNav->show();

        $group   = null;
        $group   = $this->getGroups();
        $cnt     = 0;

        if (!empty($group)) {
            $groupList = new SortableGroupList(
                $group,
                common_current_user(),
                $this
            );

            $cnt = $groupList->show();
            $group->free();

            if (0 == $cnt) {
                $this->showEmptyListMessage();
            }
        }

        $args = array();
        if (isset($this->q)) {
            $args['q'] = $this->q;
        } else {
            $args['filter'] = $this->filter;
        }

        $this->pagination(
            $this->page > 1,
            $cnt > PROFILES_PER_PAGE,
            $this->page,
            'groupdirectory',
            $args
        );

        $this->elementEnd('div');
    }

    function showForm($error=null)
    {
        $this->elementStart(
            'form',
            array(
                'method' => 'get',
                'id'     => 'form_search',
                'class'  => 'form_settings',
                'action' => common_local_url('groupdirectory')
            )
        );

        $this->elementStart('fieldset');

        // TRANS: Fieldset legend.
        $this->element('legend', null, _m('Search groups'));
        $this->elementStart('ul', 'form_data');
        $this->elementStart('li');

        // TRANS: Field label for input of one or more keywords.
        $this->input('q', _m('Keyword(s)'), $this->q);

        // TRANS: Button text for searching group directory.
        $this->submit('search', _m('BUTTON','Search'));
        $this->elementEnd('li');
        $this->elementEnd('ul');
        $this->elementEnd('fieldset');
        $this->elementEnd('form');
    }

    /*
     * Get groups filtered by the current filter, sort key,
     * sort order, and page
     */
    function getGroups()
    {
        $group = new User_group();

        $offset = ($this->page-1) * PROFILES_PER_PAGE;
        $limit  = PROFILES_PER_PAGE + 1;

        if (isset($this->q)) {

             $order = 'user_group.created ASC';

             if ($this->sort == 'nickname') {
                 if ($this->reverse) {
                     $order = 'user_group.nickname DESC';
                 } else {
                     $order = 'user_group.nickname ASC';
                 }
             } else {
                 if ($this->reverse) {
                     $order = 'user_group.created DESC';
                 }
             }

             $sql = <<< GROUP_QUERY_END
SELECT user_group.*
FROM user_group
JOIN local_group ON user_group.id = local_group.group_id
ORDER BY %s
LIMIT %d, %d
GROUP_QUERY_END;

        $cnt = 0;
        $group->query(sprintf($sql, $order, $limit, $offset));
        $group->find();

        } else {
            // User is browsing via AlphaNav
            $sort   = $this->getSortKey();

            $sql = <<< GROUP_QUERY_END
SELECT user_group.*
FROM user_group
JOIN local_group ON user_group.id = local_group.group_id
GROUP_QUERY_END;

            switch($this->filter)
            {
            case 'all':
                // NOOP
                break;
            case '0-9':
                $sql .=
                    '  AND LEFT(user_group.nickname, 1) BETWEEN \'0\' AND \'9\'';
                break;
            default:
                $sql .= sprintf(
                    ' AND LEFT(LOWER(user_group.nickname), 1) = \'%s\'',
                    $this->filter
                );
            }

            $sql .= sprintf(
                ' ORDER BY user_group.%s %s, user_group.nickname ASC LIMIT %d, %d',
                $sort,
                $this->reverse ? 'DESC' : 'ASC',
                $offset,
                $limit
            );

            $group->query($sql);
        }

        return $group;
    }

    /**
     * Filter the sort parameter
     *
     * @return string   a column name for sorting
     */
    function getSortKey()
    {
        switch ($this->sort) {
        case 'nickname':
            return $this->sort;
            break;
        case 'created':
            return $this->sort;
            break;
        default:
            return 'nickname';
        }
    }

    /**
     * Show a nice message when there's no search results
     */
    function showEmptyListMessage()
    {
        if (!empty($this->filter) && ($this->filter != 'all')) {
            $this->element(
                'p',
                'error',
                sprintf(
                    // TRANS: Empty list message for searching group directory.
                    // TRANS: %s is the search string.
                    _m('No groups starting with %s.'),
                    $this->filter
                )
            );
        } else {
            // TRANS: Empty list message for searching group directory.
            $this->element('p', 'error', _m('No results.'));
            // TRANS: Help text for searching group directory.
            $message = _m("* Make sure all words are spelled correctly.\n".
                          "* Try different keywords.\n".
                          "* Try more general keywords.\n".
                          "* Try fewer keywords.");
            $this->elementStart('div', 'help instructions');
            $this->raw(common_markup_to_html($message));
            $this->elementEnd('div');
        }
    }

    function showSections()
    {
        $gbp = new GroupsByPostsSection($this);
        $gbp->show();
        $gbm = new GroupsByMembersSection($this);
        $gbm->show();
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Output a user directory
 *
 * 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  Public
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 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);
}

require_once INSTALLDIR . '/lib/publicgroupnav.php';

/**
 * User directory
 *
 * @category Personal
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class UserdirectoryAction extends Action
{
    /**
     * The page we're on
     *
     * @var integer
     */
    public $page;

    /**
     * What to filter the search results by
     *
     * @var string
     */
    public $filter;

    /**
     * Column to sort by
     *
     * @var string
     */
    public $sort;

    /**
     * How to order search results, ascending or descending
     *
     * @var string
     */
    public $reverse;

    /**
     * Query
     *
     * @var string
     */
    public $q;

    /**
     * Title of the page
     *
     * @return string Title of the page
     */
    function title()
    {
        // @todo fixme: This looks kinda gross

        if ($this->filter == 'all') {
            if ($this->page != 1) {
                // TRANS: Page title for user directory. %d is a page number.
                return(sprintf(_m('User Directory, page %d'), $this->page));
            }
            // TRANS: Page title for user directory.
            return _m('User directory');
        } else if ($this->page == 1) {
            return sprintf(
                // TRANS: Page title for user directory. %s is the applied filter.
                _m('User directory - %s'),
                strtoupper($this->filter)
            );
        } else {
            return sprintf(
                // TRANS: Page title for user directory.
                // TRANS: %1$s is the applied filter, %2$d is a page number.
                _m('User directory - %1$s, page %2$d'),
                strtoupper($this->filter),
                $this->page
            );
        }
    }

    /**
     * Instructions for use
     *
     * @return instructions for use
     */
    function getInstructions()
    {
        // TRANS: %%site.name%% is the name of the StatusNet site.
        return _m('Search for people on %%site.name%% by their name, '
            . 'location, or interests. Separate the terms by spaces; '
            . ' they must be 3 characters or more.'
        );
    }

    /**
     * Is this page read-only?
     *
     * @return boolean true
     */
    function isReadOnly($args)
    {
        return true;
    }

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    function prepare($args)
    {
        parent::prepare($args);

        $this->page    = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
        $this->filter  = $this->arg('filter', 'all');
        $this->reverse = $this->boolean('reverse');
        $this->q       = $this->trimmed('q');
        $this->sort    = $this->arg('sort', 'nickname');

        common_set_returnto($this->selfUrl());

        return true;
    }

    /**
     * Handle request
     *
     * Shows the page
     *
     * @param array $args $_REQUEST args; handled in prepare()
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);
        $this->showPage();
    }

    /**
     * Show the page notice
     *
     * Shows instructions for the page
     *
     * @return void
     */
    function showPageNotice()
    {
        $instr  = $this->getInstructions();
        $output = common_markup_to_html($instr);

        $this->elementStart('div', 'instructions');
        $this->raw($output);
        $this->elementEnd('div');
    }

    /**
     * Content area
     *
     * Shows the list of popular notices
     *
     * @return void
     */
    function showContent()
    {
        $this->showForm();

        $this->elementStart('div', array('id' => 'profile_directory'));

        $alphaNav = new AlphaNav($this, false, false, array('0-9', 'All'));
        $alphaNav->show();

        $profile = null;
        $profile = $this->getUsers();
        $cnt     = 0;

        if (!empty($profile)) {
            $profileList = new SortableSubscriptionList(
                $profile,
                common_current_user(),
                $this
            );

            $cnt = $profileList->show();
            $profile->free();

            if (0 == $cnt) {
                $this->showEmptyListMessage();
            }
        }

        $args = array();
        if (isset($this->q)) {
            $args['q'] = $this->q;
        } elseif (isset($this->filter) && $this->filter != 'all') {
            $args['filter'] = $this->filter;
        }

        if (isset($this->sort)) {
            $args['sort'] = $this->sort;
        }
        if (!empty($this->reverse)) {
            $args['reverse'] = $this->reverse;
        }

        $this->pagination(
            $this->page > 1,
            $cnt > PROFILES_PER_PAGE,
            $this->page,
            'userdirectory',
            $args
        );

        $this->elementEnd('div');

    }

    function showForm($error=null)
    {
        $this->elementStart(
            'form',
            array(
                'method' => 'get',
                'id'     => 'form_search',
                'class'  => 'form_settings',
                'action' => common_local_url('userdirectory')
            )
        );

        $this->elementStart('fieldset');

        // TRANS: Fieldset legend.
        $this->element('legend', null, _m('Search site'));
        $this->elementStart('ul', 'form_data');
        $this->elementStart('li');

        // TRANS: Field label for user directory filter.
        $this->input('q', _m('Keyword(s)'), $this->q);

        // TRANS: Button text.
        $this->submit('search', _m('BUTTON','Search'));
        $this->elementEnd('li');
        $this->elementEnd('ul');
        $this->elementEnd('fieldset');
        $this->elementEnd('form');
    }

    /*
     * Get users filtered by the current filter, sort key,
     * sort order, and page
     */
    function getUsers()
    {
        $profile = new Profile();

        $offset = ($this->page - 1) * PROFILES_PER_PAGE;
        $limit  = PROFILES_PER_PAGE + 1;

        if (isset($this->q)) {
             // User is searching via query
             $search_engine = $profile->getSearchEngine('profile');

             $mode = 'reverse_chron';

             if ($this->sort == 'nickname') {
                 if ($this->reverse) {
                     $mode = 'nickname_desc';
                 } else {
                     $mode = 'nickname_asc';
                 }
             } else {
                 if ($this->reverse) {
                     $mode = 'chron';
                 }
             }

             $search_engine->set_sort_mode($mode);
             $search_engine->limit($offset, $limit);
             $search_engine->query($this->q);

             $profile->find();
        } else {
            // User is browsing via AlphaNav
            $sort   = $this->getSortKey();
            $sql    = 'SELECT profile.* FROM profile, user WHERE profile.id = user.id';

            switch($this->filter)
            {
            case 'all':
                // NOOP
                break;
            case '0-9':
                $sql .=
                    '  AND LEFT(profile.nickname, 1) BETWEEN \'0\' AND \'9\'';
                break;
            default:
                $sql .= sprintf(
                    ' AND LEFT(LOWER(profile.nickname), 1) = \'%s\'',
                    $this->filter
                );
            }

            $sql .= sprintf(
                ' ORDER BY profile.%s %s, profile.nickname ASC LIMIT %d, %d',
                $sort,
                $this->reverse ? 'DESC' : 'ASC',
                $offset,
                $limit
            );

            $profile->query($sql);
        }

        return $profile;
    }

    /**
     * Filter the sort parameter
     *
     * @return string   a column name for sorting
     */
    function getSortKey()
    {
        switch ($this->sort) {
        case 'nickname':
            return $this->sort;
            break;
        case 'created':
            return $this->sort;
            break;
        default:
            return 'nickname';
        }
    }

    /**
     * Show a nice message when there's no search results
     */
    function showEmptyListMessage()
    {
        if (!empty($this->filter) && ($this->filter != 'all')) {
            $this->element(
                'p',
                'error',
                sprintf(
                    // TRANS: Empty list message for user directory.
                    _m('No users starting with %s'),
                    $this->filter
                )
            );
        } else {
            // TRANS: Empty list message for user directory.
            $this->element('p', 'error', _m('No results.'));
            // TRANS: Standard search suggestions shown when a search does not give any results.
            $message = _m("* Make sure all words are spelled correctly.
* Try different keywords.
* Try more general keywords.
* Try fewer keywords.");
            $message .= "\n";

            $this->elementStart('div', 'help instructions');
            $this->raw(common_markup_to_html($message));
            $this->elementEnd('div');
        }
    }
}


code:
/* CSS file for the Directory plugin */

div#profile_directory div.alpha_nav {
    overflow: hidden;
    width: 100%;
    text-align: center;
}

/* XXX: this needs serious CSS foo */
div#profile_directory div.alpha_nav > a {
    border-left: 1px solid #000;
    padding-left: 2px;
}
div#profile_directory div.alpha_nav > a.first {
    border-left: none;
}

div#profile_directory div.alpha_nav a:link {
    text-decoration: none;
}

div#profile_directory div.alpha_nav a:visited {
    text-decoration: none;
}
div#profile_directory div.alpha_nav a:active {
    text-decoration: none;
}
div#profile_directory div.alpha_nav a:hover {
    text-decoration: underline; color: blue;
}

div#profile_directory div.alpha_nav a.current {
    background-color:#9BB43E;
}

table.profile_list {
    width: 100%;
}

table.profile_list tr {
    float: none;
}

table.profile_list tr.alt {
    background-color: #def; /* zebra stripe */
}

table.profie_list td {
    width: 100%;
    padding: 0;
}

th.current {
    background-image: url(../images/control_arrow_down.gif);
    background-repeat: no-repeat;
    background-position: 60% 2px;
}

th.current.reverse {
    background-image: url(../images/control_arrow_up.gif);
    background-repeat: no-repeat;
    background-position: 60% 2px;
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Widget to display an alphabet menu
 *
 * 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  Widget
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 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);
}

/**
 * Outputs a fancy alphabet letter navigation menu
 *
 * @category Widget
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 * @see      HTMLOutputter
 */
class AlphaNav extends Widget
{
    protected $action  = null;
    protected $filters = array();

    /**
     * Prepare the widget for use
     *
     * @param Action  $action  the current action
     * @param boolean $numbers whether to output 0..9
     * @param Array   $prepend array of filters to prepend
     * @param Array   $append  array of filters to append
     */
    function __construct(
            $action  = null,
            $numbers = false,
            $prepend = false,
            $append  = false
    )
    {
        parent::__construct($action);

        $this->action  = $action;

        if ($prepend) {
            $this->filters = array_merge($prepend, $this->filters);
        }

        if ($numbers) {
            $this->filters = array_merge($this->filters, range(0, 9));
        }

        $this->filters = array_merge($this->filters, range('A', 'Z'));

        if ($append) {
            $this->filters = array_merge($this->filters, $append);
        }
    }

    /**
     * Show the widget
     *
     * Emit the HTML for the widget, using the configured outputter.
     *
     * @return void
     */
    function show()
    {
        $actionName = $this->action->trimmed('action');

        $this->action->elementStart('div', array('class' => 'alpha_nav'));

        for ($i = 0, $size = sizeof($this->filters); $i < $size; $i++) {

            $filter = $this->filters[$i];
            $classes = '';

            // Add some classes for styling
            if ($i == 0) {
                $classes .= 'first '; // first filter in the list
            } elseif ($i == $size - 1) {
                $classes .= 'last ';  // last filter in the list
            }

            // hack to get around $m->connect(array('action' => 'all, 'nickname' => $nickname));
            if (strtolower($filter) == 'all') {
                $href = common_local_url($actionName);
            } else {
                $href = common_local_url(
                    $actionName,
                    array('filter' => strtolower($filter))
                );
            }

            $params  = array('href' => $href);

            // sort column
            if (!empty($this->action->sort)) {
                $params['sort'] = $this->action->sort;
            }

            // sort order
            if ($this->action->reverse) {
                $params['reverse'] = 'true';
            }

            $current = $this->action->arg('filter');

            // Highlight the selected filter. If there is no selected
            // filter, highlight the last filter in the list (all)
            if (!isset($current) && $i == ($size - 1)
                || $current === strtolower($filter)) {
                $classes .= 'current ';
            }

            if (!empty($classes)) {
                $params['class'] = trim($classes);
            }

            $this->action->element('a', $params, $filter);
        }

        $this->action->elementEnd('div');
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Widget to show a sortable list of profiles
 *
 * 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  Public
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 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);
}

require_once INSTALLDIR . '/lib/subscriptionlist.php';

/**
 * Widget to show a sortable list of subscriptions
 *
 * @category Public
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class SortableGroupList extends SortableSubscriptionList
{
    /** Owner of this list */
    var $owner = null;

    function __construct($profile, $owner=null, $action=null)
    {
        parent::__construct($profile, $owner, $action);

        $this->owner = $owner;
    }

    function startList()
    {
        $this->out->elementStart('table', array('class' => 'profile_list xoxo'));
        $this->out->elementStart('thead');
        $this->out->elementStart('tr');

        $tableHeaders = array(
            // TRANS: Column header in table for user nickname.
            'nickname'    => _m('Nickname'),
            // TRANS: Column header in table for timestamp when user was created.
            'created'     => _m('Created')
        );

        foreach ($tableHeaders as $id => $label) {
            $attrs   = array('id' => $id);
            $current = (!empty($this->action->sort) && $this->action->sort == $id);

            if ($current || empty($this->action->sort) && $id == 'nickname') {
                $attrs['class'] = 'current';
            }

            if ($current && $this->action->reverse) {
                $attrs['class'] .= ' reverse';
                $attrs['class'] = trim($attrs['class']);
            }

            $this->out->elementStart('th', $attrs);

            $linkAttrs = array();
            $params    = array('sort' => $id);

            if (!empty($this->action->q)) {
                $params['q'] = $this->action->q;
            }

            if ($current && !$this->action->reverse) {
                $params['reverse'] = 'true';
            }

            $args = array();

            $filter = $this->action->arg('filter');

            if (!empty($filter)) {
                $args['filter'] = $filter;
            }

            $linkAttrs['href'] = common_local_url(
                $this->action->arg('action'), $args, $params
            );

            $this->out->element('a', $linkAttrs, $label);
            $this->out->elementEnd('th');
        }

        // TRANS: Column header in table for members of a group.
        $this->out->element('th', array('id' => 'Members'), _m('Members'));
        $this->out->element('th', array('id' => 'controls'), null);

        $this->out->elementEnd('tr');
        $this->out->elementEnd('thead');

        $this->out->elementStart('tbody');
    }

    function newListItem($profile, $odd)
    {
        return new SortableGroupListItem($profile, $this->owner, $this->action, $odd);
    }
}

class SortableGroupListItem extends SortableSubscriptionListItem
{
    /** Owner of this list */
    var $owner = null;

    function __construct($profile, $owner, $action, $alt)
    {
        parent::__construct($profile, $owner, $action, $alt);

        $this->alt   = $alt; // is this row alternate?
        $this->owner = $owner;
    }

    function showHomepage()
    {
        if (!empty($this->profile->homepage)) {
            $this->out->text(' ');
            $aAttrs = $this->homepageAttributes();
            $this->out->elementStart('a', $aAttrs);
            $this->out->raw($this->highlight($this->profile->homepage));
            $this->out->elementEnd('a');
        }
    }

    function showDescription()
    {
        if (!empty($this->profile->description)) {
            $cutoff      = 140; // XXX Should this be configurable?
            $description = htmlspecialchars($this->profile->description);

            if (mb_strlen($description) > $cutoff) {
                $description = mb_substr($description, 0, $cutoff - 1)
                    .'profile->homeUrl() .'">…';
            }

            $this->out->elementStart('p', 'note');
            $this->out->raw($description);
            $this->out->elementEnd('p');
        }

    }

    // TODO: Make sure we can do ->getAvatar() for group profiles too!
    function showAvatar(Profile $profile, $size=null)
    {
        $logo = $profile->getGroup()->stream_logo ?: User_group::defaultLogo($size ?: $this->avatarSize());

        $this->out->element('img', array('src'    => $logo,
                                         'class'  => 'avatar u-photo',
                                         'width'  => AVATAR_STREAM_SIZE,
                                         'height' => AVATAR_STREAM_SIZE,
                                         'alt'    => $profile->getBestName()));
    }

    function show()
    {
        if (Event::handle('StartProfileListItem', array($this))) {
            $this->startItem();
            if (Event::handle('StartProfileListItemProfile', array($this))) {
                $this->showProfile();
                Event::handle('EndProfileListItemProfile', array($this));
            }

            // XXX Add events?
            $this->showCreatedDate();
            $this->showMemberCount();

            if (Event::handle('StartProfileListItemActions', array($this))) {
                $this->showActions();
                Event::handle('EndProfileListItemActions', array($this));
            }
            $this->endItem();

            Event::handle('EndProfileListItem', array($this));
        }
    }

    function showProfile()
    {
        $this->startProfile();

        $this->showAvatar($this->profile->getProfile());
        $this->out->element('a', array('href'  => $this->profile->homeUrl(),
                                            'class' => 'p-org p-nickname',
                                            'rel'   => 'contact group'),
                                 $this->profile->getNickname());

        $this->showFullName();
        $this->showLocation();
        $this->showHomepage();
        $this->showDescription(); // groups have this instead of bios
        // Relevant portion!
        $this->showTags();
        $this->endProfile();
    }

    function endActions()
    {
        // delete button
        $cur = common_current_user();
        list($action, $r2args) = $this->out->returnToArgs();
        $r2args['action'] = $action;
        if ($cur instanceof User && $cur->hasRight(Right::DELETEGROUP)) {
            $this->out->elementStart('li', 'entity_delete');
            $df = new DeleteGroupForm($this->out, $this->profile, $r2args);
            $df->show();
            $this->out->elementEnd('li');
        }

        $this->out->elementEnd('ul');
        $this->out->elementEnd('td');
    }

    function showActions()
    {
        $this->startActions();
        if (Event::handle('StartProfileListItemActionElements', array($this))) {
            $this->showJoinButton();
            Event::handle('EndProfileListItemActionElements', array($this));
        }
        $this->endActions();
    }

    function showJoinButton()
    {
        $user = $this->owner;
        if ($user) {

            $this->out->elementStart('li', 'entity_subscribe');
            // XXX: special-case for user looking at own
            // subscriptions page
            if ($user->isMember($this->profile)) {
                $lf = new LeaveForm($this->out, $this->profile);
                $lf->show();
            } else if (!Group_block::isBlocked($this->profile, $user->getProfile())) {
                $jf = new JoinForm($this->out, $this->profile);
                $jf->show();
            }

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

    function showMemberCount()
    {
        $this->out->elementStart('td', 'entry_member_count');
        $this->out->raw($this->profile->getMemberCount());
        $this->out->elementEnd('td');
    }

    function showCreatedDate()
    {
        $this->out->elementStart('td', 'entry_created');
        // @todo FIXME: Should we provide i18n for timestamps in core?
        $this->out->raw(date('j M Y', strtotime($this->profile->created)));
        $this->out->elementEnd('td');
    }

    function showAdmins()
    {
        $this->out->elementStart('td', 'entry_admins');
        // @todo
        $this->out->raw('gargargar');
        $this->out->elementEnd('td');
    }

    /**
     * Only show the tags if we're logged in
     */
    function showTags()
    {
         if (common_logged_in()) {
            parent::showTags();
        }

    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Widget to show a sortable list of profiles
 *
 * 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  Public
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2011 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);
}

require_once INSTALLDIR . '/lib/subscriptionlist.php';

/**
 * Widget to show a sortable list of subscriptions
 *
 * @category Public
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class SortableSubscriptionList extends SubscriptionList
{
    /** Owner of this list */
    var $owner = null;

    function __construct($profile, $owner=null, $action=null)
    {
        parent::__construct($profile, $owner, $action);

        $this->owner = $owner;
    }

    function startList()
    {
        $this->out->elementStart('table', array('class' => 'profile_list xoxo'));
        $this->out->elementStart('thead');
        $this->out->elementStart('tr');

        $tableHeaders = array(
            // TRANS: Column header in table for user nickname.
            'nickname'    => _m('Nickname'),
            // TRANS: Column header in table for timestamp when user was created.
            'created'     => _m('Created')
        );

        foreach ($tableHeaders as $id => $label) {

            $attrs   = array('id' => $id);
            $current = (!empty($this->action->sort) && $this->action->sort == $id);

            if ($current || empty($this->action->sort) && $id == 'nickname') {
                $attrs['class'] = 'current';
            }

            if ($current && $this->action->reverse) {
                $attrs['class'] .= ' reverse';
                $attrs['class'] = trim($attrs['class']);
            }

            $this->out->elementStart('th', $attrs);

            $linkAttrs = array();
            $params    = array('sort' => $id);

            if (!empty($this->action->q)) {
                $params['q'] = $this->action->q;
            }

            if ($current && !$this->action->reverse) {
                $params['reverse'] = 'true';
            }

            $args = array();

            $filter = $this->action->arg('filter');

            if (!empty($filter)) {
                $args['filter'] = $filter;
            }

            $linkAttrs['href'] = common_local_url(
                $this->action->arg('action'), $args, $params
            );

            $this->out->element('a', $linkAttrs, $label);
            $this->out->elementEnd('th');
        }

        // TRANS: Column header for number of subscriptions.
        $this->out->element('th', array('id' => 'subscriptions'), _m('Subscriptions'));
        // TRANS: Column header for number of notices.
        $this->out->element('th', array('id' => 'notices'), _m('Notices'));
        $this->out->element('th', array('id' => 'controls'), null);

        $this->out->elementEnd('tr');
        $this->out->elementEnd('thead');

        $this->out->elementStart('tbody');
    }

    function endList()
    {
        $this->out->elementEnd('tbody');
        $this->out->elementEnd('table');
    }

    function showProfiles()
    {
        // Note: we don't use fetchAll() because it's borked with query()

        $profiles = array();

        while ($this->profile->fetch()) {
            $profiles[] = clone($this->profile);
        }

        $cnt = count($profiles);

        $max = min($cnt, $this->maxProfiles());

        for ($i = 0; $i < $max; $i++) {
            $odd = ($i % 2 == 0); // for zebra striping
            $pli = $this->newListItem($profiles[$i], $odd);
            $pli->show();
        }

        return $cnt;
    }

    function newListItem($profile, $odd)
    {
        return new SortableSubscriptionListItem($profile, $this->owner, $this->action, $odd);
    }
}

class SortableSubscriptionListItem extends SubscriptionListItem
{
    /** Owner of this list */
    var $owner = null;

    function __construct($profile, $owner, $action, $alt)
    {
        parent::__construct($profile, $owner, $action);

        $this->alt   = $alt; // is this row alternate?
        $this->owner = $owner;
    }

    function startItem()
    {
        $attr = array(
            'class' => 'profile',
            'id'    => 'profile-' . $this->profile->id
        );

        if ($this->alt) {
            $attr['class'] .= ' alt';
        }

        $this->out->elementStart('tr', $attr);
    }

    function endItem()
    {
        $this->out->elementEnd('tr');
    }

    function startProfile()
    {
        $this->out->elementStart('td', 'entity_profile h-card');
    }

    function endProfile()
    {
        $this->out->elementEnd('td');
    }

    function startActions()
    {
        $this->out->elementStart('td', 'entity_actions');
        $this->out->elementStart('ul');
    }

    function endActions()
    {

		// delete button
		$cur = common_current_user();
        list($action, $r2args) = $this->out->returnToArgs();
        $r2args['action'] = $action;
		if ($cur instanceof User && $cur->hasRight(Right::DELETEUSER)) {
			$this->out->elementStart('li', 'entity_delete');
			$df = new DeleteUserForm($this->out, $this->profile, $r2args);
			$df->show();
			$this->out->elementEnd('li');
		}

        $this->out->elementEnd('ul');
        $this->out->elementEnd('td');
    }

    function show()
    {
        if (Event::handle('StartProfileListItem', array($this))) {
            $this->startItem();
            if (Event::handle('StartProfileListItemProfile', array($this))) {
                $this->showProfile();
                Event::handle('EndProfileListItemProfile', array($this));
            }

            // XXX Add events?
            $this->showCreatedDate();
            $this->showSubscriberCount();
            $this->showNoticeCount();

            if (Event::handle('StartProfileListItemActions', array($this))) {
                $this->showActions();
                Event::handle('EndProfileListItemActions', array($this));
            }
            $this->endItem();
            Event::handle('EndProfileListItem', array($this));
        }
    }

    function showSubscriberCount()
    {
        $this->out->elementStart('td', 'entry_subscriber_count');
        $this->out->raw($this->profile->subscriberCount());
        $this->out->elementEnd('td');
    }

    function showCreatedDate()
    {
        $this->out->elementStart('td', 'entry_created');
        $this->out->raw(date('j M Y', strtotime($this->profile->created)));
        $this->out->elementEnd('td');
    }

    function showNoticeCount()
    {
        $this->out->elementStart('td', 'entry_notice_count');
        $this->out->raw($this->profile->noticeCount());
        $this->out->elementEnd('td');
    }

    /**
     * Overrided to truncate the bio if it's real long, because it
     * looks better that way in the SortableSubscriptionList's table
     */
    function showBio()
    {
        if (!empty($this->profile->bio)) {
            $cutoff = 140; // XXX Should this be configurable?
            $bio    = htmlspecialchars($this->profile->bio);

            if (mb_strlen($bio) > $cutoff) {
                $bio = mb_substr($bio, 0, $cutoff - 1)
                    .'profile->profileurl .'">…';
            }

            $this->out->elementStart('p', 'note');
            $this->out->raw($bio);
            $this->out->elementEnd('p');
        }
    }

    /**
     * Only show the tags if we're logged in
     */
    function showTags()
    {
         if (common_logged_in()) {
            parent::showTags();
        }

    }
}